二叉树的遍历详解

article/2025/9/22 5:08:17

概述

二叉树的遍历是一个很常见的问题。二叉树的遍历方式主要有:先序遍历、中序遍历、后序遍历、层次遍历。先序、中序、后序其实指的是访问父节点的次序。在遍历过程中,若访问顺序是父节点-左孩子节点-右孩子节点,就是先序遍历,若访问顺序是左孩子节点-父节点-右孩子节点,就是中序遍历,若访问顺序是左孩子节点-右孩子节点-父节点,就是后序遍历。不论是先序遍历、中序遍历还是后序遍历,访问左右孩子节点的相对次序是不变的,总是先访问左孩子节点,再访问右孩子节点。而层次遍历,就是按照从上到下、从左到右的顺序访问二叉树的每个节点。

在这里插入图片描述

在这里插入图片描述

在介绍遍历算法之前,先定义一个二叉树的结构体。使用的是 C++ 语言。

//filename: BinTreeNode.h
template <typename T> struct BinTreeNode {T data; //数据域BinTreeNode * LeftChild; //左孩子节点指针BinTreeNode * RightChild; //右孩子节点指针BinTreeNode * parent; //父节点指针
};

先序遍历

递归

使用递归,很容易写出一个遍历算法。代码如下:

//filename: BinTreeNode.h
template <typename T>
void travPre_R(BinTreeNode<T> * root) {//二叉树先序遍历算法(递归版)if (!root) return;cout << root->data;travPre_R(root->LeftChild);travPre_R(root->RightChild);
}

迭代

在之前的文章中,我不止一次地说过,递归是很耗费计算机资源的,所以我们在写程序的时候要尽量避免使用递归。幸运的是,绝大部分递归的代码都有相应的迭代版本。那么我们就试着将上述递归代码改写成迭代的版本。改写之后,代码如下:

//filename: BinTreeNode.h
template <typename T>
void travPre_I1(BinTreeNode<T> * root) {//二叉树先序遍历算法(迭代版#1)Stack<BinTreeNode<T>*> s; //辅助栈if (root) //如果根节点不为空s.push(root); //则令根节点入栈while (!s.empty()) { //在栈变空之前反复循环root = s.pop(); cout << root->data; //弹出并访问当前节点//下面左右孩子的顺序不能颠倒,必须先让右孩子先入栈,再让左孩子入栈。if (root->RightChild)s.push(root->RightChild); //右孩子先入后出if (root->LeftChild)s.push(root->LeftChild); //左孩子后入先出}
}

下面我们通过一个实例来了解一下该迭代版本是如何工作的。

PS:黑色的元素表示已经被弹出并访问过。

结合代码,该二叉树的先序遍历过程如下:

  1. 初始化一个空栈。
  2. 根节点入栈,此时将 a 入栈。
  3. 循环开始,弹出并访问栈顶元素,此时栈顶元素是 a。
  4. 如果 a 有右孩子,则将其右孩子节点入栈;如果 a 有左孩子,则将其左孩子节点入栈。此时栈中有 b、c 两个元素。
  5. 这时进入下一轮循环。弹出并访问栈顶元素,此时栈顶元素是 b。经检查,b 没有右孩子,也没有左孩子,进入下一轮循环。
  6. 弹出并访问栈顶元素,此时栈顶元素是 c。c 的右孩子是 f,左孩子是 d,故分别将 d、f 入栈。进入下一轮循环。
  7. 此时栈中的元素是 d、f。
  8. 弹出并访问栈顶元素,此时栈顶元素是 d。d 的右孩子是 e,d 没有左孩子,故将 e 入栈。进入下一轮循环。
  9. 此时栈中的元素是 e、f。
  10. 弹出并访问栈顶元素,此时栈顶元素是 e。e 没有左右孩子,进入下一轮循环。
  11. 弹出并访问栈顶元素,此时栈顶元素是 f。f 没有左右孩子,进入下一轮循环。
  12. 此时栈为空,退出循环。遍历结束。

