前言

二叉树的遍历是一个比较常见的问题,递归实现二叉树的前中后序遍历比较简单,但非递归实现二叉树的前中后序遍历相对有难度。这篇博客将详述如何使用非递归的方式实现二叉树的前中后序遍历,在进行理论描述的同时会附上递归实现以及非递归实现的代码。此外,本文还将描述二叉树的层次遍历以及zig-zag型遍历。

一、前序遍历

1、概念

对于一棵二叉树,前序遍历将先遍历根节点,再遍历左子树(如果存在的话),最后遍历右子树(如果存在的话)。其中左右子树又分别是一棵二叉树,因此,它们的遍历也是采用根节点->左子树->右子树的顺序。

2、递归实现

递归实现前序遍历比较简单,直接按照概念来即可,需要注意的是,遍历前要对二叉树的根节点判断是否为空。

struct treeNode {treeNode *left, *right;int val;treeNode(int a=0):left(nullptr), right(nullptr), val(a){};
};void preOrderRecursively(const treeNode *root) {if(root) {cout << root->val << " ";preOrderRecursively(root->left);preOrderRecursively(root->right);}
}

3、非递归实现

非递归实现前序遍历也相对简单,使用栈这样一个数据结构即可实现,栈顶元素即为每次访问的元素。首先将非空的根节点入栈,其次访问根节点并将其出栈。由于前序遍历需要先访问根节点的左子树,再访问根节点的右子树,而根据栈先进后出的特性,接下来我们需要将根节点的右孩子先入栈(如果存在右孩子的话),再将根节点的左孩子入栈(如果存在左孩子的话)。接下来继续访问栈顶元素并将其出栈,再将其右孩子(如果存在的话)、左孩子(如果存在的话)分别入栈。重复这样一个过程,直到栈中没有元素。源码如下:

void preOrder(const treeNode *root) {if(!root) {return;}stack<const treeNode*> s;const treeNode *t=nullptr;s.push(root);while(!s.empty()) {t=s.top();s.pop();cout << t->val << " ";if(t->right) {s.push(t->right);}if(t->left) {s.push(t->left);}}cout << endl;
}

二、中序遍历

1、概念

中序遍历是指先访问二叉树的左子树,再访问二叉树的根节点,最后访问二叉树的右子树。对于左右子树这两个二叉树来说,访问同样遵循左子树->根节点->右子树的顺序。

2、递归实现

中序遍历的递归实现也很简单,直接按照概念来。需要注意的是,一开始同样需要判断根节点是否为空。

void inOrderRecursively(const treeNode *root) {if(root) {inOrderRecursively(root->left);cout << root->val << " ";inOrderRecursively(root->right);}
}

3、非递归实现

中序遍历的非递归实现稍微复杂一点,同样需要借助栈这个数据结构来实现。首先我们回顾一下中序遍历的概念,对于一个二叉树来说,中序遍历先访问二叉树的左子树,再访问二叉树的根节点,最后访问二叉树的右子树。而对于左子树来说,中序遍历继续访问其左子树,再访问其根节点与右子树。因此,我们首先需要将整棵树的根节点入栈,其次将栈顶元素的左孩子依次入栈,直至栈顶元素没有左孩子。此时,栈顶元素没有左孩子,因此访问栈顶元素(将其记为节点t1)并将其出栈。

当访问完节点t1之后,需要判断它是否有右孩子。

当节点t1没有右孩子时,只需要继续访问栈顶元素并将其出栈即可,接下来仍然是进一步判断刚出栈的元素是否存在右孩子并继续上述过程。

当节点t1存在右孩子,则我们需要将右孩子入栈,同时我们需要将栈顶元素的左孩子依次入栈(如果存在的话),直至栈顶元素没有左孩子。此时,栈顶元素没有左孩子,因此访问栈顶元素(将其记为节点t2)并将其出栈。

同样的道理,访问完节点t2之后,也需要进一步判断它是否有右孩子。接下来就是跟节点t1类似的过程。

下面给出一个具体的例子。

第一步:节点1,2,4入栈

