对于树结构,最重要的部分莫过于遍历,因为对于树的其他操作,都离不开遍历操作,而其中最典型的遍历,便是对二叉树的遍历,说到二叉树的遍历,我们首先想到的肯定是用递归实现的先序、中序和后序遍历,因为代码简洁明了,也很容易理解,但是众所周知,递归实现的代码效率肯定不高,因此在这里,除了介绍常用的递归实现之外,还将介绍非递归实现的方法。

这里使用的是C++实现。

准备工作

首先这里使用的树结构如下所示,可以看到,这是一颗二叉排序树:

相应的代码如下所示:

typedef struct BiTNode {int data;struct BiTNode* lChild, * rChild;  //左右孩子指针
}BiTNode;typedef struct {BiTNode* root;
}BiTree;void insert(BiTree &tree, int data) {  //根据二叉排序树的思想插入结点BiTNode* node = new(BiTNode);node->data = data;node->lChild = node->rChild = nullptr;if (tree.root == nullptr) {tree.root = node;} else {BiTNode* temp = tree.root;while (temp != nullptr) {if (node->data < temp->data) {if (temp->lChild == nullptr) {temp->lChild = node;return;} else {temp = temp->lChild;}} else {if (temp->rChild == nullptr) {temp->rChild = node;return;} else {temp = temp->rChild;}}}}
}

然后在main()函数中添加上这段代码:

int data[10] = { 5, 3, 9, 2, 4, 1, 7, 10, 6, 8 };
BiTree tree;
tree.root = nullptr;
for (int i = 0; i < 10; i++)  insert(tree, data[i]);

那么接下来就进入到正题了。

递归版本实现二叉树遍历

void preOrder1(BiTNode* root) {  //先序遍历递归版if (root) {cout << root->data << " ";preOrder1(root->lChild);preOrder1(root->rChild);}
}void inOrder1(BiTNode* root) {  //中序遍历递归版if (root) {inOrder1(root->lChild);cout << root->data << " ";inOrder1(root->rChild);}
}void postOrder1(BiTNode* root) {  //后序遍历递归版if (root) {postOrder1(root->lChild);postOrder1(root->rChild);cout << root->data << " ";}
}

main()函数中显示:

int main() {int data[10] = { 5, 3, 9, 2, 4, 1, 7, 10, 6, 8 };BiTree tree;tree.root = nullptr;for (int i = 0; i < 10; i++)  insert(tree, data[i]);cout << "递归版先序遍历:" << endl;preOrder1(tree.root);cout << endl << "递归版中序遍历:" << endl;inOrder1(tree.root);cout << endl << "递归版后序遍历:" << endl;postOrder1(tree.root);delTree(tree.root);cout << endl << "释放成功!" << endl;return 0;
}

得到的运行结果如下:

非递归版本实现二叉树遍历

递归版本转换为非递归版本往往需要使用到栈结构。

非递归版本中先序遍历和中序遍历的思想是一致的:

void preOrder2(BiTNode* root) {  //先序遍历非递归版stack<BiTNode*> S;BiTNode* t = root;while (t || !S.empty()) {if (t) {  //若当前节点不为空,则先访问当前节点,入栈,再访问左子树cout << t->data << " ";S.push(t);t = t->lChild;} else {  //否则就访问栈顶结点的右子树t = S.top();S.pop();t = t->rChild;}}
}void inOrder2(BiTNode* root) {  //中序遍历非递归版stack<BiTNode*> S;BiTNode* t = root;while (t || !S.empty()) {if (t) {S.push(t);t = t->lChild;} else {t = S.top();S.pop();cout << t->data << " ";t = t->rChild;}}
}

以先序遍历为例,首先判断当前节点是否为空,若不为空,则首先访问当前节点,之后需要将该节点入栈,这是为了后面访问该节点的右子树做准备的,然后指针指向该节点的左子树;若当前节点为空(这种情况就相当于是叶子节点的左指针,当然也可能是上一个节点只有右子树),说明上一个节点的左子树已经访问过了,那么根据先序遍历的思想,我们需要访问上一个节点的右子树,而上一个节点便是栈顶元素,因此首先需要出栈,然后指针指向其右子树,中序遍历类似。

非递归实现的后序遍历就要稍微复杂一点了,因为根节点是最后才访问的:

void postOrder2(BiTNode* root) {  //后序遍历非递归版stack<BiTNode*> S;BiTNode* t = root;BiTNode* r = nullptr;while (t || !S.empty()) {if (t) {S.push(t);t = t->lChild;} else {t = S.top();  //这里只是得到栈顶元素并未出栈if (t->rChild && t->rChild != r) {  //若有右子树且右子树未被访问过t = t->rChild;} else {S.pop();  //因为左右子树都已经被访问过了,所以栈顶元素应该被弹出栈了cout << t->data << " ";r = t;  //记录最近访问过的节点t = nullptr;  //节点访问完以后,需要将指针重置}}}
}

对于后序遍历,这里可能会有些难以理解,所以我们以前面的二叉树为例,大致地走一下流程:

首先根节点是5,根据代码我们可以知道,开始的几步都是进入if语句中,依次入栈的节点分别为5、3、2、1,然后1入栈之后,由于1是叶子节点,因此它的左子树为空,于是进入到else语句中,然后得到栈顶元素即1,注意这里并没有弹出栈顶元素,因为到这一步仅仅只是该节点的左子树遍历完了,再判断它的右子树是否存在且未被访问过,因为右子树为空,所以进入到else语句中,到了这里就说明对于以当前节点为根节点的子树,它的左右子树都已经遍历过了,所以需要将栈顶节点出栈,然后用r记录节点1,表示最近访问过的节点,同时我们需要将t指向nullptr,后面以此类推。

问题一、为什么在访问过节点t之后,需要将t指向nullptr?

这是因为若不将t指向nullptr,则接下来就会进入到if语句中。如上面介绍过的,t指向节点1,访问完节点1之后不指向nullptr的话,那么又会进入到if语句中,然后又是将节点1入栈,访问它的左子树,之后又进入到else语句中,访问节点1,以此往复,没错,进入死循环了!所以这里必须将t指向nullptr。

问题二、为什么要添加一个指针r来记录最近访问过的节点?

我们同样用实例来说明,在访问完1、2以后,我们便通过else语句进入到了节点3,由于右子树存在,于是我们进入到了节点4,由于节点4是叶子节点,因此访问完节点4之后,我们重新通过else语句进入到节点3,但是此时的节点3左右子树都已经访问过了,因此我们应该访问的节点应该是节点3,但是若没有指针r记录最近访问的节点的话,我们又进入到了节点4,于是乎,我们又进入死循环了!所以我们必须有一个指针r来记录最近访问的节点。

由上面的两个问题我们也可以发现,后序遍历的非递归实现确实要比先序遍历、中序遍历的非递归实现难一些。

main()函数中显示:

int main() {int data[10] = { 5, 3, 9, 2, 4, 1, 7, 10, 6, 8 };BiTree tree;tree.root = nullptr;for (int i = 0; i < 10; i++)  insert(tree, data[i]);cout << "非递归版先序遍历:" << endl;preOrder2(tree.root);cout << endl << "非递归版中序遍历:" << endl;inOrder2(tree.root);cout << endl << "非递归版后序遍历:" << endl;postOrder2(tree.root);delTree(tree.root);cout << endl << "释放成功!" << endl;return 0;
}

运行结果如下:

层序遍历

那么我们接下来再来介绍一下二叉树的层序遍历,层序遍历其实就是树结构中的BFS,因此层序遍历也要用到队列,还是以上面的二叉树为例:

层序遍历部分的代码:

void levelOrder(BiTNode* root) {queue<BiTNode*> Q;BiTNode* t;Q.push(root);while (!Q.empty()) {t = Q.front();Q.pop();cout << t->data << " ";if (t->lChild) {Q.push(t->lChild);}if (t->rChild) {Q.push(t->rChild);}}
}

在main()函数中调用:

int main() {int data[10] = { 5, 3, 9, 2, 4, 1, 7, 10, 6, 8 };BiTree tree;tree.root = nullptr;for (int i = 0; i < 10; i++)  insert(tree, data[i]);cout << "层序遍历:" << endl;levelOrder(tree.root);delTree(tree.root);cout << endl << "释放成功!" << endl;return 0;
}

运行结果如下:

完整代码

#include<iostream>
#include<stack>
using namespace std;typedef struct BiTNode {int data;struct BiTNode* lChild, * rChild;  //左右孩子指针
}BiTNode;typedef struct {BiTNode* root;
}BiTree;void insert(BiTree &tree, int data) {  //根据二叉排序树的思想插入结点BiTNode* node = new(BiTNode);node->data = data;node->lChild = node->rChild = nullptr;if (tree.root == nullptr) {tree.root = node;} else {BiTNode* temp = tree.root;while (temp != nullptr) {if (node->data < temp->data) {if (temp->lChild == nullptr) {temp->lChild = node;return;} else {temp = temp->lChild;}} else {if (temp->rChild == nullptr) {temp->rChild = node;return;} else {temp = temp->rChild;}}}}
}void delTree(BiTNode* root) {if (root == nullptr) return;delTree(root->lChild);delTree(root->rChild);delete(root);
}void preOrder1(BiTNode* root) {  //先序遍历递归版if (root) {cout << root->data << " ";preOrder1(root->lChild);preOrder1(root->rChild);}
}void inOrder1(BiTNode* root) {  //中序遍历递归版if (root) {inOrder1(root->lChild);cout << root->data << " ";inOrder1(root->rChild);}
}void postOrder1(BiTNode* root) {  //后序遍历递归版if (root) {postOrder1(root->lChild);postOrder1(root->rChild);cout << root->data << " ";}
}void preOrder2(BiTNode* root) {  //先序遍历非递归版stack<BiTNode*> S;BiTNode* t = root;while (t || !S.empty()) {if (t) {  //若当前节点不为空,则先访问当前节点,入栈,再访问左子树cout << t->data << " ";S.push(t);t = t->lChild;} else {  //否则就访问栈顶结点的右子树t = S.top();S.pop();t = t->rChild;}}
}void inOrder2(BiTNode* root) {  //中序遍历非递归版stack<BiTNode*> S;BiTNode* t = root;while (t || !S.empty()) {if (t) {S.push(t);t = t->lChild;} else {t = S.top();S.pop();cout << t->data << " ";t = t->rChild;}}
}void postOrder2(BiTNode* root) {  //后序遍历非递归版stack<BiTNode*> S;BiTNode* t = root;BiTNode* r = nullptr;while (t || !S.empty()) {if (t) {S.push(t);t = t->lChild;} else {t = S.top();  //这里只是得到栈顶元素并未出栈if (t->rChild && t->rChild != r) {  //若有右子树且右子树未被访问过t = t->rChild;} else {S.pop();  //因为左右子树都已经被访问过了,所以栈顶元素应该被弹出栈了cout << t->data << " ";r = t;  //记录最近访问过的节点t = nullptr;  //节点访问完以后,需要将指针重置}}}
}int main() {int data[10] = { 5, 3, 9, 2, 4, 1, 7, 10, 6, 8 };BiTree tree;tree.root = nullptr;for (int i = 0; i < 10; i++)  insert(tree, data[i]);cout << "递归版先序遍历:" << endl;preOrder1(tree.root);cout << endl << "递归版中序遍历:" << endl;inOrder1(tree.root);cout << endl << "递归版后序遍历:" << endl;postOrder1(tree.root);cout << endl;cout << "非递归版先序遍历:" << endl;preOrder2(tree.root);cout << endl << "非递归版中序遍历:" << endl;inOrder2(tree.root);cout << endl << "非递归版后序遍历:" << endl;postOrder2(tree.root);delTree(tree.root);cout << endl << "释放成功!" << endl;return 0;
}

运行结果如下:

二叉树遍历之递归与非递归遍历相关推荐

  1. 二叉树的建造、递归与非递归遍历

    #include "stdafx.h" #include <iostream> #include <stack> #include <queue> ...

  2. 遍历二叉树的各种操作(非递归遍历)

    先使用先序的方法建立一棵二叉树,然后分别使用递归与非递归的方法实现前序.中序.后序遍历二叉树,并使用了两种方法来进行层次遍历二叉树,一种方法就是使用STL中的queue,另外一种方法就是定义了一个数组 ...

  3. 二叉树的几种递归和非递归式遍历:

    二叉树的几种递归和非递归式遍历: 1 #include <fstream> 2 #include <iostream> 3 4 using namespace std; 5 6 ...

  4. 分别用递归和非递归方式实现二叉树先序、中序和后序遍历(java实现)

    分别用递归和非递归方式实现二叉树先序.中序和后序遍历 用递归和非递归方式,分别按照二叉树先序.中序和后序打印所有的节点.我们约定:先序遍历顺序 为根.左.右;中序遍历顺序为左.根.右;后序遍历顺序为左 ...

  5. 二叉树路径应用举例(基于非递归后序遍历)

    #include "stdafx.h" #include <iostream> #include <fstream>using namespace std; ...

  6. 转载:二叉树的前中后和层序遍历详细图解(递归和非递归写法)

    二叉树的前中后和层序遍历详细图解(递归和非递归写法) Monster_ii 2018-08-27 17:01:53 50530 收藏 403 分类专栏: 数据结构拾遗 文章标签: 二叉树 前序 中序 ...

  7. 九十五、二叉树的递归和非递归的遍历算法模板

    @Author:Runsen 刷Leetcode,需要知道一定的算法模板,本次先总结下二叉树的递归和非递归的遍历算法模板. 二叉树的四种遍历方式,前中后加上层序遍历.对于二叉树的前中后层序遍历,每种遍 ...

  8. 二叉树层序遍历递归与非递归_二叉树基础(1)-构建和遍历(递归和非递归)...

    二叉树的构建有2种方式:1.直接输入数字.2.根据两种顺序来判断另外一中顺序(后面会提到) 这里分享第一种构建方式,二叉树的前中后三种遍历方式(递归和非递归版本),和二叉树的层次遍历. 见代码demo ...

  9. 二叉树的遍历:先序 中序 后序遍历的递归与非递归实现及层序遍历

    二叉树的定义:一种基本的数据结构,是一种每个节点的儿子数目都不多于2的树 树节点的定义如下: // 树(节点)定义 struct TreeNode {int data; // 值TreeNode* l ...

  10. 左神算法:分别用递归和非递归方式实现二叉树先序、中序和后序遍历(Java版)

    本题来自左神<程序员代码面试指南>"分别用递归和非递归方式实现二叉树先序.中序和后序遍历"题目. 题目 用递归和非递归方式,分别按照二叉树先序.中序和后序打印所有的节点 ...

最新文章

  1. 超酷的超级DataGrid
  2. 算法族的集中管理——策略模式
  3. java基础---System类
  4. linux contos7防火墙加端口,Linux:centos7防火墙开放端口
  5. 北大毕业 15 年经验架构师,重磅解读 5G 时代的计算平台
  6. python使用pycurl抓取获取12306验证码
  7. android微信群视频,10. 搞定微信群聊的神器——录屏软件集合
  8. 离散数学——自动生成真值表、主合取范式
  9. java架构师之路-并发编程
  10. python分秒换算_如何将度分秒转换为度分
  11. 新一代天气雷达信息共享平台
  12. 软件测试之黑盒、白盒的测试方法?
  13. opencv remap matlab,C++ Opencv remap()重映射函数详解及使用示例
  14. 如何理解界面陷阱电荷呢(interface trapped charge)和费米钉扎效应?
  15. 实战PyQt5: 086-图元类QGraphicsItem
  16. 做好自己安全第一责任人 嘀嗒全面上线安全带智能语音提醒
  17. 指南Java面试常问问题及答案
  18. 隐秘而伟大,探访鹏博士大数据双十一背后那些真英雄
  19. 手机刷机后数据还在吗?手机刷机并保留数据的方法
  20. 大一计算机VB自学教程,2017秋VB6.0程序设计(兰州理工大学)

热门文章

  1. 通用无线公共接口cpri学习笔记_11/24
  2. 生成田字格模板(word)
  3. JanusGraph对于Gremlin查询语言的介绍
  4. 付款方对接银联入网仿真测试系统
  5. 神通数据库导出sql脚本
  6. lol走砍e源码_【精选】某LOL走砍E盾+VMP卡登陆解决办法
  7. fgo升级经验计算机,命运冠位指定FGO升级所需经验值介绍
  8. 动图体积太大怎么缩小?教你一招快速压缩gif
  9. 韩立刚老师《计算机网络》笔记2
  10. pdf.js 跨域 php,JavaScript_js跨域资源共享 基础篇,本文详细介绍了javascript跨域资 - phpStudy...