这个迭代的遍历算法非常简明,但是很遗憾,这种算法并不容易推广到我们接下来要研究的中序遍历和后序遍历。因此我问需要寻找另一种策略。

第 2 种迭代方式

我们来看一个规模更大、更具一般性的二叉树:

在这里插入图片描述

这个二叉树的先序遍历序列是:idcabhfeglkjnmpo,也就是遵循了下图所示的顺序:

在这里插入图片描述

再进一步,我们把二叉树抽象成下面这个样子,

在这里插入图片描述

L 0 L_0 L0 ~ L d L_d Ld 是二叉树的左侧链上的节点, R 0 R_0 R0 ~ R d R_d Rd 分别是 L 0 L_0 L0 ~ L d L_d Ld 的右孩子, T 0 T_0 T0 ~ T d T_d Td 分别是 L 0 L_0 L0 ~ L d L_d Ld 的右子树。不难发现,二叉树的先序遍历就是先自上而下访问左侧链上的节点,再自下而上访问左侧链上的节点的右子树。而我们的遍历算法,就是根据这样一个思路来进行设计。

首先需要实现一个子方法,就是访问二叉树左侧链上的节点:

//从当前节点出发,沿左分支不断深入,直至没有左分支的节点;沿途节点遇到后立即访问
template <typename T> //元素类型、操作器
static void visitAlongLeftBranch ( BinTreeNode<T>* x, Stack<BinTreeNode<T>*>& S ) {while ( x ) {cout << x->data; //访问当前节点if( x->RightChild )S.push ( x->RightChild ); //右孩子入栈暂存(可优化:通过判断,避免空的右孩子入栈)x = x->LeftChild;  //沿左分支深入一层}
}

然后是主方法,在主方法中,通过迭代,不断地调用上面这个子方法,从而实现完整的二叉树先序遍历。

template <typename T> //元素类型、操作器
void travPre_I2 ( BinTreeNode<T>* root) { //二叉树先序遍历算法(迭代版#2)Stack<BinTreeNode<T>*> S; //辅助栈while ( true ) {visitAlongLeftBranch ( root, S ); //从当前节点出发,逐批访问if ( S.empty() ) break; //直到栈空root = S.pop(); //弹出下一批的起点}
}

中序遍历

递归

与先序遍历类似,递归版的中序遍历算法很容易实现,代码如下:

template <typename T>
void travIn_R(BinTreeNode<T> * root) {//二叉树先序遍历算法(递归版)if (!root)return;travPre_R(root->LeftChild);cout << root->data;travPre_R(root->RightChild);
}

递归代码不仅容易实现,也很好理解,这里不再做过多解释。

迭代

参照迭代式先序遍历版本 2 的思路,在宏观上,我们可以将中序遍历的顺序抽象为,先访问二叉树的左侧链上的最底部的节点,然后访问该节点的右子树(如果有的话),然后访问该节点的父节点,然后访问该节点的父节点的右子树(如果有的话)……直至全部节点被访问完毕。如下图所示:

在这里插入图片描述

按照以上思路,可以实现迭代版中序遍历算法如下:

template <typename T> //从当前节点出发,沿左分支不断深入,直至没有左分支的节点
static void goAlongLeftBranch ( BinTreeNode<T> * x, Stack<BinTreeNode<T> * >& S ) {while (x) { S.push(x); x = x->LeftChild; } //当前节点入栈后随即向左侧分支深入,迭代直到无左孩子
}template <typename T> //元素类型、操作器
void travIn_I(BinTreeNode<T> * root) {//二叉树先序遍历算法(迭代版)Stack<BinTreeNode<T> *> S; //辅助栈while ( true ) {goAlongLeftBranch ( root, S ); //从当前节点出发,逐批入栈if ( S.empty() ) break; //直至所有节点处理完毕root = S.pop(); cout << root->data; //弹出栈顶节点并访问之root = root->RightChild; //转向右子树}
}

也可以对代码稍加改进,将这两个方法写成一个方法:

template <typename T> //元素类型
void travIn_I2 ( BinTreeNode<T>* root ) { //二叉树中序遍历算法(迭代版#2)Stack<BinTreeNode<T>*> S; //辅助栈while ( true )if ( root ) {S.push ( root ); //根节点进栈root = root->LeftChild; //深入遍历左子树} else if ( !S.empty() ) {root = S.pop(); //尚未访问的最低祖先节点退栈cout << root->data; //访问该祖先节点root = root->RightChild; //遍历祖先的右子树} elsebreak; //遍历完成
}

后序遍历

递归

与前两个一样,二叉树的后序遍历算法可以很容易地用递归的方式实现。

template <typename T>
void travPost_R(BinTreeNode<T> * root) {//二叉树先序遍历算法(递归版)if (!root)return;travPost_R(root->LeftChild);travPost_R(root->RightChild);cout << root->data;
}

迭代

但是要想用迭代的方式实现后序遍历算法,则有一定的难度,因为左、右子树的递归遍历均严格地不属于尾递归。不过,仍可继续套用此前的思路和技巧,考虑一下,后序遍历中,首先访问的是哪个节点?答案就是二叉树的最高最左侧的叶子节点。
在这里插入图片描述
由于最高最左侧的叶子节点 V 可能是左孩子节点,也可能是右孩子节点,所以 V 与其父节点之间的联接用竖直的线表示。考查联接于 V 与树根之间的唯一通路(以粗线示意)。与先序与中序遍历类似地,自底而上地沿着该通路,整个后序遍历序列也可以分解为若干个片段。每一片段,分别起始于通路上的一个节点,并包括三步:访问当前节点,遍历以其右兄弟(若存在)为根的子树,以及向上回溯至其父亲节点(若存在)并转入下一片段。

基于以上理解,即可写出迭代式后序遍历算法。

template <typename T> //在以S栈顶节点为根的子树中,找到最高左侧叶节点
static void gotoHLVFL ( Stack<BinTreeNode<T>*>& S ) { //沿途所遇节点依次入栈while ( BinTreeNode<T>* x = S.top() ) //自顶而下,反复检查当前节点(即栈顶)if ( x->LeftChild ) { //尽可能向左if ( x->RightChild ) S.push ( x->RightChild ); //若有右孩子,优先入栈S.push ( x->LeftChild ); //然后才转至左孩子} else //实不得已S.push ( x->RightChild ); //才向右S.pop(); //返回之前,弹出栈顶的空节点
}template <typename T>
void travPost_I ( BinTreeNode<T>* root ) { //二叉树的后序遍历(迭代版)Stack<BinTreeNode<T>*> S; //辅助栈if ( root ) S.push ( root ); //根节点入栈while ( !S.empty() ) {if ( S.top() != root->parent ) //若栈顶非当前节点之父(则必为其右兄),此时需gotoHLVFL ( S ); //在以其右兄为根之子树中,找到HLVFL(相当于递归深入其中)root = S.pop(); cout << root->data; //弹出栈顶(即前一节点之后继),并访问之}
}

层次遍历

在文章开头我们已经对层次遍历做了介绍,层次遍历严格按照自上而下、自左向右的顺序访问树的节点。所以我们需要用队列作为辅助,具体代码如下:

template <typename T> //元素类型
void travLevel ( BinTreeNode<T>* root ) { //二叉树层次遍历算法Queue<BinTreeNode<T>*> Q; //辅助队列Q.enqueue ( root ); //根节点入队while ( !Q.empty() ) { //在队列再次变空之前,反复迭代BinTreeNode<T>* x = Q.dequeue(); cout << x->data; //取出队首节点并访问之if ( x->LeftChild ) Q.enqueue ( x->LeftChild ); //左孩子入队if ( x->RightChild ) Q.enqueue ( x->RightChild ); //右孩子入队}
}

好了,以上就是二叉树的几种常见的遍历方式,各位小伙伴看完如果有什么疑问或建议欢迎与我交流。

最后,欢迎大家关注我的微信公众号:AProgrammer


http://chatgpt.dhexx.cn/article/fOYJf7uv.shtml

相关文章

“二叉树遍历“详解 以及 二叉树的实现

目录 一.二叉树的遍历 1.二叉树的遍历的解释&#xff1a; 2.二叉树的遍历有三种递归结构 (1) 实现先序遍历&#xff1a; (2) 实现中序遍历&#xff1a; (3) 实现后序遍历&#xff1a; (4) 二叉树的层序遍历 层序遍历代码&#xff1a; 二.二叉树的递归实现相关函数讲解…

二叉树遍历详解

二叉树的遍历方式是最基本&#xff0c;也是最重要的一类题目&#xff0c;我们将从「前序」、「中序」、「后序」、「层序」四种遍历方式出发&#xff0c;总结他们的递归和迭代解法。 一、二叉树定义 二叉树&#xff08;Binary tree&#xff09;是树形结构的一个重要类型…

讲透学烂二叉树(三):二叉树的遍历图解算法步骤及JS代码

二叉树的遍历是指不重复地访问二叉树中所有结点&#xff0c;主要指非空二叉树&#xff0c;对于空二叉树则结束返回。 二叉树的遍历分为 深度优先遍历 先序遍历&#xff1a;根节点->左子树->右子树&#xff08;根左右&#xff09;&#xff0c;有的叫&#xff1a;前序遍历…

二叉树的中序遍历算法(Java三种实现方法)

文章目录 题目一、二叉树的节点定义二、三种遍历方法1.递归算法思想 2.迭代算法思想 3.Morris 中序遍历算法思想 总结 题目 给定一个二叉树的根节点 root &#xff0c;返回它的 中序 遍历 一、二叉树的节点定义 public class TreeNode {int val;TreeNode left;TreeNode righ…

二叉树遍历的几种常见方法

二叉树的遍历方法 一.二叉树分类&#xff1a; 完全二叉树满二叉树扩充二叉树平衡二叉树 二.二叉树的四种遍历方式&#xff1a; 前序遍历&#xff08;先根&#xff0c;再左&#xff0c;最后右&#xff09;中序遍历&#xff08;先左&#xff0c;再根&#xff0c;最后右&#…

二叉树的三种遍历方式

目录 1.二叉树的结构&#xff1a; 2.二叉树的前序遍历&#xff1a; 3.二叉树的中序遍历&#xff1a; 4.二叉树的后序遍历&#xff1a; 5.二叉树前、中、后序的代码实现&#xff1a; 前序遍历函数&#xff1a; 中序遍历函数&#xff1a; 后序遍历&#xff1a; 完整代码&am…

图解二叉树的三种遍历

1、二叉树的遍历 前序遍历&#xff1a;根结点 —> 左子树 —> 右子树 中序遍历&#xff1a;左子树—> 根结点 —> 右子树 后序遍历&#xff1a;左子树 —> 右子树 —> 根结点 层次遍历&#xff1a;仅仅需按层次遍历就可以 前序遍历&#xff1a;1 2 4 5 7…

二叉树的遍历【 详细讲解 】

二叉树的遍历 一共有4种遍历 先看图&#xff0c;对于这个图进行4种遍历的讲解 1、 先序遍历 定义&#xff1a;若二叉树为空&#xff0c;则空操作&#xff1b;否则 &#xff08;1&#xff09;访问根节点&#xff08;2&#xff09;先序遍历左子树&#xff08;3&#…

二叉树的创建及遍历方法

目录 1、二叉树的定义及特点 2、满二叉树和完全二叉树的概念 3、二叉树的存储结构 4、创建二叉树 5、树的四种遍历方法 6、结果及其分析 1、二叉树的定义及特点 二叉树是指树中节点的度不大于2的有序树&#xff0c;它是一种最简单且最重要的树。二叉树的递归定义为&#…

二叉树遍历(图解)

二叉树的顺序存储结构就是用一维数组存储二叉树中的节点&#xff0c;并且节点的存储位置&#xff0c;也就是数组的下标要能体现节点之间的逻辑关系。—–>一般只用于完全二叉树 链式存储—–>二叉链表 定义&#xff1a; lchild | data | rchild&#xff08;两个指针域&…

图解二叉树及二叉树遍历

二叉树及二叉树遍历 完全二叉树二叉树的遍历遍历的性质 1、完全二叉树 对于一棵具有n个节点的二叉树&#xff08;按层序编号&#xff09;&#xff0c;如果编号为i的节点与同样深度的满二叉树中编号为i的节点在二叉树的位置完全相同&#xff0c;则为完全二叉树。 换句话来说&a…

数据结构--二叉树遍历(详细过程)

目录 一、什么是二叉树&#xff1f; 二、二叉树的遍历 1. 先序遍历 2. 中序遍历 3.后序遍历 4. 遍历的推导 三、重要的事情 一、什么是二叉树&#xff1f; 1. 二叉树&#xff1a;一种树形结构&#xff0c;特点是每个结点至多只有两颗子树&#xff0c;并且子树有左右之分…

图解法:三分钟掌握二叉树的三种遍历

二叉树作为树中的一种特殊存在机制&#xff0c;人们对于它的排序总结出来了三种方式&#xff0c;让我们一起探寻它的魅力吧&#xff01; 测试对象 1.先序遍历 首先看一下排序规则 先访问根节点 再先序访问左子树 再先序访问右子树 看上面的素材&#xff0c;得知根节点为…

二叉树的各种遍历算法以及实例

一、二叉树 在计算机科学中&#xff0c;树是一种重要的非线性数据结构&#xff0c;直观地看&#xff0c;它是数据元素&#xff08;在树中称为结点&#xff09;按分支关系组织起来的结构。二叉树是每个节点最多有两个子树的有序树。通常子树被称作“左子树”&#xff08;left s…

二叉树的四种遍历方法(前序遍历、中序遍历、后序遍历、层序遍历)有图有真相!!!

文章目录 二叉树的四种遍历方式前序遍历&#xff08;Preorder Traversal&#xff09;中序遍历&#xff08;Inorder Traversal&#xff09;后序遍历&#xff08;Postorder Traversal&#xff09;层序遍历&#xff08;Level Order Traversal&#xff09; 二叉树的四种遍历方式 相…

二叉树的层次遍历算法

前面学的二叉树的遍历是把二叉树看作3个部分&#xff1a;根&#xff0c;左子树&#xff0c;右子树&#xff0c;然后我们以此来访问3个部分 而层次遍历是把树看成从上到下的若干层&#xff1a;根结点在第一层&#xff0c;根结点的孩子在第二层&#xff0c;根结点的孩子的孩子在第…

二叉树各种遍历算法

二叉树是许多算法题的常用结构&#xff0c;其遍历算法的各种变种就是各种题目。具体的顺序如下&#xff1a; 先序遍历&#xff1a;先根、后左、再右中序遍历&#xff1a;先左、后根、再右后序遍历&#xff1a;先左、后右、再根 递归版先序、中序、后序遍历 最简单、最直接的…

带你图解二叉树的多种递归遍历(建议收藏!!)

文章目录 二叉树的构建结点类型的定义构建二叉树之间的关系 深度优先遍历前序遍历代码实现图解递归&#xff08;由于图片大小问题&#xff0c;建议用手机客户端查看&#xff0c;以下图解也是&#xff09; 中序遍历代码实现图解递归 后序遍历代码实现图解递归 广度优先遍历层序遍…

二叉树遍历之图解Mirror算法(莫里斯算法)

144. 二叉树的前序遍历 我们写二叉树的遍历时&#xff0c;一般有两种方式&#xff0c;迭代和递归。然而还有一种神奇的算法&#xff0c;也可以作我们的二叉树递归&#xff0c;且空间复杂度为O(1)&#xff0c;要知道&#xff0c;我们迭代和递归都是需要额外栈空间的 递归和迭代网…

二叉树遍历方法——前、中、后序遍历(图解)

目录 一、前序遍历 &#xff08;1&#xff09;递归版本 &#xff08;2&#xff09;非递归版本 二、中序遍历 &#xff08;1&#xff09;递归版本 &#xff08;2&#xff09;非递归版本 三、后序遍历 &#xff08;1&#xff09;递归版本 &#xff08;2&#xff09;非递归版…