第二步:由于节点4没有左孩子,因此访问节点4并将其出栈。同时,由于节点4有右孩子,因此我们将节点4的右孩子节点8入栈并将节点8的左孩子节点10入栈

第三步:由于节点10没有左孩子,因此访问节点10并将其出栈

第四步:由于节点10也没有右孩子,因此继续访问栈顶元素节点8并将其出栈

第五步:由于节点8也没有右孩子,因此继续访问栈顶元素节点2并将其出栈。同时,由于节点2有右孩子,因此,我们将节点2的右孩子节点5以及节点5的左孩子节点9入栈

第六步:由于节点9没有左孩子,因此访问节点9并将其出栈

第七步:由于节点9也没有右孩子,因此继续访问栈顶元素节点5并将其出栈

第八步:由于节点5没有右孩子,因此继续访问栈顶元素节点1并将其出栈。同时,由于节点1存在右孩子,因此将节点1的右孩子节点3以及节点3的左孩子节点6入栈

第九步:由于节点6没有左孩子,因此访问节点6并将其出栈

第十步:由于节点6也没有右孩子,因此继续访问栈顶元素节点3并将其出栈。同时,由于节点3存在右孩子,因此将节点3的右孩子节点7入栈

第十一步:访问节点7。栈空。

源码如下:

void inOrder(const treeNode *root){if(!root) {return;}stack<const treeNode*> s;const treeNode *t=nullptr;s.push(root);while(!s.empty()) {while(s.top()->left) {s.push(s.top()->left);}while(!s.empty()) {t=s.top(); s.pop();cout << t->val << " ";if(t->right) {s.push(t->right);break;}}}cout << endl;
}

三、后序遍历

1、概念

后序遍历先访问左子树,再访问右子树,最后访问根节点。

2、递归实现

void postOrderRecursively(const treeNode *root) {if(root) {postOrderRecursively(root->left);postOrderRecursively(root->right);cout << root->val << " ";}
}

3、非递归实现

后序遍历的非递归实现与中序遍历的大体相同。首先仍然是将整棵树的根节点入栈,其次将栈顶元素的左孩子依次入栈,直至栈顶元素没有左孩子。

接下来需要判断栈顶元素是否存在右孩子。

若栈顶元素存在右孩子且该右孩子与最近刚出栈的元素不同,则将右孩子也入栈,同时将栈顶元素的左孩子依次入栈,直至栈顶元素没有左孩子。接下来继续判断栈顶元素是否存在右孩子,重复上述过程。

否则,则直接访问栈顶元素并将其出栈。接下来继续判断栈顶元素是否存在右孩子,重复上述过程。

之所以增加红色部分的判断,是为了避免出现下面这种情况:若节点8是节点4的右孩子,节点8是叶子节点,且4和8都在栈中,那么由于节点8没有左孩子也没有右孩子,则我们访问节点8并将其出栈。接下来是判断节点4是否存在右孩子。如果没有红色部分的判断,那么节点8又会入栈,从而导致死循环。而增加红色部分的判断,是为了避免节点8再次入栈。

源码如下:

void postOrder(const treeNode *root){if(!root) {return ;}stack<const treeNode*> s;s.push(root);const treeNode *prev=nullptr;while(!s.empty()) {while(s.top()->left) {s.push(s.top()->left);}while(!s.empty()) {if(s.top()->right&&s.top()->right!=prev) {s.push(s.top()->right);break;}cout << s.top()->val << " ";prev=s.top();s.pop();}}cout << endl;
}

四、层次遍历

1、概念

层次遍历指的是按照树的层次对树进行遍历,即按照从根节点往叶子节点,从左往右的顺序遍历二叉树。上面中序遍历的例子中,层次遍历序列即为1,2,3,4,5,6,7,8,9,10

2、实现

基本的层次遍历的实现还是比较简单,借助队列queue数据结构即可实现。若要增加一点难度,则需要在输出时体现层次感,将不同层的节点放在不同行输出,而同一层的节点在相同行输出。上面中序遍历的例子中,改进版的层次遍历序列即为

