尧图精选

二叉树递归算法解析与C语言实现

🕒 发布时间:2026/9/17 6:41:59 📁 来源:尧图网络
1. 二叉树基础与递归思想二叉树是数据结构中最基础也最重要的非线性结构之一它由节点组成每个节点最多有两个子节点左子节点和右子节点。在解决二叉树问题时递归是最自然、最直观的思维方式。1.1 二叉树的C语言表示在C语言中我们通常使用结构体来表示二叉树节点typedef int BTDataType; typedef struct BTNode { struct BTNode* left; // 左子节点指针 struct BTNode* right; // 右子节点指针 BTDataType data; // 节点存储的数据 } BTNode;这种表示方法简洁明了left和right指针分别指向左右子节点data字段存储节点值。当left或right为NULL时表示该侧没有子节点。1.2 递归思想的本质递归是一种通过将问题分解为更小的同类子问题来解决问题的方法。在二叉树中递归天然适用因为每个节点都可以看作是一个子树的根节点。递归函数通常包含两部分基线条件Base Case递归终止的条件递归条件Recursive Case如何将问题分解为更小的子问题提示编写递归函数时一定要先明确基线条件否则递归将无法终止导致栈溢出。2. 查找二叉树第k层节点个数2.1 问题描述与函数原型给定一个二叉树的根节点root和一个整数k返回该二叉树第k层的节点个数。规定根节点为第1层。函数原型int TreeLevelKSize(BTNode* root, int k);2.2 递归解法详解int TreeLevelKSize(BTNode* root, int k) { if (root NULL) // 基线条件1空节点 return 0; if (k 1) // 基线条件2到达目标层 return 1; // 递归条件左子树k-1层 右子树k-1层 return TreeLevelKSize(root-left, k - 1) TreeLevelKSize(root-right, k - 1); }2.2.1 递归过程分析假设我们有如下二叉树查找第3层的节点个数A / \ B C / \ \ D E F调用过程TreeLevelKSize(A, 3)k3≠1递归计算左子树B和右子树C的第2层TreeLevelKSize(B, 2)k2≠1递归计算左子树D和右子树E的第1层TreeLevelKSize(D, 1)k1返回1TreeLevelKSize(E, 1)k1返回1B的返回值112TreeLevelKSize(C, 2)k2≠1递归计算左子树NULL和右子树F的第1层TreeLevelKSize(NULL, 1)空节点返回0TreeLevelKSize(F, 1)k1返回1C的返回值011A的返回值213最终结果为3D、E、F。2.3 时间复杂度分析该算法的时间复杂度为O(n)其中n是树中的节点数。在最坏情况下树完全不平衡每个节点只有一个子节点需要访问所有节点。空间复杂度为O(h)其中h是树的高度这是由于递归调用栈的深度。3. 二叉树查找值为x的节点3.1 问题描述与常见错误查找二叉树中值为x的节点并返回该节点的指针。常见错误版本BTNode* TreeFind(BTNode* root, BTDataType x) { if (root NULL) return NULL; if (root-data x) return root; TreeFind(root-left, x); // 错误返回值被忽略 TreeFind(root-right, x); // 错误返回值被忽略 // 错误非void函数缺少返回值 }这个版本有三个主要问题递归调用的返回值被忽略函数缺少最终的return语句即使找到目标节点结果可能无法正确返回3.2 正确解法与优化BTNode* TreeFind(BTNode* root, BTDataType x) { if (root NULL) return NULL; if (root-data x) return root; BTNode* ret TreeFind(root-left, x); if (ret ! NULL) // 如果在左子树找到直接返回 return ret; return TreeFind(root-right, x); // 否则返回右子树查找结果 }3.2.1 优化思路先检查当前节点是否满足条件然后在左子树中查找如果找到立即返回最后在右子树中查找无论是否找到都返回结果这种短路策略可以提高效率一旦找到目标就立即返回避免不必要的搜索。3.3 实际应用场景这种查找操作在实际中有广泛应用例如在文件系统中查找特定文件在DOM树中查找特定元素在游戏场景树中查找特定对象4. 单值二叉树判断4.1 问题描述判断一棵二叉树是否是单值二叉树即所有节点的值都相同。LeetCode题目链接 965. 单值二叉树4.2 递归解法bool isUnivalTree(struct TreeNode* root) { if (root NULL) return true; // 检查左子节点 if (root-left root-left-val ! root-val) return false; // 检查右子节点 if (root-right root-right-val ! root-val) return false; // 递归检查左右子树 return isUnivalTree(root-left) isUnivalTree(root-right); }4.3 迭代解法递归解法虽然简洁但可能会因为递归深度过大导致栈溢出。下面是使用栈的迭代解法bool isUnivalTree(struct TreeNode* root) { if (root NULL) return true; int val root-val; struct TreeNode* stack[100]; int top -1; stack[top] root; while (top 0) { struct TreeNode* node stack[top--]; if (node-val ! val) return false; if (node-right) stack[top] node-right; if (node-left) stack[top] node-left; } return true; }4.3.1 迭代法分析使用栈模拟递归过程先将根节点入栈循环处理栈中节点弹出栈顶节点并检查其值将右子节点和左子节点依次入栈保证处理顺序如果所有节点值都相同返回true这种方法的空间复杂度最坏为O(n)但避免了递归的栈溢出风险。5. 相同的树判断5.1 问题描述给定两棵二叉树的根节点p和q判断它们是否完全相同结构和节点值。LeetCode题目链接 100. 相同的树5.2 递归解法bool isSameTree(struct TreeNode* p, struct TreeNode* q) { if (p NULL q NULL) return true; if (p NULL || q NULL) return false; if (p-val ! q-val) return false; return isSameTree(p-left, q-left) isSameTree(p-right, q-right); }5.3 边界条件分析两棵树都为空相同一棵树为空另一棵不为空不同当前节点值不同不同递归检查左右子树是否相同5.4 实际应用这种比较操作在以下场景很有用版本控制系统中比较目录结构测试框架中验证生成的树结构是否符合预期数据库索引结构的验证6. 对称二叉树判断6.1 问题描述判断一棵二叉树是否是镜像对称的。LeetCode题目链接 101. 对称二叉树6.2 递归解法bool isMirror(struct TreeNode* p, struct TreeNode* q) { if (p NULL q NULL) return true; if (p NULL || q NULL) return false; return (p-val q-val) isMirror(p-left, q-right) isMirror(p-right, q-left); } bool isSymmetric(struct TreeNode* root) { if (root NULL) return true; return isMirror(root-left, root-right); }6.3 解题思路空树是对称的非空树对称的条件左子树和右子树互为镜像两棵树互为镜像的条件根节点值相同一棵树的左子树与另一棵树的右子树互为镜像一棵树的右子树与另一棵树的左子树互为镜像6.4 迭代解法bool isSymmetric(struct TreeNode* root) { if (root NULL) return true; struct TreeNode* stack[1000]; int top -1; stack[top] root-left; stack[top] root-right; while (top 0) { struct TreeNode* p stack[top--]; struct TreeNode* q stack[top--]; if (p NULL q NULL) continue; if (p NULL || q NULL) return false; if (p-val ! q-val) return false; stack[top] p-left; stack[top] q-right; stack[top] p-right; stack[top] q-left; } return true; }这种迭代解法使用栈来模拟递归过程每次比较两个节点然后将它们的子节点按镜像顺序压入栈中。7. 判断子树7.1 问题描述给定两棵非空二叉树root和subRoot判断subRoot是否是root的子树。LeetCode题目链接 572. 另一棵树的子树7.2 递归解法bool isSameTree(struct TreeNode* p, struct TreeNode* q) { if (p NULL q NULL) return true; if (p NULL || q NULL) return false; if (p-val ! q-val) return false; return isSameTree(p-left, q-left) isSameTree(p-right, q-right); } bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot) { if (root NULL) return false; if (isSameTree(root, subRoot)) return true; return isSubtree(root-left, subRoot) || isSubtree(root-right, subRoot); }7.3 算法分析首先实现判断两棵树是否相同的辅助函数isSameTree主函数isSubtree递归检查当前节点开始的子树是否与subRoot相同递归检查左子树或右子树是否包含subRoot时间复杂度O(m×n)其中m和n分别是root和subRoot的节点数。对于root中的每个节点最坏情况下需要比较n个节点。7.4 优化思路可以引入字符串匹配的思想将两棵树序列化为字符串然后判断subRoot的序列化字符串是否是root序列化字符串的子串。这种方法可以利用KMP等高效字符串匹配算法。8. 二叉树的前序遍历8.1 问题描述实现二叉树的前序遍历根-左-右顺序并返回节点值数组。LeetCode题目链接 144. 二叉树的前序遍历8.2 递归解法int treeSize(struct TreeNode* root) { if (root NULL) return 0; return treeSize(root-left) treeSize(root-right) 1; } void preorder(struct TreeNode* root, int* a, int* i) { if (root NULL) return; a[(*i)] root-val; preorder(root-left, a, i); preorder(root-right, a, i); } int* preorderTraversal(struct TreeNode* root, int* returnSize) { *returnSize treeSize(root); int* a (int*)malloc(sizeof(int) * (*returnSize)); int i 0; preorder(root, a, i); return a; }8.3 关键点解析需要预先计算树的大小以分配足够的内存使用指针传递索引i确保递归调用间共享同一个计数器前序遍历顺序先访问根节点再递归遍历左子树最后递归遍历右子树8.4 迭代解法int* preorderTraversal(struct TreeNode* root, int* returnSize) { if (root NULL) { *returnSize 0; return NULL; } struct TreeNode* stack[100]; int top -1; int* result (int*)malloc(sizeof(int) * 100); int count 0; stack[top] root; while (top 0) { struct TreeNode* node stack[top--]; result[count] node-val; if (node-right) stack[top] node-right; if (node-left) stack[top] node-left; } *returnSize count; return result; }迭代解法使用栈来模拟递归过程注意右子节点要先入栈这样左子节点会先出栈被处理。9. 二叉树的中序遍历9.1 问题描述根据输入的字符串构建二叉树并输出其中序遍历结果。输入字符串中#表示空节点。牛客网题目链接 二叉树遍历9.2 解法实现#include stdio.h #include stdlib.h typedef char BTDataType; typedef struct BinaryTreeNode { BTDataType data; struct BinaryTreeNode* left; struct BinaryTreeNode* right; } BTNode; void InOrder(BTNode* root) { if (root NULL) return; InOrder(root-left); printf(%c , root-data); InOrder(root-right); } BTNode* CreateTree(char* n, int* i) { if (n[*i] #) { (*i); return NULL; } BTNode* root (BTNode*)malloc(sizeof(BTNode)); root-data n[(*i)]; root-left CreateTree(n, i); root-right CreateTree(n, i); return root; } int main() { char n[100]; scanf(%s, n); int i 0; BTNode* root CreateTree(n, i); InOrder(root); return 0; }9.3 代码解析CreateTree函数递归构建二叉树遇到#表示空节点返回NULL否则创建新节点并递归构建左右子树InOrder函数递归实现中序遍历左-根-右关键点使用指针传递索引i确保递归调用间共享同一个字符串位置9.4 输入输出示例输入字符串ABC##DE#G##F###表示的二叉树结构A / \ B F / \ C D / \ E G中序遍历输出C B E G D A F10. 二叉树算法总结与技巧10.1 递归解题模板大多数二叉树问题都可以套用以下递归模板ReturnType traversal(TreeNode* root) { // 1. 处理空节点情况 if (root NULL) { return ...; } // 2. 处理当前节点根据遍历顺序调整位置 // 前序遍历在这里处理 // 中序遍历在左子树递归后处理 // 后序遍历在右子树递归后处理 // 3. 递归处理左子树 LeftResult traversal(root-left); // 4. 递归处理右子树 RightResult traversal(root-right); // 5. 合并结果并返回 return CombineResults(...); }10.2 常见错误与调试技巧递归终止条件不完整确保考虑了所有可能的NULL指针情况递归返回值处理不当确保所有路径都有返回值正确处理递归调用的返回值指针操作错误特别注意指针传递和引用传递的区别修改指针指向时要确保内存管理正确调试技巧画小规模的树示例手动模拟递归过程添加打印语句跟踪递归调用和返回值使用调试器观察调用栈和变量变化10.3 性能优化建议对于重复计算的问题考虑使用记忆化技术对于深度较大的树考虑使用迭代代替递归避免栈溢出合理选择遍历顺序有时可以提前终止不必要的递归对于大规模数据考虑非递归的迭代解法在实际工程中二叉树算法的选择需要根据具体场景和数据特点进行权衡。理解这些基础算法和思想是解决更复杂树结构问题的基础。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →