二叉树是每个节点最多有两个子节点的树形结构,在计算机科学中有着广泛的应用。让我们先了解一些基本术语:
在C++中,我们通常这样定义二叉树节点:
cpp复制struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
这个简单的结构体包含三个成员:
val存储节点的值left指向左子节点的指针right指向右子节点的指针注意:在实际应用中,我们通常会为这个结构体添加构造函数,确保指针初始化为nullptr,避免野指针问题。
前序遍历的顺序是:根节点 → 左子树 → 右子树。这种遍历方式的特点是首先访问根节点,然后递归地访问左子树和右子树。
前序遍历的一个典型应用场景是复制一棵树,因为我们可以先创建父节点,再创建子节点。
递归实现前序遍历非常直观:
cpp复制void preorderHelper(TreeNode* node, vector<int>& result) {
if (node == nullptr) return;
result.push_back(node->val); // 访问根节点
preorderHelper(node->left, result); // 遍历左子树
preorderHelper(node->right,result); // 遍历右子树
}
vector<int> preorderRecursive(TreeNode* root) {
vector<int> result;
preorderHelper(root, result);
return result;
}
递归调用的执行过程可以用下面的调用栈表示:
code复制preorder(1) → push(1)
preorder(2) → push(2)
preorder(4) → push(4)
preorder(5) → push(5)
preorder(3) → push(3)
preorder(6) → push(6)
preorder(7) → push(7)
非递归实现使用显式栈来模拟递归调用:
cpp复制vector<int> preorderIterative(TreeNode* root) {
vector<int> result;
if (root == nullptr) return result;
stack<TreeNode*> stk;
stk.push(root);
while (!stk.empty()) {
TreeNode* node = stk.top();
stk.pop();
result.push_back(node->val);
// 注意:先压右子节点,再压左子节点
if (node->right) stk.push(node->right);
if (node->left) stk.push(node->left);
}
return result;
}
执行过程追踪:
| 步骤 | 操作 | 栈内容(底→顶) | 输出 |
|---|---|---|---|
| 初始 | push(1) | [1] | [] |
| 1 | pop(1), push(3,2) | [3, 2] | [1] |
| 2 | pop(2), push(5,4) | [3, 5, 4] | [1,2] |
| 3 | pop(4) | [3, 5] | [1,2,4] |
| 4 | pop(5) | [3] | [1,2,4,5] |
| 5 | pop(3), push(7,6) | [7, 6] | [1,2,4,5,3] |
| 6 | pop(6) | [7] | [1,2,4,5,3,6] |
| 7 | pop(7) | [] | [1,2,4,5,3,6,7] |
提示:非递归实现的关键在于理解栈的LIFO(后进先出)特性。为了保证左子树先被处理,我们需要先压入右子节点,再压入左子节点。
中序遍历的顺序是:左子树 → 根节点 → 右子树。对于二叉搜索树(BST),中序遍历可以得到一个升序排列的序列,这是BST最重要的特性之一。
cpp复制void inorderHelper(TreeNode* node, vector<int>& result) {
if (node == nullptr) return;
inorderHelper(node->left, result); // 遍历左子树
result.push_back(node->val); // 访问根节点
inorderHelper(node->right, result); // 遍历右子树
}
vector<int> inorderRecursive(TreeNode* root) {
vector<int> result;
inorderHelper(root, result);
return result;
}
非递归实现稍微复杂一些,需要维护一个当前节点指针和栈:
cpp复制vector<int> inorderIterative(TreeNode* root) {
vector<int> result;
stack<TreeNode*> stk;
TreeNode* curr = root;
while (curr != nullptr || !stk.empty()) {
// 阶段一:一直向左走,沿途压栈
while (curr != nullptr) {
stk.push(curr);
curr = curr->left;
}
// 阶段二:回溯,弹出节点并访问
curr = stk.top();
stk.pop();
result.push_back(curr->val);
// 阶段三:转向右子树
curr = curr->right;
}
return result;
}
执行过程追踪:
| 步骤 | curr | 栈内容(底→顶) | 输出 |
|---|---|---|---|
| 向左 | null | [1, 2, 4] | [] |
| pop4 | null | [1, 2] | [4] |
| pop2 | 5 | [1] | [4,2] |
| push5 | null | [1, 5] | [4,2] |
| pop5 | null | [1] | [4,2,5] |
| pop1 | 3 | [] | [4,2,5,1] |
| 向左 | null | [3, 6] | [4,2,5,1] |
| pop6 | null | [3] | [4,2,5,1,6] |
| pop3 | 7 | [] | [4,2,5,1,6,3] |
| pop7 | null | [] | [4,2,5,1,6,3,7] |
注意事项:中序遍历的非递归实现需要特别注意三个阶段:向左走到底、回溯访问节点、转向右子树。这个过程模拟了递归调用的执行流程。
后序遍历的顺序是:左子树 → 右子树 → 根节点。这种遍历方式常用于需要先处理子节点再处理父节点的场景,比如释放树的内存或计算目录大小。
cpp复制void postorderHelper(TreeNode* node, vector<int>& result) {
if (node == nullptr) return;
postorderHelper(node->left, result); // 遍历左子树
postorderHelper(node->right, result); // 遍历右子树
result.push_back(node->val); // 访问根节点
}
vector<int> postorderRecursive(TreeNode* root) {
vector<int> result;
postorderHelper(root, result);
return result;
}
双栈法利用了后序遍历与前序遍历的关系:
cpp复制vector<int> postorderIterative(TreeNode* root) {
vector<int> result;
if (root == nullptr) return result;
stack<TreeNode*> stk1, stk2;
stk1.push(root);
while (!stk1.empty()) {
TreeNode* node = stk1.top();
stk1.pop();
stk2.push(node);
if (node->left) stk1.push(node->left);
if (node->right) stk1.push(node->right);
}
while (!stk2.empty()) {
result.push_back(stk2.top()->val);
stk2.pop();
}
return result;
}
单栈法更为高效,但逻辑也更复杂:
cpp复制vector<int> postorderIterativeSingleStack(TreeNode* root) {
vector<int> result;
stack<TreeNode*> stk;
TreeNode* curr = root;
TreeNode* lastVisited = nullptr;
while (curr != nullptr || !stk.empty()) {
while (curr != nullptr) {
stk.push(curr);
curr = curr->left;
}
TreeNode* peekNode = stk.top();
if (peekNode->right != nullptr && lastVisited != peekNode->right) {
curr = peekNode->right;
} else {
result.push_back(peekNode->val);
lastVisited = peekNode;
stk.pop();
}
}
return result;
}
实用技巧:对于后序遍历,双栈法更容易理解和实现,但需要额外的栈空间。单栈法空间效率更高,但需要维护一个lastVisited指针来判断右子树是否已经处理过。
层序遍历按照树的层次从上到下、每层从左到右访问节点。这种遍历方式实际上是图的广度优先搜索(BFS)在树上的应用。
递归实现层序遍历需要借助深度参数:
cpp复制void levelorderHelper(TreeNode* node, int depth, vector<vector<int>>& levels) {
if (node == nullptr) return;
if ((int)levels.size() == depth)
levels.push_back(vector<int>());
levels[depth].push_back(node->val);
levelorderHelper(node->left, depth + 1, levels);
levelorderHelper(node->right, depth + 1, levels);
}
vector<int> levelorderRecursive(TreeNode* root) {
vector<vector<int>> levels;
levelorderHelper(root, 0, levels);
vector<int> result;
for (auto& level : levels)
for (int val : level)
result.push_back(val);
return result;
}
非递归实现使用队列:
cpp复制vector<int> levelorderIterative(TreeNode* root) {
vector<int> result;
if (root == nullptr) return result;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
int levelSize = q.size();
for (int i = 0; i < levelSize; i++) {
TreeNode* node = q.front();
q.pop();
result.push_back(node->val);
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
}
return result;
}
执行过程追踪:
| 层次 | 处理前队列 | 本层输出 | 加入队列 |
|---|---|---|---|
| 第0层 | [1] | [1] | 2, 3 |
| 第1层 | [2, 3] | [2, 3] | 4, 5, 6, 7 |
| 第2层 | [4,5,6,7] | [4,5,6,7] | — |
注意:层序遍历的非递归实现中,使用levelSize来记录当前层的节点数非常重要,这确保了我们在处理完当前层所有节点后才会开始处理下一层。
所有遍历方式都需要访问每个节点恰好一次,因此时间复杂度均为O(n),其中n是节点总数。
空间复杂度取决于实现方式和树的结构:
| 遍历方式 | 递归 | 非递归(双栈) | 非递归(单栈/队列) |
|---|---|---|---|
| 前序遍历 | O(h) | — | O(h) |
| 中序遍历 | O(h) | — | O(h) |
| 后序遍历 | O(h) | O(n) | O(h) |
| 层序遍历 | O(w) | — | O(w) |
其中:
| 对比维度 | 递归 | 非递归 |
|---|---|---|
| 代码可读性 | 简洁,与定义一一对应 | 逻辑较复杂 |
| 栈溢出风险 | 深树(h≈n)时有风险 | 无风险,显式可控 |
| 空间可控性 | 依赖系统栈,难以干预 | 完全可控 |
| 运行效率 | 略低(函数调用开销) | 略高 |
| 调试难度 | 容易(逻辑清晰) | 较难(需维护栈/队列状态) |
| 推荐场景 | 算法学习、树较平衡 | 生产环境、大规模数据 |
cpp复制#include <iostream>
#include <vector>
#include <stack>
#include <queue>
using namespace std;
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class BinaryTreeTraversal {
public:
// 前序遍历
vector<int> preorderRecursive(TreeNode* root) {
vector<int> res;
preHelper(root, res);
return res;
}
vector<int> preorderIterative(TreeNode* root) {
vector<int> res;
if (!root) return res;
stack<TreeNode*> stk;
stk.push(root);
while (!stk.empty()) {
TreeNode* node = stk.top(); stk.pop();
res.push_back(node->val);
if (node->right) stk.push(node->right);
if (node->left) stk.push(node->left);
}
return res;
}
// 中序遍历
vector<int> inorderRecursive(TreeNode* root) {
vector<int> res;
inHelper(root, res);
return res;
}
vector<int> inorderIterative(TreeNode* root) {
vector<int> res;
stack<TreeNode*> stk;
TreeNode* curr = root;
while (curr || !stk.empty()) {
while (curr) { stk.push(curr); curr = curr->left; }
curr = stk.top(); stk.pop();
res.push_back(curr->val);
curr = curr->right;
}
return res;
}
// 后序遍历
vector<int> postorderRecursive(TreeNode* root) {
vector<int> res;
postHelper(root, res);
return res;
}
vector<int> postorderIterative(TreeNode* root) {
vector<int> res;
if (!root) return res;
stack<TreeNode*> stk1, stk2;
stk1.push(root);
while (!stk1.empty()) {
TreeNode* node = stk1.top(); stk1.pop();
stk2.push(node);
if (node->left) stk1.push(node->left);
if (node->right) stk1.push(node->right);
}
while (!stk2.empty()) {
res.push_back(stk2.top()->val);
stk2.pop();
}
return res;
}
vector<int> postorderIterativeSingleStack(TreeNode* root) {
vector<int> res;
stack<TreeNode*> stk;
TreeNode* curr = root;
TreeNode* lastVisited = nullptr;
while (curr || !stk.empty()) {
while (curr) { stk.push(curr); curr = curr->left; }
TreeNode* peek = stk.top();
if (peek->right && lastVisited != peek->right) {
curr = peek->right;
} else {
res.push_back(peek->val);
lastVisited = peek;
stk.pop();
}
}
return res;
}
// 层序遍历
vector<int> levelorderRecursive(TreeNode* root) {
vector<vector<int>> levels;
lvlHelper(root, 0, levels);
vector<int> res;
for (auto& lv : levels)
for (int v : lv) res.push_back(v);
return res;
}
vector<int> levelorderIterative(TreeNode* root) {
vector<int> res;
if (!root) return res;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
int sz = q.size();
for (int i = 0; i < sz; i++) {
TreeNode* node = q.front(); q.pop();
res.push_back(node->val);
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
}
return res;
}
private:
void preHelper(TreeNode* node, vector<int>& res) {
if (!node) return;
res.push_back(node->val);
preHelper(node->left, res);
preHelper(node->right, res);
}
void inHelper(TreeNode* node, vector<int>& res) {
if (!node) return;
inHelper(node->left, res);
res.push_back(node->val);
inHelper(node->right, res);
}
void postHelper(TreeNode* node, vector<int>& res) {
if (!node) return;
postHelper(node->left, res);
postHelper(node->right, res);
res.push_back(node->val);
}
void lvlHelper(TreeNode* node, int depth, vector<vector<int>>& levels) {
if (!node) return;
if ((int)levels.size() == depth) levels.push_back({});
levels[depth].push_back(node->val);
lvlHelper(node->left, depth + 1, levels);
lvlHelper(node->right, depth + 1, levels);
}
};
void printVec(const string& label, const vector<int>& v) {
cout << label << ": [";
for (int i = 0; i < (int)v.size(); i++) {
cout << v[i];
if (i + 1 < (int)v.size()) cout << ", ";
}
cout << "]" << endl;
}
int main() {
// 构建示例二叉树
// 1
// / \
// 2 3
// / \ / \
// 4 5 6 7
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);
root->right->left = new TreeNode(6);
root->right->right = new TreeNode(7);
BinaryTreeTraversal bt;
cout << "===== 前序遍历 =====" << endl;
printVec("递归 ", bt.preorderRecursive(root));
printVec("非递归", bt.preorderIterative(root));
cout << "\n===== 中序遍历 =====" << endl;
printVec("递归 ", bt.inorderRecursive(root));
printVec("非递归", bt.inorderIterative(root));
cout << "\n===== 后序遍历 =====" << endl;
printVec("递归 ", bt.postorderRecursive(root));
printVec("非递归双栈法", bt.postorderIterative(root));
printVec("非递归单栈法", bt.postorderIterativeSingleStack(root));
cout << "\n===== 层序遍历 =====" << endl;
printVec("递归 ", bt.levelorderRecursive(root));
printVec("非递归", bt.levelorderIterative(root));
return 0;
}
程序输出:
code复制===== 前序遍历 =====
递归 : [1, 2, 4, 5, 3, 6, 7]
非递归: [1, 2, 4, 5, 3, 6, 7]
===== 中序遍历 =====
递归 : [4, 2, 5, 1, 6, 3, 7]
非递归: [4, 2, 5, 1, 6, 3, 7]
===== 后序遍历 =====
递归 : [4, 5, 2, 6, 7, 3, 1]
非递归双栈法: [4, 5, 2, 6, 7, 3, 1]
非递归单栈法: [4, 5, 2, 6, 7, 3, 1]
===== 层序遍历 =====
递归 : [1, 2, 3, 4, 5, 6, 7]
非递归: [1, 2, 3, 4, 5, 6, 7]
在实际开发中,选择哪种遍历方式和实现方法取决于具体需求。递归实现通常更简洁易懂,适合学习和处理不太深的树结构;而非递归实现虽然复杂一些,但可以避免栈溢出风险,更适合处理大规模数据。