1

2 3

4 5 6 7

8 9

10

解决方案是增加一个计数器,记录现在正在输出的节点所属行的节点总数,每次输出该行的一个节点,计数器减一。当计数器变为0时,即输出换行符,并将其值重置为队列的节点总数(下一行的节点总数)。

void traverseTreeLinely(const treeNode *root) {if(!root) {return;}queue<const treeNode*> q;unsigned numInQueue=1;q.push(root);const treeNode *t=nullptr;while(!q.empty()) {t=q.front(); q.pop(); --numInQueue;cout << t->val;if(t->left) {q.push(t->left);}if(t->right) {q.push(t->right);}if(numInQueue) {cout << " ";}else {cout << endl;numInQueue+=q.size();}}
}

五、Zig-Zag遍历

1、概念

Zig-Zag遍历指的是相邻两层按照从左到右和从右到左的顺序遍历。第一层从左到右遍历,第二层从右到左遍历,第三层又变成从左到右遍历...上面中序遍历的例子中,层次遍历序列即为1,3,2,4,5,6,7,9,8,10

2、实现

借助两个栈即可实现。

void traverseTreeZigZag(const treeNode *root) {if(!root) {return;}stack<const treeNode*> leftFirst, rightFirst;rightFirst.push(root);while(!leftFirst.empty()||!rightFirst.empty()) {if(!rightFirst.empty()) {while(!rightFirst.empty()) {cout << rightFirst.top()->val << " ";if(rightFirst.top()->left) {leftFirst.push(rightFirst.top()->left);}if(rightFirst.top()->right) {leftFirst.push(rightFirst.top()->right);}rightFirst.pop();}cout << endl;}if(!leftFirst.empty()) {while(!leftFirst.empty()) {cout << leftFirst.top()->val << " ";if(leftFirst.top()->right) {rightFirst.push(leftFirst.top()->right);}if(leftFirst.top()->left) {rightFirst.push(leftFirst.top()->left);}leftFirst.pop();}cout << endl;}}
}

六、附录

附上测试代码

