概述
二叉树的遍历是指按照某种规则访问二叉树中所有节点的过程。遍历不仅是理解二叉树结构的基础,也是实现各种树操作的前提。二叉树的遍历顺序主要由根节点的访问时机决定,从而形成了三种深度优先遍历方式:前序、中序和后序。此外,还有基于层次的广度优先遍历方式:层序遍历。
深度优先遍历的特点是:沿着树的深度尽可能深地搜索,直到到达叶子节点或无法继续为止,然后回溯。广度优先遍历的特点是:按照树的层次从上到下、从左到右依次访问节点。

深度前序遍历
深度前序遍历的核心特点是:先访问根节点,然后再递归地遍历左子树和右子树。具体访问顺序如下:
1、访问当前节点(根节点)。
2、递归遍历左子树。
3、递归遍历右子树。
考虑下面的二叉树,其前序遍历的结果为:1 → 2 → 4 → 5 → 3。
cpp
1
/ \
2 3
/ \
4 5
递归法是实现深度前序遍历最直观、最简洁的方式,直接体现了前序遍历的定义,示例代码如下。
cpp
void PreorderTree(TreeNode* pRoot)
{
if (pRoot == NULL)
{
return;
}
cout << pRoot->nData << " ";
PreorderTree(pRoot->pLeft);
PreorderTree(pRoot->pRight);
}
深度前序遍历的时间复杂度为O(n),其中n是树中节点的数量,每个节点恰好被访问一次。空间复杂度为O(h),其中h是树的高度。
深度中序遍历
深度中序遍历的核心特点是:在遍历完左子树后访问根节点,然后再遍历右子树。具体访问顺序如下:
1、递归遍历左子树。
2、访问当前节点(根节点)。
3、递归遍历右子树。
考虑下面的二叉树,其中序遍历的结果为:1 → 2 → 3 → 4 → 5 → 6 → 7。
cpp
4
/ \
2 6
/ \ / \
1 3 5 7
深度前序遍历采用了递归的方法实现,这里我们采用迭代法来实现深度中序遍历,示例代码如下。
cpp
void InorderTreeIterative(TreeNode* pRoot)
{
stack<TreeNode*> nodeStack;
TreeNode* pCurrent = pRoot;
while (pCurrent != NULL || !nodeStack.empty())
{
// 一直向左走到底,将路径上的节点压入栈
while (pCurrent != NULL)
{
nodeStack.push(pCurrent);
pCurrent = pCurrent->pLeft;
}
// 此时pCurrent为空,说明到达最左节点
pCurrent = nodeStack.top();
nodeStack.pop();
// 访问该节点
cout << pCurrent->nData << " ";
// 转向右子树
pCurrent = pCurrent->pRight;
}
}
深度后序遍历
深度后序遍历的核心特点是:最后访问根节点,在遍历完左右子树之后才访问当前节点。具体访问顺序如下:
1、递归遍历左子树。
2、递归遍历右子树。
3、访问当前节点(根节点)。
考虑下面的二叉树,其后序遍历的结果为:4 → 5 → 2 → 3 → 1。
cpp
1
/ \
2 3
/ \
4 5
使用递归法实现深度后续遍历相对比较简单,可参考下面的示例代码。
cpp
void PostorderTree(TreeNode* pRoot)
{
if (pRoot == NULL)
{
return;
}
PostorderTree(pRoot->pLeft);
PostorderTree(pRoot->pRight);
cout << pRoot->nData << " ";
}
广度层序遍历
广度层序遍历的核心特点是:按层次访问,从根节点开始,逐层向下,每层从左到右访问所有节点。具体访问顺序如下:
1、访问第1层(根节点)。
2、访问第2层(根的子节点)。
3、访问第3层(第2层节点的子节点)。
4、依此类推,直到最后一层。
考虑下面的二叉树,其层序遍历的结果为:1 → 2 → 3 → 4 → 5 → 6 → 7。
cpp
1
/ \
2 3
/ \ \
4 5 6
\
7
广度层序遍历推荐使用迭代法来实现,最自然的实现方式是使用队列数据结构,遵循"先进先出"的原则,示例代码如下。
cpp
void LevelOrder(TreeNode* pRoot)
{
if (pRoot == NULL)
{
return;
}
queue<TreeNode*> nodeQueue;
nodeQueue.push(pRoot);
while (!nodeQueue.empty())
{
// 取出队首节点
TreeNode* pCurrent = nodeQueue.front();
nodeQueue.pop();
// 访问当前节点
cout << pCurrent->nData << " ";
// 将左子节点入队(如果存在)
if (pCurrent->pLeft != NULL)
{
nodeQueue.push(pCurrent->pLeft);
}
// 将右子节点入队(如果存在)
if (pCurrent->pRight != NULL)
{
nodeQueue.push(pCurrent->pRight);
}
}
}