目录

二叉树

二叉树的递归遍历

先序递归遍历

中序递归遍历

后序递归遍历

二叉树的非递归遍历

先序遍历使用栈结构

中序遍历使用栈结构

后序遍历使用栈结构

中序遍历:(morris遍历)空间复杂度O(1),非递归,不用栈

先序遍历:空间复杂度O(1),非递归,不用栈

后序遍历:空间复杂度O(1),非递归,不用栈

层序遍历

层序遍历:使用队列辅助


二叉树

相信各位小伙伴能进来,就已经知道二叉树是什么,这里就不一一描述了.

下面给出二叉树结点:

class Node {
public:int data;Node*left;Node*right;
};

二叉树的递归遍历

在递归遍历一颗二叉树的任意非空节点时,我们会先后调用函数进入左子树和右子树,在这个过程中,我们有三次处理数据的机会,即进入左子树之前一次,出左子树后/进右子树之前一次,出右子树之后一次,根据处理时机的不同,有:先序遍历(先处理根结点数据->根左右),中序遍历(在左右子树中间处理根结点->左根右),后序遍历(最后处理根结点->左右根);

如图先序遍历:124536

中序遍历:425136

后序遍历:452631

有了以上解释,下面三种简单的递归遍历就不需要多说了

先序递归遍历

void PreOrderTreeWalk(Node* root) {if (root == nullptr)return;cout << root->data << " ";PreOrderTreeWalk(root->left);PreOrderTreeWalk(root->right);
}

中序递归遍历

void InOrderTreeWalk(Node* root) {if (root == nullptr)return;InOrderTreeWalk(root->left);cout << root->data << " ";InOrderTreeWalk(root->right);
}

后序递归遍历

void PostOrderTreeWalk(Node* root) {if (root == nullptr)return;PostOrderTreeWalk(root->left);cout << root->data << " ";PostOrderTreeWalk(root->right);
}

二叉树的非递归遍历

先序遍历使用栈结构(不知道栈的评论区留言,下次可以出栈)

由于栈是先进后出结构,先序遍历是根左右形式,我们可以考虑先处理根节点,再将右树根压入栈中待处理(下面代码push的根节点,但pop后会让它指向右子树,可以避免push空节点,也可以在push前判断右子树是否为空),去处理左树根,以根节点root为例:先push右根,处理左根,左根有右树时会继续push,当处理到无左节点时开始pop,作为新根开始处理;上面过程模拟了递归调用,我个人通俗的理解是,处理根,存右树,走左树

void PreOrderTreeWalkWithStack(Node* root) {//建栈stack<Node*>st;Node*p = root;while (p != nullptr || !st.empty()) {//当p空时栈不空,栈空时p不空if (p != nullptr) {cout << p->data << " ";st.push(p);p = p->left;}else {p = st.top();st.pop();p = p->right;}}
}

中序遍历使用栈结构

这个...中序遍历和先序遍历一样,我就改了两行代码,因为中序遍历是左根右,所以直接压根结点,处理左子树,处理完后pop,接着处理根结点,然后根结点右指,处理右子树;

void InOrderTreeWalkWithStack(Node* root) {//建栈stack<Node*>st;Node*p = root;while (p != nullptr || !st.empty()) {//当p空时栈不空,栈空时p不空if (p != nullptr) {st.push(p);p = p->left;}else {p = st.top();st.pop();cout << p->data << " ";p = p->right;}}
}

后序遍历使用栈结构

后序遍历非递归栈有一点难,我们找一种简单的方法吧,前面我们提到过,一个节点可以有三次处理的机会,后序遍历就是在第三次时处理的,我们只需要找一种简单的方法判断是第几次遇见该结点,就可以实现后序遍历