/** main.cpp**  Created on: 2019年7月6日*      Author: lee*/
#include<iostream>
#include<queue>
#include<stack>
using namespace std;struct treeNode {treeNode *left, *right;int val;treeNode(int a=0):left(nullptr), right(nullptr), val(a){};
};treeNode* constructBinTree();
void traverseTreeLinely(const treeNode*);
void traverseTreeZigZag(const treeNode*);
void preOrderRecursively(const treeNode*);
void preOrder(const treeNode*);
void inOrderRecursively(const treeNode*);
void inOrder(const treeNode*);
void postOrderRecursively(const treeNode*);
void postOrder(const treeNode*);
void deleteTree(treeNode*&);int main() {cout << "Construct a binary tree." << endl;treeNode *root=constructBinTree();cout << "Traverse the tree linely:" << endl;traverseTreeLinely(root);cout << "Traverse the tree in a zig-zag manner:" << endl;traverseTreeZigZag(root);cout << "Traverse the tree in pre-order:" << endl;preOrderRecursively(root);cout << endl;preOrder(root);cout << "Traverse the tree in in-order:" << endl;inOrderRecursively(root);cout << endl;inOrder(root);cout << "Traverse the tree in post-order:" << endl;postOrderRecursively(root);cout << endl;postOrder(root);cout << "Destroy the tree." << endl;deleteTree(root);return 0;
}treeNode* constructBinTree() {treeNode *root=new treeNode(1);root->left=new treeNode(2);root->right=new treeNode(3);root->left->left=new treeNode(4);root->left->right=new treeNode(5);root->right->left=new treeNode(6);root->right->right=new treeNode(7);root->left->left->right=new treeNode(8);root->left->right->left=new treeNode(9);root->left->left->right->left=new treeNode(10);return root;
}void traverseTreeLinely(const treeNode *root) {if(!root) {return;}queue<const treeNode*> q;unsigned numInQueue=1;q.push(root);const treeNode *t=nullptr;while(!q.empty()) {t=q.front(); q.pop(); --numInQueue;cout << t->val;if(t->left) {q.push(t->left);}if(t->right) {q.push(t->right);}if(numInQueue) {cout << " ";}else {cout << endl;numInQueue+=q.size();}}
}void traverseTreeZigZag(const treeNode *root) {if(!root) {return;}stack<const treeNode*> leftFirst, rightFirst;rightFirst.push(root);while(!leftFirst.empty()||!rightFirst.empty()) {if(!rightFirst.empty()) {while(!rightFirst.empty()) {cout << rightFirst.top()->val << " ";if(rightFirst.top()->left) {leftFirst.push(rightFirst.top()->left);}if(rightFirst.top()->right) {leftFirst.push(rightFirst.top()->right);}rightFirst.pop();}cout << endl;}if(!leftFirst.empty()) {while(!leftFirst.empty()) {cout << leftFirst.top()->val << " ";if(leftFirst.top()->right) {rightFirst.push(leftFirst.top()->right);}if(leftFirst.top()->left) {rightFirst.push(leftFirst.top()->left);}leftFirst.pop();}cout << endl;}}
}void preOrderRecursively(const treeNode *root) {if(root) {cout << root->val << " ";preOrderRecursively(root->left);preOrderRecursively(root->right);}
}void preOrder(const treeNode *root) {if(!root) {return;}stack<const treeNode*> s;const treeNode *t=nullptr;s.push(root);while(!s.empty()) {t=s.top();s.pop();cout << t->val << " ";if(t->right) {s.push(t->right);}if(t->left) {s.push(t->left);}}cout << endl;
}void inOrderRecursively(const treeNode *root) {if(root) {inOrderRecursively(root->left);cout << root->val << " ";inOrderRecursively(root->right);}
}void inOrder(const treeNode *root){if(!root) {return;}stack<const treeNode*> s;const treeNode *t=nullptr;s.push(root);while(!s.empty()) {while(s.top()->left) {s.push(s.top()->left);}while(!s.empty()) {t=s.top(); s.pop();cout << t->val << " ";if(t->right) {s.push(t->right);break;}}}cout << endl;
}void postOrderRecursively(const treeNode *root) {if(root) {postOrderRecursively(root->left);postOrderRecursively(root->right);cout << root->val << " ";}
}void postOrder(const treeNode *root){if(!root) {return ;}stack<const treeNode*> s;s.push(root);const treeNode *prev=nullptr;while(!s.empty()) {while(s.top()->left) {s.push(s.top()->left);}while(!s.empty()) {if(s.top()->right&&s.top()->right!=prev) {s.push(s.top()->right);break;}cout << s.top()->val << " ";prev=s.top();s.pop();}}cout << endl;
}void deleteTree(treeNode *&root) {if(root) {deleteTree(root->left);deleteTree(root->right);delete root;root=nullptr;}
}

