二叉树遍历
(Traversal)
是按照某种特定的规则,依次对二叉
树中的结点进行相应的操作,并且每个结点只操作一次
。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
前·中·后序遍历:
按照规则,二叉树的遍历有:
前序
/
中序
/
后序的递归结构遍历
:
- 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
- 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
- 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
由于被访问的结点必是某子树的根,
所以
N(Node
)、
L(Left subtree
)和
R(Right subtree
)又可解释为
根、根的左子树和根的右子树
。
NLR
、
LNR
和
LRN
分别又称为先根遍历、中根遍历和后根遍历。
相应地,前序、中序和后序遍历都属于
深度优先遍历(depth-first traversal),也称
深度优先搜索(depth-first search, DFS),它体现了一种“先走到尽头,再回溯继续”的遍历方式。
深度优先遍历就像是绕着整棵二叉树的外围“走”一圈,在每个节点都会遇到三个位置,分别对应前序遍历、中序遍历和后序遍历。
为了更好理解递归,下图展示了前序遍历二叉树的递归过程,其可分为“递”和“归”两个逆向的部分:
- “递”表示开启新方法,程序在此过程中访问下一个节点。
- “归”表示函数返回,代表当前节点已经访问完毕。
以下使用C语言,利用递归实现二叉树的前·中·后序遍历:
首先:定义一个二叉树节点的结构体:
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
// 二叉树前序遍历
void PreOrder(TreeNode* root);
// 二叉树中序遍历
void InOrder(TreeNode* root);
// 二叉树后序遍历
void PostOrder(TreeNode* root);
前序遍历:
NLR:
实现代码:
void preOrder(TreeNode* node) {
if (node == nullptr) return;
std::cout << node->val << " "; // 访问当前节点
preOrder(node->left); // 遍历左子树
preOrder(node->right); // 遍历右子树
}
按照这种递归方式,我们可以很快理解中序和后序遍历
中序遍历:
LNR:
实现代码:
void inOrder(TreeNode* node) {
if (node == nullptr) return;
inOrder(node->left); // 遍历左子树
std::cout << node->val << " "; // 访问当前节点
inOrder(node->right); // 遍历右子树
}
后序遍历:
LRN:
实现代码:
void postOrder(TreeNode* node) {
if (node == nullptr) return;
postOrder(node->left); // 遍历左子树
postOrder(node->right); // 遍历右子树
std::cout << node->val << " "; // 访问当前节点
}
复杂度:
对于二叉树的前·中·后序遍历:
- 时间复杂度为 O(n) :所有节点被访问一次,使用 O(n) 时间。
- 空间复杂度为 O(n) :在最差情况下,即树退化为链表时,递归深度达到 n ,系统占用 O(n) 栈帧空间。
层序遍历:
层序遍历(level-order traversal)从顶部到底部逐层遍历二叉树,并在每一层按照从左到右的顺序访问节点。
层序遍历本质上属于广度优先遍历(breadth-first traversal),也称广度优先搜索(breadth-first search, BFS),它体现了一种“一圈一圈向外扩展”的逐层遍历方式。
广度优先遍历通常借助“队列”来实现。队列遵循“先进先出”的规则,而广度优先遍历则遵循“逐层推进”的规则,两者背后的思想是一致的。
层序遍历的步骤:
-
初始化:使用一个队列来存储待访问的节点。从根节点开始,将其添加到队列中。
-
循环遍历:当队列不为空时,执行以下操作:
- 从队列中取出一个节点(这通常是当前层的第一个节点)。
- 对该节点进行访问操作,例如打印节点的值。
- 将该节点的所有未访问过的子节点添加到队列的末尾。
-
继续遍历:重复步骤2,直到队列为空,这意味着所有节点已经被访问过。
层序遍历的特点:
- 层序遍历按照从上到下、从左到右的顺序访问节点。
- 它使用队列作为辅助数据结构来实现。
- 层序遍历可以用于实现树的复制、最大深度计算、节点间最短路径查找等。
实现代码:
#include <iostream>
#include <queue>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
void levelOrderTraversal(TreeNode* root) {
if (!root) return;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
TreeNode* current = q.front();
q.pop();
cout << current->val << " "; // 访问当前节点
if (current->left) q.push(current->left); // 添加左子节点
if (current->right) q.push(current->right); // 添加右子节点
}
cout << endl; // 结束当前层的输出
}
注意事项:
- 在实际应用中,确保在访问完队列中的所有节点后,将它们从队列中移除(
q.pop()
)。 - 访问节点的操作可以根据具体需求进行修改,例如,可以是打印节点值、计算节点数量等。
- 确保在添加子节点到队列之前检查它们是否存在,避免添加
nullptr
到队列。 - 在使用完动态分配的内存后,记得释放以避免内存泄漏。
层序遍历是一种非常实用的树遍历方法,特别是在需要逐层处理树结构时。
复杂度:
- 时间复杂度为 O(n) :所有节点被访问一次,使用 O(n) 时间,其中 n 为节点数量。
- 空间复杂度为 O(n) :在最差情况下,即满二叉树时,遍历到最底层之前,队列中最多同时存在 (n+1)/2 个节点,占用 O(n) 空间。
测试结果:
int main() {
TreeNode* root = new TreeNode(1);
root->left = new TreeNode(2);
root->right = new TreeNode(3);
root->left->left = new TreeNode(4);
root->left->right = new TreeNode(5);
std::cout << "Pre-order traversal: ";
preOrder(root);
std::cout << std::endl;
std::cout << "In-order traversal: ";
inOrder(root);
std::cout << std::endl;
std::cout << "Post-order traversal: ";
postOrder(root);
std::cout << std::endl;
std::cout << "Level-order traversal: ";
levelOrderTraversal(root);
std::cout << std::endl;
return 0;
}
Pre-order traversal: 1 2 4 5 3
In-order traversal: 4 2 5 1 3
Post-order traversal: 4 5 2 3 1
Level-order traversal: 1 2 3 4 5
D:\2024C语言\data-structure\二叉树遍历\x64\Debug\二叉树遍历.exe (进程 11512)已退出,代码为 0 (0x0)。
按任意键关闭此窗口. . .