news 2026/9/12 10:33:04

二叉树算法实战:从递归到迭代的C++实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
二叉树算法实战:从递归到迭代的C++实现

1. 二叉树基础与字符串表示

在C++中处理二叉树问题时,最基础也最容易被忽视的就是如何正确表示树结构。我们先来看一个看似简单但暗藏玄机的问题:根据二叉树创建字符串。

1.1 问题描述与示例分析

LeetCode 606题要求我们将二叉树按照特定规则转换为字符串表示。规则如下:

  • 空节点用空字符串表示
  • 非空节点用其值表示
  • 对于每个非空节点:
    • 如果只有右孩子,左孩子的空括号不能省略
    • 如果只有左孩子,可以省略右孩子的空括号

举个例子:

1 / \ 2 3 / 4

应该表示为"1(2(4))(3)",而不是"1(2(4)())(3())"。

1.2 递归解法实现

这个问题的经典解法是递归遍历,但有几个关键细节需要注意:

class Solution { public: string tree2str(TreeNode* root) { if (!root) return ""; string res = to_string(root->val); // 关键判断1:左子树为空但右子树不空时 if (!root->left && root->right) { res += "()"; } // 关键判断2:左子树不空时 if (root->left) { res += "(" + tree2str(root->left) + ")"; } // 关键判断3:右子树不空时 if (root->right) { res += "(" + tree2str(root->right) + ")"; } return res; } };

注意:to_string()函数在转换节点值时可能会成为性能瓶颈,对于高频调用场景建议预先分配缓冲区。

1.3 迭代解法优化

递归解法虽然直观,但在处理大型树时可能引发栈溢出。我们可以用栈模拟递归过程:

string tree2str_iterative(TreeNode* root) { if (!root) return ""; stack<TreeNode*> st; st.push(root); unordered_set<TreeNode*> visited; string res; while (!st.empty()) { TreeNode* node = st.top(); if (visited.count(node)) { st.pop(); res += ")"; } else { visited.insert(node); res += "(" + to_string(node->val); // 处理右左子树的顺序 if (!node->left && node->right) res += "()"; if (node->right) st.push(node->right); if (node->left) st.push(node->left); } } return res.substr(1, res.size()-2); }

这种解法虽然代码量增加,但避免了递归深度限制,适合生产环境使用。

2. 最近公共祖先问题

2.1 LCA问题定义

最近公共祖先(Lowest Common Ancestor)是二叉树中的经典问题。给定两个节点p和q,找到它们在树中最低的公共祖先节点。

2.2 递归解法分析

最直观的解法是通过递归搜索:

TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { if (!root || root == p || root == q) return root; TreeNode* left = lowestCommonAncestor(root->left, p, q); TreeNode* right = lowestCommonAncestor(root->right, p, q); if (left && right) return root; return left ? left : right; }

这个解法的时间复杂度是O(n),空间复杂度最坏情况下也是O(n)。

2.3 非递归解法实现

我们可以使用父指针记录法来优化:

TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { unordered_map<TreeNode*, TreeNode*> parent; stack<TreeNode*> st; parent[root] = nullptr; st.push(root); // 构建父指针映射 while (!parent.count(p) || !parent.count(q)) { TreeNode* node = st.top(); st.pop(); if (node->left) { parent[node->left] = node; st.push(node->left); } if (node->right) { parent[node->right] = node; st.push(node->right); } } // 收集p的祖先路径 set<TreeNode*> ancestors; while (p) { ancestors.insert(p); p = parent[p]; } // 查找q的祖先中第一个出现在p路径中的节点 while (!ancestors.count(q)) { q = parent[q]; } return q; }

这种方法虽然空间复杂度略高,但在多次查询时可以复用父指针映射,适合查询密集型场景。

3. 二叉搜索树与双向链表

3.1 问题转换思路

将二叉搜索树转换为排序的双向链表,要求不能创建新节点,只能调整指针指向。这是一个典型的树与链表转换问题。

3.2 中序遍历解法

利用BST的中序遍历特性,我们可以得到有序序列:

class Solution { TreeNode* prev = nullptr; TreeNode* head = nullptr; public: TreeNode* treeToDoublyList(TreeNode* root) { if (!root) return nullptr; inorder(root); // 连接首尾形成循环 head->left = prev; prev->right = head; return head; } void inorder(TreeNode* node) { if (!node) return; inorder(node->left); if (!prev) { head = node; // 记录链表头 } else { prev->right = node; node->left = prev; } prev = node; inorder(node->right); } };

注意:在面试中常被问及非递归实现,建议同时掌握迭代版本。

3.3 迭代实现版本

TreeNode* treeToDoublyList_iterative(TreeNode* root) { if (!root) return nullptr; stack<TreeNode*> st; TreeNode *head = nullptr, *prev = nullptr; TreeNode* curr = root; while (curr || !st.empty()) { while (curr) { st.push(curr); curr = curr->left; } curr = st.top(); st.pop(); if (!prev) { head = curr; } else { prev->right = curr; curr->left = prev; } prev = curr; curr = curr->right; } // 连接首尾 head->left = prev; prev->right = head; return head; }

4. 前序与中序构建二叉树

4.1 重建二叉树原理

给定前序和中序遍历序列,可以唯一确定一棵二叉树。前序的第一个元素是根节点,中序中该元素左边是左子树,右边是右子树。

4.2 递归实现

TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) { unordered_map<int, int> inMap; for (int i = 0; i < inorder.size(); i++) { inMap[inorder[i]] = i; } return build(preorder, 0, preorder.size()-1, inorder, 0, inorder.size()-1, inMap); } TreeNode* build(vector<int>& preorder, int preStart, int preEnd, vector<int>& inorder, int inStart, int inEnd, unordered_map<int, int>& inMap) { if (preStart > preEnd || inStart > inEnd) return nullptr; TreeNode* root = new TreeNode(preorder[preStart]); int inRoot = inMap[root->val]; int numsLeft = inRoot - inStart; root->left = build(preorder, preStart+1, preStart+numsLeft, inorder, inStart, inRoot-1, inMap); root->right = build(preorder, preStart+numsLeft+1, preEnd, inorder, inRoot+1, inEnd, inMap); return root; }

4.3 迭代实现优化

递归解法在极端情况下可能导致栈溢出,我们可以用栈来模拟递归过程:

TreeNode* buildTree_iterative(vector<int>& preorder, vector<int>& inorder) { if (preorder.empty()) return nullptr; stack<TreeNode*> st; TreeNode* root = new TreeNode(preorder[0]); st.push(root); int inIndex = 0; for (int i = 1; i < preorder.size(); i++) { TreeNode* node = st.top(); if (node->val != inorder[inIndex]) { node->left = new TreeNode(preorder[i]); st.push(node->left); } else { while (!st.empty() && st.top()->val == inorder[inIndex]) { node = st.top(); st.pop(); inIndex++; } node->right = new TreeNode(preorder[i]); st.push(node->right); } } return root; }

5. 二叉树的非递归遍历

5.1 前序遍历的非递归实现

vector<int> preorderTraversal(TreeNode* root) { vector<int> res; stack<TreeNode*> st; if (root) st.push(root); while (!st.empty()) { TreeNode* node = st.top(); st.pop(); res.push_back(node->val); // 注意右子树先入栈 if (node->right) st.push(node->right); if (node->left) st.push(node->left); } return res; }

5.2 中序遍历的非递归实现

vector<int> inorderTraversal(TreeNode* root) { vector<int> res; stack<TreeNode*> st; TreeNode* curr = root; while (curr || !st.empty()) { while (curr) { st.push(curr); curr = curr->left; } curr = st.top(); st.pop(); res.push_back(curr->val); curr = curr->right; } return res; }

5.3 后序遍历的非递归实现

后序遍历是最复杂的,需要记录访问状态:

vector<int> postorderTraversal(TreeNode* root) { vector<int> res; stack<TreeNode*> st; TreeNode* lastVisited = nullptr; TreeNode* curr = root; while (curr || !st.empty()) { if (curr) { st.push(curr); curr = curr->left; } else { TreeNode* peek = st.top(); if (peek->right && peek->right != lastVisited) { curr = peek->right; } else { res.push_back(peek->val); lastVisited = peek; st.pop(); } } } return res; }

5.4 统一迭代法模板

为了统一三种遍历方式,可以使用标记法:

// 前序遍历 vector<int> preorderTraversal_unified(TreeNode* root) { vector<int> res; stack<TreeNode*> st; if (root) st.push(root); while (!st.empty()) { TreeNode* node = st.top(); st.pop(); if (node) { if (node->right) st.push(node->right); // 右 if (node->left) st.push(node->left); // 左 st.push(node); // 中 st.push(nullptr); // 标记 } else { node = st.top(); st.pop(); res.push_back(node->val); } } return res; }

只需调整入栈顺序,即可实现三种遍历的统一模板。

6. 二叉树问题实战技巧

6.1 调试与可视化

在处理复杂二叉树问题时,可视化工具能极大提升效率。可以自定义打印函数:

void printTree(TreeNode* root, int space = 0, int height = 10) { if (!root) return; space += height; printTree(root->right, space); cout << endl; for (int i = height; i < space; i++) cout << ' '; cout << root->val << "\n"; printTree(root->left, space); }

6.2 内存管理注意事项

在面试或竞赛中,经常需要手动管理二叉树内存:

void deleteTree(TreeNode* root) { if (!root) return; deleteTree(root->left); deleteTree(root->right); delete root; }

6.3 常见错误排查

  1. 空指针异常:总是检查节点是否为null
  2. 无限递归:确保递归有终止条件
  3. 错误的状态维护:在非递归遍历中正确维护栈状态
  4. 指针修改错误:在链表转换问题中注意指针修改顺序

6.4 性能优化建议

  1. 对于高频调用的辅助函数,考虑使用静态变量缓存结果
  2. 在递归解法中,尽可能使用尾递归优化
  3. 对于大型树,优先考虑迭代解法避免栈溢出
  4. 使用哈希表存储中间结果,减少重复计算

7. 二叉树扩展问题

7.1 序列化与反序列化

// 序列化为字符串 string serialize(TreeNode* root) { if (!root) return "#"; return to_string(root->val) + "," + serialize(root->left) + "," + serialize(root->right); } // 从字符串反序列化 TreeNode* deserialize(string data) { queue<string> q; string s; for (char c : data) { if (c == ',') { q.push(s); s = ""; } else { s += c; } } if (!s.empty()) q.push(s); return helper(q); } TreeNode* helper(queue<string>& q) { string s = q.front(); q.pop(); if (s == "#") return nullptr; TreeNode* root = new TreeNode(stoi(s)); root->left = helper(q); root->right = helper(q); return root; }

7.2 验证二叉搜索树

bool isValidBST(TreeNode* root) { stack<TreeNode*> st; TreeNode* prev = nullptr; TreeNode* curr = root; while (curr || !st.empty()) { while (curr) { st.push(curr); curr = curr->left; } curr = st.top(); st.pop(); if (prev && prev->val >= curr->val) return false; prev = curr; curr = curr->right; } return true; }

7.3 二叉树的最大路径和

int maxPathSum(TreeNode* root) { int maxSum = INT_MIN; helper(root, maxSum); return maxSum; } int helper(TreeNode* node, int& maxSum) { if (!node) return 0; int left = max(helper(node->left, maxSum), 0); int right = max(helper(node->right, maxSum), 0); maxSum = max(maxSum, left + right + node->val); return max(left, right) + node->val; }

在实际工程中,二叉树问题的解决往往需要结合具体业务场景进行调整。建议在掌握这些经典算法的基础上,多思考如何将它们应用到实际问题中。例如,文件系统的目录结构、组织架构图、决策树等都可以用二叉树模型来表示和处理。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 10:32:32

SpringBoot+Vue3美食分享平台架构设计与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 10:31:13

HJ212解析器实战:Java实现TCP粘包拆包与CRC校验

简介&#xff1a;面向环保数据通信开发者的HJ212协议Java解析器示例&#xff0c;聚焦环境监测数据采集传输协议&#xff08;HJ212&#xff09;的报文解析与数据处理&#xff0c;适合需要对接环保数采仪、搭建数据接入平台或学习自定义协议解析的程序员。压缩包共112个文件&…

作者头像 李华
网站建设 2026/9/12 10:30:25

Rust驱动的具身智能边缘运行时:MicroDuck设计与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 10:30:24

Flutter在鸿蒙平台的errno库适配与优化实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 10:29:45

低功耗开发实战:DVFS原理与安卓嵌入式功耗优化全链路解析

1. 为什么“低功耗”不是一句口号&#xff0c;而是嵌入式与安卓工程师的生存门槛你刚投出一份嵌入式软件工程师简历&#xff0c;HR秒回&#xff1a;“有低功耗开发经验吗&#xff1f;”你打开某招聘平台搜“安卓开发”&#xff0c;前20个岗位里17个写着“熟悉系统功耗优化者优先…

作者头像 李华
网站建设 2026/9/12 10:28:30

静态代码分析工具选型与实战:从规则配置到团队落地的最佳实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华