二叉树前中后序遍历的非递归实现以及层次遍历、zig-zag型遍历详解相关推荐

  1. 二叉树前中后序遍历以及节点计算

    二叉树前中后序遍历以及节点计算 二叉树 分类 二叉链的数据结构 三叉链的数据结构 四种遍历方法 深度优先遍历:前中后序 广度优先遍历:层序遍历 计算 节点个数 叶子节点个数 树的高度 第k层的节点个数 ...

  2. 二叉树前中后序遍历+刷题【中】【数据结构/初阶/C语言实现】

    文章目录 1. 二叉树基础操作 1.1 二叉树遍历 1.1.1 前序遍历 前序遍历(Pre-Order Traversal) 1.1.2 中序遍历 中序遍历(In-Order Traversal) 1 ...

  3. 【霍罗维兹数据结构】二叉树前中后序遍历 | 层序遍历 | 复制二叉树 | 判断两个二叉树全等 | 可满足性问题

    写在前面 学习二叉树结构,最简单的方式就是遍历.所谓二叉树遍历,就是按照某种特定的规则,一次对二叉树中的节点进行相应的操作,并且每个节点只操作一次. 访问节点所做的操作要看具体的应用问题.遍历是二叉树 ...

  4. 线索二叉树(前中后序线索化/遍历/画线索)

    线索二叉树 文章目录 线索二叉树 1 线索二叉树的基本概念 2 线索二叉树的构造 2.1 线索二叉树的存储结构 2.2 给线索二叉树画线索 2.2.1 中序 2.2.2 先序 2.2.3 后序 2.3 ...

  5. 前中后序的迭代和递归写法

    前序 递归 class Solution { public:vector<int> preorderTraversal(TreeNode* root) {vector<int> ...

  6. LeetCode——树:层次遍历、前中后序遍历

    LeetCode--树:层次遍历.前中后序遍历 目录 层次遍历 二叉树的层平均值 找树左下角的值 前中后序遍历 概述 非递归实现二叉树的前序遍历 非递归实现二叉树的中序遍历 非递归实现二叉树的后序遍历 ...

  7. 二叉树非递归dfs——简单思路搞定前中后序遍历

    前言:相信很多同学都被二叉树非递归dfs的前中后序遍历方法弄的头疼.网上的答案,什么前中后序遍历各有一套写法,还有什么一个栈的写法,两个栈的写法.看起来能理解,一闭眼自己写都记不住.今天介绍一种用一种 ...

  8. python实现二叉树非递归前中后序遍历

    python实现二叉树非递归前中后层序遍历 二叉树是数据结构中重要的一部分,本文简单介绍用python实现二叉树的前中后序遍历,包括递归和非递归思路算法. # -*- 二叉树 begin -*- # ...

  9. 数据结构与算法(java):树-二叉树(二叉查找树(BST)、线索化二叉树、哈夫曼树、平衡二叉树【AVL】、二叉树的前中后序遍历)

    二叉树 1.定义 二叉树 就是度不超过2的树(每个结点最多只有两个子结点).如图 2.特殊二叉树 满二叉树 当二叉树的每一个层的结点树都达到最大值,则这个二叉树就是满二叉树. 完全二叉树 叶结点只能出 ...

最新文章

  1. python的基本知识点
  2. 用缓动函数模拟物理动画
  3. 10. 解析XML文件(SAX/DOM/ElementTre)
  4. 12核心 联发科和台积电将研发7nm芯片
  5. .net core连接MongoDB
  6. 【SAM】差异(P4248)
  7. MySQL子查询作为列_mysql 列子查询
  8. 问题 C: 判断三角形的性质
  9. 翁恺老师C语言学习笔记(十)指针_指针变量就是记录地址的变量
  10. crt是什么意思 windows编程_软件工程师应该知道的关于Windows API、CRT和STL二三事...
  11. 可以用计算机控制手机的软件,还有这样的神奇软件!让你用PC操控手机的神器...
  12. 快讯分类_如何掌握Google快讯
  13. JAVA操作Excel(POI、easyPOI、easyExcel)
  14. 【C语言】案例十六:掷骰子(随机数)
  15. Towards More Flexible and Accurate Object Tracking with Natural Language:Algorithms and Benchmark
  16. table表格表头单元格添加斜线
  17. JAVA之线程子类秒表(静态)
  18. 加拿大学校申请条件获关注,雅思专家有话说
  19. python获取所有a股股票代码_股票量化分析(一)获取A股列表
  20. Elang在Windows console下显示中文

热门文章

  1. 【Windows】修改C盘用户名称
  2. hyperLynx VX2.5 PCB仿真
  3. 小学生python游戏开发pygame5--title地图调用
  4. 三星android5.0 蓝牙,蓝牙5.0手机有哪些 蓝牙5.0和4.2的区别是什么【区别介绍】
  5. 实体店也可以代办?外卖市场还需严加监管
  6. AD画螺旋形走线或者天线的操作
  7. 史上最全因果推断合集-12(因果推断在哈啰出行的实践探索)
  8. Linux上的文件类型与默认图标
  9. python的接口和抽象类
  10. 99.扩展.霍尔三维结构方法论 vs 切克兰德方法论