全文概要
所谓遍历二叉树,就是遵从某种次序,顺着某一条搜索路径访问二叉树中的各个结点,使得每个结点均被访问一次,而且仅被访问一次。本文详细介绍了二叉树的前序(又称先序)、中序和后序遍历的规则及其算法实现。本文全部代码示例可从此处获得。
遍历的定义
“遍历”,即访问到二叉树中的所有结点,且每个结点仅被访问一次。“访问”的含义可以很广,如:输出结点的信息、修改结点的数据之等,但一般要求这种访问不破坏原来数据之间的逻辑结构。
本文中”访问“规定为输出当前遍历结点元素值,定义打印函数如下:1
2
3
4
5template <class ElemType>
void Print(ElemType e)
{
cout << e;
}
实际上,“遍历”是任何数据结构均有的公共操作,二叉树是非线性结构,每个结点最多有两个后继,则存在如何遍历,即按什么样的搜索路径遍历的问题。这样就必须规定遍历的规则,按此规则遍历二叉树,最后得到二叉树中所有结点组成的一个线性序列。
二叉树的遍历类型
根据二叉树的结构特征,可以有三类搜索路径:先上而下的按层次遍历、先左(子树)后右(子树)的遍历、先右(子树)后左(子树)的遍历。设访问根结点记作 $D$,遍历根左子树记作 $L$,遍历根的右子树记作 $R$,则可能的遍历次序有:$DLR、LDR、LRD、DRL、RDL、RLD$ 及层次遍历。若规定先左后右,则只剩下4种遍历方式:$DLR、LDR、LRD$ 及层次遍历,根据根结点被遍历的次序,通常称 $DLR、LDR和LRD$ 这3种遍历为前序遍历、中序遍历和后序遍历。
给出如下二叉树实例:
则其不同方式的遍历序列分别为:
- 层次遍历结果序列:ABCDEFG
- 前序遍历结果序列:ABDGCEF
- 中序遍历结果序列:DGBAECF
- 后序遍历结果序列:GDBEFCA
后文依次介绍了层次遍历以及前序、中序和后序算法实现,其中用到的两个二叉树相关的数据结构 BinTree 和 BinTreeNode 可参见此篇。
为简化二叉树遍历代码实现,给出以下二叉树的结点结构:1
2
3
4
5
6
7
8template <class ElemType>
struct BinTreeNode
{
ElemType val; // 结点关键码
BinTreeNode *leftChild; // 左孩子结点
BinTreeNode *rightChild; // 右孩子结点
BinTreeNode(ElemType v) : val(v), leftChild(nullptr), rightChild(nullptr) {} // 构造函数
};
在调用本文中实现的遍历算法时,函数指针参数 Visit
即传入 Print
函数名即可。
层次遍历(Level-Order Traversal)
层次遍历是先访问层次小的所有结点,即从根结点开始,同一层次从左到右访问,然后再访问下一层次的结点。根据层次遍历的定义,除根结点外,每个结点都处于其双亲结点的下一层次,而指向每个结点的指针都记录在其双亲结点中,因此为了找到各结点,需将已经访问过的结点的孩子结点保存下来。使用一个 队列 来存储已访问过的结点的孩子结点。初始将根结点入栈,每次要访问的下一个结点都是队列上取出指向结点的指针,每访问完一个结点后,如果它有左孩子、右孩子结点,则将它的左、右孩子结点入队,如此重复,直到队列为空,则遍历结束。
下面给出二叉树层次遍历具体实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21template <class ElemType>
void LevelOrderTraverse(BinTreeNode<ElemType> *root, void (*Visit)(ElemType &))
{
// 操作结果:层次遍历二叉树
if (root == nullptr) // 二叉树为空,结束算法
return;
queue<BinTreeNode<ElemType> *> q; // 辅助队列
BinTreeNode<ElemType> *node = root; // 从根结点开始进行层次遍历
q.push(node);
while (!q.empty())
{
// 队列非空,说明还有结点未访问
node = q.front();
q.pop();
(*Visit)(node->val);
if (node->leftChild != nullptr) // 左孩子非空
q.push(node->leftChild); // 左孩子入队
if (node->rightChild != nullptr) // 右孩子非空
q.push(node->rightChild); // 右孩子入队
}
}
前序遍历(Pre-Order Traversal)
二叉树的前序遍历定义如下:
如果二叉树为空,则算法结束。
否则:
- 访问根结点(D)
- 前序遍历左子树(L)
- 前序遍历右子树(R)
前序遍历也称为先序遍历,就是按照“根-左子树-右子树”的次序遍历二叉树。
前序遍历算法分为递归和非递归实现。
递归遍历
1 | template <class ElemType> |
非递归遍历
1 | template <class ElemType> |
中序遍历(In-Order Traversal)
二叉树的中序遍历定义如下:
如果二叉树为空,则算法结束。
否则:
- 中序遍历左子树(L)
- 访问根结点(D)
- 中序遍历右子树(R)
中序遍历就是按照“左子树-根-右子树”的次序遍历二叉树。
中序遍历算法分为递归和非递归实现。
递归遍历
1 | template <class ElemType> |
非递归遍历
1 | template <class ElemType> |
后序遍历(Post-Order Traversal)
二叉树的前序遍历定义如下:
如果二叉树为空,则算法结束。
否则:
- 后序遍历左子树(L)
- 后序遍历右子树(R)
- 访问根结点(D)
后序遍历就是按照“左子树-右子树-根”的次序遍历二叉树。
后序遍历算法分为递归和非递归实现。
递归遍历
1 | template <class ElemType> |
非递归遍历
1 | enum Tag |