/*
改变结点结构,这个有点难
每次visited++,判断visited%3+1
class Node {
public:int data;Node*left;Node*right;int visited = 0;
};
*/
/*
哈希表这么好用,不会有人不会吧
map<Node*, int>m;
*///建新结点,复制原树
class pNode {
public:int data;pNode*left;pNode*right;int visited = 0;
};
//利用了递归先序遍历
pNode* CopyTree(Node* root) {if (root == nullptr)return nullptr;pNode*p = new pNode;p->data = root->data;p->left = CopyTree(root->left);p->right = CopyTree(root->right);return p;
}void PostOrderTreeWalkWithStack(pNode*root) {stack<pNode*>st;pNode*p = root;while (p != nullptr || !st.empty()) {//当p空时栈不空,栈空时p不空if (p != nullptr) {//第一次p->visited++;st.push(p);p = p->left;}else {p = st.top();st.pop();//这里是第二次if (p->visited % 3 + 1 == 2) {p->visited++;st.push(p);p = p->right;}//第三次else {//visited++三次,不妨碍下次使用p->visited++;cout << p->data << " ";//该结点处理时子树已经处理完了,需要置空p = nullptr;}}}
}

上述代码时间复杂度O(n),空间复杂度看起来也不低

下面开始介绍不用栈结构和递归实现的二叉树遍历

中序遍历:(morris遍历)空间复杂度O(1),非递归,不用栈

空间复杂度O(1)难点在于,遍历子结点时如何返回父节点,由于二叉树有许多叶结点,他们的左右指针都是空,我们可以利用这些指针返回父节点;

步骤如下:

1.如果当前节点的左孩子为空,则输出当前节点并将其右孩子作为当前节点。

2.如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点。

a) 如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。当前节点更新为当前节点的左孩子。

b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空(恢复树的形状)。输出当前节点。当前节点更新为当前节点的右孩子。

3. 重复以上1、2直到当前节点为空。

void InOrderTreeWalkWithoutStack(Node* root) {Node*cur = root;Node*pre = nullptr;while (cur!=nullptr) {if (cur->left == nullptr) {//cur没有左子树,只会停留一次cout << cur->data << " ";cur = cur->right;}else {pre = cur->left;//找到cur左树最右节点while (pre->right != nullptr&&pre->right != cur) {pre = pre->right;}//第一次停留,储存父结点,cur处理左边if (pre->right == nullptr) {pre->right = cur;cur = cur->left;}//第二次停留,恢复叶结点,左边处理完成,cur处理右边else {pre->right = nullptr;cout << cur->data << " ";cur = cur->right;}}}
}

先序遍历:空间复杂度O(1),非递归,不用栈

先序和中序只有一行不同,先序是第一次到达时处理,中序是第二次

void PreOrderTreeWalkWithoutStack(Node* root) {Node*cur = root;Node*pre = nullptr;while (cur != nullptr) {if (cur->left == nullptr) {//cur没有左子树,只会停留一次cout << cur->data << " ";cur = cur->right;}else {pre = cur->left;//找到左树最右节点while (pre->right != nullptr&&pre->right != cur) {pre = pre->right;}//第一次停留,储存父结点,处理左边if (pre->right == nullptr) {pre->right = cur;cout << cur->data << " ";cur = cur->left;}//第二次停留,恢复叶结点,左边处理完成,处理右边else {pre->right = nullptr;cur = cur->right;}}}
}

后序遍历:空间复杂度O(1),非递归,不用栈

如图,对于后序遍历,可以看成--逆序打印每个结点左树右边界,最后逆序打印大树右边界

如果用递归

void PrintReverse(Node*root) {if (root == nullptr) return;PrintReverse(root->right);cout << root->data << " ";
}

但我们说了不用递归,栈.所以我们将它看成链表,先反转,然后处理,处理后再反转


void Reverse(Node* from, Node* to) {if (from == to)return;Node*pre = from->right;Node*cur = from;Node*ptr = nullptr;while (cur != to) {ptr = pre->right;pre->right = cur;cur = pre;pre = ptr;}
}
void PrintReverse1(Node* from, Node* to) {Reverse(from, to);Node*p = to;while (p != from) {cout << p->data << " ";p = p->right;}     cout << p->data << " ";Reverse(to, from);
}
//使用了dump,这样就不用单独打印大树右边界了
void PostOrderTreeWalkWithoutStack(Node* root) {Node dump;dump.right = nullptr;dump.data = 0;dump.left = root;Node*cur = &dump;Node*pre = nullptr;while (cur != nullptr) {if (cur->left == nullptr) {cur = cur->right;}else {pre = cur->left;//找到左树最右节点while (pre->right != nullptr&&pre->right != cur) {pre = pre->right;}//第一次停留,储存父结点,处理左边if (pre->right == nullptr) {pre->right = cur;cur = cur->left;}//第二次停留,恢复叶结点,左边处理完成,处理右边else {PrintReverse1(cur->left, pre);pre->right = nullptr;cur = cur->right;}}}
}

层序遍历

上图层序遍历结果为:1 2 3 4 5 6

我们可以知道这是从上到下从左到右输出的

有一种比较好懂的层序遍历,先用深度搜索找最大深度d,然后循环1-d,递归先序遍历记录当前层,相等就输出

void PreOrderWalk(Node*root, int i, int j) {if (root == nullptr)return;if (i == j) {cout << root->data << " ";return;}PreOrderWalk(root->left, i, j + 1);PreOrderWalk(root->right, i, j + 1);
}
int DeepSearch(Node*root, int i) {if(root==nullptr)return i;return max(DeepSearch(root->left, i + 1), DeepSearch(root->right, i + 1));
}
void LevelOrderTreeWalk(Node*root) {int d = DeepSearch(root, 0);for (int i = 0; i < d; i++) {PreOrderWalk(root, i, 0);}
}

使用先序遍历保证了每层之间元素的相对次序,这种方法时间复杂度较高

层序遍历:使用队列辅助(不知道队列的也可以留言哈)

队列是先入先出的结构,这个是真简单,执行以下步骤即可

首先,根结点 1 入队;
根结点 1 出队,出队的同时,将左孩子 2 和右孩子 3 分别入队;
队头结点 2 出队,出队的同时,将结点 2 的左孩子 4 和右孩子 5 依次入队;
队头结点 3 出队,出队的同时,将结点 3 的左孩子 6 和右孩子 7 依次入队;
不断地循环,直至队列内为空。

从左到右使子结点入队,可以保证下一层的相对次序,可以看成从第一层开始相对次序就是正确的

void LevelOrderTreeWalkWithQueue(Node*root) {queue<Node*>qu;qu.push(root);Node*p = nullptr;while (!qu.empty()) {p = qu.front();qu.pop();cout << p->data << " ";if (p->left != nullptr) {qu.push(p->left);}if (p->right != nullptr) {qu.push(p->right);}}
}

如果你想知道哪些数据是哪一层的,可以从第一层开始,记录当前队尾元素,每次处理到该元素时,说明到达该层结尾了,处理结束后刷新记录的队尾,重复上述操作即可.

如果有问题可以评论区,大家一起讨论

二叉树:先序遍历,中序遍历,后序遍历,层序/层次遍历相关推荐

  1. 二叉树前序遍历python输出_[宜配屋]听图阁 - Python实现输入二叉树的先序和中序遍历,再输出后序遍历操作示例...

    本文实例讲述了Python实现输入二叉树的先序和中序遍历,再输出后序遍历操作.分享给大家供大家参考,具体如下: 实现一个功能: 输入:一颗二叉树的先序和中序遍历 输出:后续遍历 思想: 先序遍历中,第 ...

  2. 为什么普通树没有中序遍历和森林没有后序遍历

    本文为个人理解,若有错误欢迎指正! 这里说的树当然是指普通的树,而不是树中比较特别的二叉树. 1.为什么普通树没有中序遍历 中序遍历是对于二叉树而言: 中序遍历左子树,访问根结点,中序遍历右结点. 在 ...

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

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

  4. 给定二叉树先序、中序遍历序列,求后序遍历

    给定一个二叉树的前序遍历和中序遍历的序列,输出对应这个二叉树的后续遍历序列. 输入描述: 输入为一行. 两个字符串,分别表示二叉树的前序遍历和中序遍历结果,用空格分隔.保证数据合法 输出描述: 对应输 ...

  5. 二叉树前序中序后序_leetcode889_go_根据前序和后序遍历构造二叉树

    leetcode889_根据前序和后序遍历构造二叉树 01 - 题目 返回与给定的前序和后序遍历匹配的任何二叉树. pre 和 post 遍历中的值是不同的正整数. 示例:输入:pre = [1,2, ...

  6. 已知二叉树先序和中序遍历结果,求后序遍历结果

    以下面的例题为例进行讲解:已知一棵二叉树的先序遍历序列和中序遍历序列分别是ABDCEF.BDAECF,求二叉树及后序遍历序列. 分析:先序遍历序列的第一个字符为根结点.对于中序遍历,根结点在中序遍历序 ...

  7. leetcode 236. 二叉树的最近公共祖先LCA(后序遍历,回溯)

    LCA(Least Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先. 题目描述 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先.百度百科 ...

  8. 二叉树前序中序后续线索树_后序线索二叉树怎么画 线索二叉树基本操作详解 - 办公软件 - 服务器之家...

    后序线索二叉树怎么画 线索二叉树基本操作详解 发布时间:2017-05-23 来源:服务器之家 遍历二叉树是以一定规则将二叉树中结点排列成一个线性序列,得到二叉树中结点的先序,中序或后序序列.这实际上 ...

  9. 树形结构:使用栈实现,快排,先序遍历,归并排序,后序遍历

    主要还是学习使用栈模拟实现递归: 总结一下,首先通过栈实现递归是有规律可行的,这里面涉及整体到部分,一切都是对象的思想,把一个整体看成一个对象,这个对象是数据和操作的集合,定义这些操作,把操作顺序按照 ...

最新文章

  1. [云炬创业基础笔记]第二章创业者测试9
  2. php bindresult,mysqli_stmt::bind_result
  3. EFLS开源 | 阿里妈妈联邦学习解决方案详解
  4. 世界粮农组织五大健康食品_粮农组织的完整形式是什么?
  5. mysql 同一字段分别统计,mysql查询同一个字段根据不同值分组成不同列统计
  6. do…while 第二讲
  7. OpenPilot 0.3.2 发布,开源自动驾驶技术
  8. ERROR:ModuleNotFoundError: No module named ‘cv2‘
  9. Gitee 管理UE4项目
  10. 黑客入门教程(非常详细)从零基础入门到精通,看完这一篇就够了。
  11. win10系统进不了服务器失败,快速解决Win10安装失败重启进不了系统的方法
  12. Exception: ROM is missing for xxxx, see https://github.com/openai/atari-py 强化学习安装Atari环境时ROMS丢失解决办法
  13. 动态规划-背包问题、兑换零钱问题、旅行商问题
  14. 财税SaaS起风,税友股份成“中国版Intuit”?
  15. 软件项目管理——项目三角形
  16. 网站调用在线二维码生成 api
  17. activiti 入门——activiti API (二)
  18. 暑假阅读的正确打开方式原来这么简单!
  19. 正在连接172.16.1.54:9703...无法打开到主机的连接。 在端口 23: 连接失败
  20. eclipse svn插件 如何查看所有历史记录及如何设置默认历史记录数

热门文章

  1. 工商管理专业知识与实务(中级)【1】
  2. python小项目超级大脑抱香_“超级大脑”来了!丰泽区建成全市首个区县级大数据中心...
  3. 特征工程-幅度调整-无量纲化(二):归一化和标准化案例,看不懂你打我
  4. js 获取本周 周一-周日的日期,周一周日问题特殊处理
  5. 我的世界java版特别卡怎么办_我的世界卡顿延迟怎么办
  6. 基于Java的设计管理系统
  7. 解决C++ MFC源码运行时 由于找不到MFC42D.DLL,无法继续执行代码
  8. (自适应手机端)极致CMS居家用品纸盘纸盒纸杯卫生纸巾生产网站模板
  9. PMML(一):初探
  10. 树和二叉树的基本概念和相关计算