二叉树核心概念与遍历算法详解

张开发
2026/5/21 23:53:39 15 分钟阅读
二叉树核心概念与遍历算法详解
1. 二叉树基础概念与核心操作解析二叉树是计算机科学中最基础且重要的数据结构之一它由节点组成每个节点最多有两个子节点分别称为左子节点和右子节点。在实际应用中二叉树常被用于实现高效的搜索和排序算法如二叉搜索树、AVL树等。1.1 二叉树的基本性质每个二叉树节点包含三个部分数据域、左指针和右指针。特殊情况下节点还可以包含指向父节点的指针这在某些操作中能提供便利。二叉树具有以下重要特性第i层最多有2^(i-1)个节点深度为k的二叉树最多有2^k-1个节点对于任何非空二叉树如果叶子节点数为n0度为2的节点数为n2则n0n211.2 二叉树的存储结构二叉树通常有两种存储方式链式存储通过节点间的指针连接struct TreeNode { int val; TreeNode *left; TreeNode *right; TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} };顺序存储使用数组表示适合完全二叉树对于下标为i的节点父节点下标(i-1)/2左子节点2*i1右子节点2*i22. 二叉树的遍历算法精解遍历是二叉树最基本的操作按照访问根节点的顺序不同主要分为三种遍历方式。2.1 递归遍历实现递归实现是最直观的遍历方式代码简洁但需要注意栈溢出问题。// 前序遍历 void preOrder(TreeNode* root) { if(!root) return; cout root-val ; // 访问根节点 preOrder(root-left); // 遍历左子树 preOrder(root-right); // 遍历右子树 } // 中序遍历 void inOrder(TreeNode* root) { if(!root) return; inOrder(root-left); // 遍历左子树 cout root-val ; // 访问根节点 inOrder(root-right); // 遍历右子树 } // 后序遍历 void postOrder(TreeNode* root) { if(!root) return; postOrder(root-left); // 遍历左子树 postOrder(root-right); // 遍历右子树 cout root-val ; // 访问根节点 }2.2 非递归遍历实现非递归实现使用显式栈模拟递归过程避免了递归调用的开销。// 前序遍历非递归版 void preOrderNonRec(TreeNode* root) { if(!root) return; stackTreeNode* s; s.push(root); while(!s.empty()) { TreeNode* node s.top(); s.pop(); cout node-val ; if(node-right) s.push(node-right); // 右子节点先入栈 if(node-left) s.push(node-left); // 左子节点后入栈 } } // 中序遍历非递归版 void inOrderNonRec(TreeNode* root) { stackTreeNode* s; TreeNode* curr root; while(curr || !s.empty()) { while(curr) { // 将左子节点全部入栈 s.push(curr); curr curr-left; } curr s.top(); s.pop(); cout curr-val ; curr curr-right; // 转向右子树 } }注意后序遍历的非递归实现较为复杂通常需要记录前一个访问的节点来判断是否已经处理过右子树。2.3 Morris遍历算法Morris遍历是一种空间复杂度为O(1)的遍历算法它通过利用叶子节点的空指针来临时保存信息。// 中序遍历Morris版 void inOrderMorris(TreeNode* root) { TreeNode *curr root, *pre nullptr; while(curr) { if(!curr-left) { cout curr-val ; curr curr-right; } else { pre curr-left; while(pre-right pre-right ! curr) pre pre-right; if(!pre-right) { pre-right curr; // 建立线索 curr curr-left; } else { pre-right nullptr; // 删除线索 cout curr-val ; curr curr-right; } } } }Morris遍历的关键在于如果当前节点的左子节点为空访问当前节点并转向右子节点如果左子节点不为空找到当前节点在中序遍历下的前驱节点如果前驱节点的右指针为空将其指向当前节点建立线索然后转向左子节点如果前驱节点的右指针指向当前节点说明左子树已访问断开线索访问当前节点然后转向右子节点3. 二叉树的高级操作与应用3.1 树的基本属性计算// 计算节点总数 int countNodes(TreeNode* root) { if(!root) return 0; return 1 countNodes(root-left) countNodes(root-right); } // 计算叶子节点数 int countLeaves(TreeNode* root) { if(!root) return 0; if(!root-left !root-right) return 1; return countLeaves(root-left) countLeaves(root-right); } // 计算树的高度 int getHeight(TreeNode* root) { if(!root) return 0; return 1 max(getHeight(root-left), getHeight(root-right)); }3.2 特殊节点查找// 查找最低公共祖先(LCA) TreeNode* findLCA(TreeNode* root, TreeNode* p, TreeNode* q) { if(!root || root p || root q) return root; TreeNode* left findLCA(root-left, p, q); TreeNode* right findLCA(root-right, p, q); if(left right) return root; // p和q分别在左右子树 return left ? left : right; // 都在左子树或右子树 } // 计算两节点距离 int distanceNodes(TreeNode* root, TreeNode* p, TreeNode* q) { TreeNode* lca findLCA(root, p, q); functionint(TreeNode*, TreeNode*) getDepth [](TreeNode* r, TreeNode* target) { if(!r) return -1; if(r target) return 0; int left getDepth(r-left, target); int right getDepth(r-right, target); if(left -1 right -1) return -1; return 1 (left ! -1 ? left : right); }; return getDepth(lca, p) getDepth(lca, q); }3.3 二叉树转换操作// 二叉树的镜像翻转 void mirror(TreeNode* root) { if(!root) return; swap(root-left, root-right); mirror(root-left); mirror(root-right); } // 判断两棵树结构是否相同 bool isSameStructure(TreeNode* a, TreeNode* b) { if(!a !b) return true; if(!a || !b) return false; return isSameStructure(a-left, b-left) isSameStructure(a-right, b-right); }4. 二叉搜索树专项二叉搜索树(BST)是一种特殊的二叉树对于每个节点左子树所有节点的值小于当前节点的值右子树所有节点的值大于当前节点的值4.1 BST验证与操作// 验证BST bool isValidBST(TreeNode* root, TreeNode* minNode nullptr, TreeNode* maxNode nullptr) { if(!root) return true; if((minNode root-val minNode-val) || (maxNode root-val maxNode-val)) return false; return isValidBST(root-left, minNode, root) isValidBST(root-right, root, maxNode); } // BST中查找节点的前驱和后继 TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) { TreeNode* successor nullptr; while(root) { if(p-val root-val) { successor root; root root-left; } else { root root-right; } } return successor; }4.2 BST与有序链表转换// 有序链表转平衡BST TreeNode* sortedListToBST(ListNode* head) { functionTreeNode*(ListNode*, ListNode*) build [](ListNode* start, ListNode* end) { if(start end) return (TreeNode*)nullptr; ListNode *slow start, *fast start; while(fast ! end fast-next ! end) { slow slow-next; fast fast-next-next; } TreeNode* root new TreeNode(slow-val); root-left build(start, slow); root-right build(slow-next, end); return root; }; return build(head, nullptr); } // BST转循环双链表 TreeNode* treeToDoublyList(TreeNode* root) { if(!root) return nullptr; TreeNode *head nullptr, *prev nullptr; functionvoid(TreeNode*) inorder [](TreeNode* node) { if(!node) return; inorder(node-left); if(!head) head node; if(prev) { prev-right node; node-left prev; } prev node; inorder(node-right); }; inorder(root); head-left prev; prev-right head; return head; }5. 二叉树常见问题与解决方案5.1 序列化与反序列化// 前序遍历序列化 string serialize(TreeNode* root) { if(!root) return #; return to_string(root-val) , serialize(root-left) , serialize(root-right); } // 前序遍历反序列化 TreeNode* deserialize(string data) { queuestring q; stringstream ss(data); string item; while(getline(ss, item, ,)) q.push(item); functionTreeNode*() build []() { string val q.front(); q.pop(); if(val #) return (TreeNode*)nullptr; TreeNode* root new TreeNode(stoi(val)); root-left build(); root-right build(); return root; }; return build(); }5.2 完全二叉树验证bool isCompleteTree(TreeNode* root) { if(!root) return true; queueTreeNode* q; q.push(root); bool seenNull false; while(!q.empty()) { TreeNode* node q.front(); q.pop(); if(!node) { seenNull true; } else { if(seenNull) return false; q.push(node-left); q.push(node-right); } } return true; }5.3 根据遍历序列重建二叉树// 前序中序重建二叉树 TreeNode* buildTree(vectorint preorder, vectorint inorder) { unordered_mapint, int inMap; for(int i 0; i inorder.size(); i) inMap[inorder[i]] i; functionTreeNode*(int, int, int, int) build [](int preStart, int preEnd, int inStart, int inEnd) { if(preStart preEnd || inStart inEnd) return (TreeNode*)nullptr; TreeNode* root new TreeNode(preorder[preStart]); int inRoot inMap[root-val]; int numsLeft inRoot - inStart; root-left build(preStart1, preStartnumsLeft, inStart, inRoot-1); root-right build(preStartnumsLeft1, preEnd, inRoot1, inEnd); return root; }; return build(0, preorder.size()-1, 0, inorder.size()-1); }6. 二叉树算法优化与实战技巧6.1 递归优化策略尾递归优化将递归调用放在函数最后某些编译器可以优化为迭代备忘录技术缓存已计算结果避免重复计算剪枝策略提前终止不必要的递归路径6.2 迭代替代递归的通用模式// 通用迭代模板 void traversal(TreeNode* root) { stackpairTreeNode*, bool s; s.push({root, false}); while(!s.empty()) { auto [node, visited] s.top(); s.pop(); if(!node) continue; if(visited) { // 处理节点 cout node-val ; } else { // 调整以下三行的顺序可实现不同遍历 s.push({node-right, false}); s.push({node-left, false}); s.push({node, true}); // 标记为已访问 } } }6.3 二叉树问题调试技巧可视化工具使用图形化工具展示二叉树结构小规模测试先用简单树结构验证算法正确性边界测试空树、单节点、左斜树、右斜树等特殊情况遍历验证通过不同遍历方式交叉验证结果在实际开发中二叉树相关算法广泛应用于文件系统、数据库索引、路由算法等领域。掌握这些基础算法不仅能帮助解决面试问题更能为理解更复杂的数据结构和算法打下坚实基础。

更多文章