0基础学嵌入式--数据结构--二叉树完全指南,一篇文章彻底搞懂
二叉树完全指南:从基础概念到实战遍历,一篇文章彻底搞懂
为什么二叉树是面试必考点?
如果你正在准备技术面试,那么二叉树绝对是你绕不开的坎。据统计,LeetCode上超过30%的题目与树相关,而其中大部分都是二叉树。今天,我们就用一篇文章,彻底讲透二叉树的所有核心知识点。
一、树的基本概念:理解“一对多”的数据关系
1.1 什么是树?
树是一种非线性数据结构,它描述的是一对多的关系。与线性结构(数组、链表)不同,树形结构中的每个元素可能有多个后继。
现实中的树类比:
公司CEO(根节点)
│
┌────┴────┐
销售总监 技术总监(分支节点)
│ │
销售经理 开发经理
│ │
销售员1 程序员1(叶子节点)
1.2 树的节点类型(必须掌握的三种节点)
1. 根节点 (Root Node)
// 特点:没有前驱,只有后继
// 类比:公司的CEO,没有上级,只有下级
struct TreeNode {
int data; // 节点数据
struct TreeNode* left; // 左子节点
struct TreeNode* right; // 右子节点
};
2. 分支节点 (Branch Node)
// 特点:既有前驱又有后继
// 类比:部门经理,有上级(总监)和下级(员工)
// 注意:树中分支节点最多有一个前驱,但可以有多个后继
// 二叉树中:分支节点恰好有0个或2个子节点
3. 叶子节点 (Leaf Node)
// 特点:只有前驱,没有后继
// 类比:基层员工,只有上级,没有下级
// 在二叉树中:叶子节点的 left 和 right 指针都为 NULL
1.3 重要术语解析
前驱 vs 后继
-
前驱(祖先):节点的直接或间接上级
-
后继(子孙):节点的直接或间接下级
度 (Degree)
// 度的计算
int getNodeDegree(TreeNode* node) {
int degree = 0;
if (node->left != NULL) degree++;
if (node->right != NULL) degree++;
return degree;
}
-
入度:指向该节点的边数(树中通常为1)
-
出度:从该节点出发的边数
层、深度、高度
// 计算节点深度(从根节点到该节点的边数)
int getDepth(TreeNode* root, TreeNode* target) {
if (root == NULL) return -1;
if (root == target) return 0;
int leftDepth = getDepth(root->left, target);
if (leftDepth != -1) return leftDepth + 1;
int rightDepth = getDepth(root->right, target);
if (rightDepth != -1) return rightDepth + 1;
return -1;
}
// 计算节点高度(从该节点到最远叶子节点的边数)
int getHeight(TreeNode* node) {
if (node == NULL) return -1;
int leftHeight = getHeight(node->left);
int rightHeight = getHeight(node->right);
return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1;
}
重要结论:
树的高度 = 树的深度 = 树的层数
(注意:根节点的深度为0,高度为树的高度)
二、二叉树:每个节点最多有两个孩子
2.1 二叉树的定义
// 二叉树的节点定义
typedef struct BinaryTreeNode {
int data;
struct BinaryTreeNode* left; // 左孩子
struct BinaryTreeNode* right; // 右孩子
} BTNode;
二叉树的特性:
-
每个节点最多有两个子节点
-
子节点有左右之分,次序不能颠倒
-
即使某个节点只有一个子节点,也要区分它是左孩子还是右孩子
2.2 特殊二叉树类型
1. 满二叉树 (Full Binary Tree)
// 满二叉树:所有叶子节点都在同一层
// 特点:每个节点要么是叶子,要么有两个子节点
bool isFullBinaryTree(BTNode* root) {
if (root == NULL) return true;
// 如果是叶子节点
if (root->left == NULL && root->right == NULL) return true;
// 如果有两个子节点
if (root->left != NULL && root->right != NULL) {
return isFullBinaryTree(root->left) && isFullBinaryTree(root->right);
}
return false; // 只有一个子节点
}
2. 完全二叉树 (Complete Binary Tree)
// 完全二叉树:按层编号后,编号是连续的
// 应用:堆(Heap)就是完全二叉树
bool isCompleteBinaryTree(BTNode* root) {
if (root == NULL) return true;
Queue* queue = createQueue();
enqueue(queue, root);
bool end = false; // 标记是否遇到了第一个不完全的节点
while (!isEmpty(queue)) {
BTNode* current = dequeue(queue);
if (current == NULL) {
end = true;
} else {
if (end) return false; // 前面有空节点,但当前不是空节点
enqueue(queue, current->left);
enqueue(queue, current->right);
}
}
return true;
}
2.3 二叉树的重要特性(必背公式)
特性1:第k层最多节点数
第k层最多有:2^(k-1) 个节点
推导过程:
-
第1层:根节点,1 = 2^(1-1) = 2^0 = 1
-
第2层:最多2个 = 2^(2-1) = 2^1 = 2
-
第3层:最多4个 = 2^(3-1) = 2^2 = 4
-
...
-
第k层:最多2^(k-1)个
特性2:前k层最多节点数
前k层最多有:2^k - 1 个节点
推导过程(等比数列求和):
S = 2^0 + 2^1 + 2^2 + ... + 2^(k-1)
= 1 * (1 - 2^k) / (1 - 2)
= 2^k - 1
代码验证:
// 计算二叉树的实际节点数
int countNodes(BTNode* root) {
if (root == NULL) return 0;
return 1 + countNodes(root->left) + countNodes(root->right);
}
// 计算满二叉树的节点数(已知高度h)
int countNodesFullBinaryTree(int height) {
return (1 << height) - 1; // 2^height - 1
}
三、二叉树的遍历:四种方式深度解析
3.1 深度优先遍历 (DFS)
深度优先遍历有三种顺序,区别在于访问根节点的时机。
1. 前序遍历 (Preorder Traversal)
顺序:根节点 → 左子树 → 右子树
void preorderTraversal(BTNode* root) {
if (root == NULL) return;
printf("%d ", root->data); // 1. 访问根节点
preorderTraversal(root->left); // 2. 遍历左子树
preorderTraversal(root->right); // 3. 遍历右子树
}
应用场景:复制二叉树、获取前缀表达式
2. 中序遍历 (Inorder Traversal)
顺序:左子树 → 根节点 → 右子树
void inorderTraversal(BTNode* root) {
if (root == NULL) return;
inorderTraversal(root->left); // 1. 遍历左子树
printf("%d ", root->data); // 2. 访问根节点
inorderTraversal(root->right); // 3. 遍历右子树
}
重要特性:对二叉搜索树中序遍历,结果是升序序列
3. 后序遍历 (Postorder Traversal)
顺序:左子树 → 右子树 → 根节点
void postorderTraversal(BTNode* root) {
if (root == NULL) return;
postorderTraversal(root->left); // 1. 遍历左子树
postorderTraversal(root->right); // 2. 遍历右子树
printf("%d ", root->data); // 3. 访问根节点
}
应用场景:删除二叉树、计算表达式树的值
3.2 广度优先遍历 (BFS) / 层序遍历
层序遍历:按层从上到下、从左到右访问节点
void levelOrderTraversal(BTNode* root) {
if (root == NULL) return;
Queue* queue = createQueue();
enqueue(queue, root);
while (!isEmpty(queue)) {
int levelSize = queue->size; // 当前层的节点数
for (int i = 0; i < levelSize; i++) {
BTNode* current = dequeue(queue);
printf("%d ", current->data);
if (current->left != NULL) {
enqueue(queue, current->left);
}
if (current->right != NULL) {
enqueue(queue, current->right);
}
}
printf("
"); // 换行表示新的一层
}
}
应用场景:找最短路径、按层处理数据
四、遍历方式对比与记忆技巧
4.1 遍历顺序记忆口诀
前序遍历:根 → 左 → 右
中序遍历:左 → 根 → 右
后序遍历:左 → 右 → 根
记忆技巧:
-
"前"序遍历:根在前面
-
"中"序遍历:根在中间
-
"后"序遍历:根在后面
4.2 四种遍历对比表
| 遍历方式 | 访问顺序 | 时间复杂度 | 空间复杂度 | 典型应用 |
|---|---|---|---|---|
| 前序遍历 | 根左右 | O(n) | O(h) | 复制树、前缀表达式 |
| 中序遍历 | 左根右 | O(n) | O(h) | 二叉搜索树排序 |
| 后序遍历 | 左右根 | O(n) | O(h) | 删除树、后缀表达式 |
| 层序遍历 | 按层访问 | O(n) | O(w) | 找最短路径、按层统计 |
注:
-
n: 节点总数
-
h: 树的高度
-
w: 树的最大宽度
五、二叉树遍历的非递归实现
5.1 前序遍历(非递归)
void preorderIterative(BTNode* root) {
if (root == NULL) return;
Stack* stack = createStack();
push(stack, root);
while (!isEmptyStack(stack)) {
BTNode* current = pop(stack);
printf("%d ", current->data);
// 注意:先右后左,因为栈是LIFO
if (current->right != NULL) {
push(stack, current->right);
}
if (current->left != NULL) {
push(stack, current->left);
}
}
}
5.2 中序遍历(非递归)
void inorderIterative(BTNode* root) {
Stack* stack = createStack();
BTNode* current = root;
while (current != NULL || !isEmptyStack(stack)) {
// 1. 遍历到最左节点
while (current != NULL) {
push(stack, current);
current = current->left;
}
// 2. 访问节点
current = pop(stack);
printf("%d ", current->data);
// 3. 处理右子树
current = current->right;
}
}
六、实战:二叉树常见面试题
6.1 求二叉树的最大深度
int maxDepth(BTNode* root) {
if (root == NULL) return 0;
int leftDepth = maxDepth(root->left);
int rightDepth = maxDepth(root->right);
return (leftDepth > rightDepth ? leftDepth : rightDepth) + 1;
}
6.2 判断两棵树是否相同
bool isSameTree(BTNode* p, BTNode* q) {
if (p == NULL && q == NULL) return true;
if (p == NULL || q == NULL) return false;
if (p->data != q->data) return false;
return isSameTree(p->left, q->left) &&
isSameTree(p->right, q->right);
}
6.3 二叉树的镜像(翻转)
BTNode* invertTree(BTNode* root) {
if (root == NULL) return NULL;
// 交换左右子树
BTNode* temp = root->left;
root->left = root->right;
root->right = temp;
// 递归处理子树
invertTree(root->left);
invertTree(root->right);
return root;
}
七、二叉树的应用场景
7.1 二叉搜索树 (BST)
-
特性:左子树所有节点 < 根节点 < 右子树所有节点
-
应用:快速查找、插入、删除(平均O(logn))
7.2 堆 (Heap)
-
完全二叉树
-
应用:优先队列、堆排序
7.3 哈夫曼树
-
应用:数据压缩(哈夫曼编码)
7.4 表达式树
-
应用:编译器的语法分析
八、学习建议与面试准备
8.1 学习路线
-
掌握基本概念(本文已涵盖)
-
实现基本操作(创建、遍历、查找)
-
解决经典问题(深度、镜像、对称等)
-
学习变种(BST、AVL、红黑树)
8.2 面试常见问题
-
实现二叉树的三种深度遍历
-
实现层序遍历
-
求二叉树的最大深度/最小深度
-
判断是否为平衡二叉树
-
最近公共祖先(LCA)
8.3 调试技巧
// 打印二叉树(可视化调试)
void printTree(BTNode* root, int space) {
if (root == NULL) return;
space += 5;
printTree(root->right, space);
printf("
");
for (int i = 5; i < space; i++) {
printf(" ");
}
printf("%d
", root->data);
printTree(root->left, space);
}
九、总结
二叉树是数据结构中的重要部分,掌握它需要:
-
理解基本概念:节点类型、度、深度、高度
-
掌握遍历方法:四种遍历方式及它们的应用场景
-
熟悉重要特性:层节点数公式、满/完全二叉树
-
大量练习:通过刷题巩固理解
记住:学习二叉树最好的方法就是动手实现。从最简单的遍历开始,逐步挑战更复杂的问题。
立即行动!
-
实现一个完整的二叉树结构
-
编写四种遍历方法的代码
-
在LeetCode上练习二叉树相关题目
-
在评论区分享你遇到的难题和解决方案
学习二叉树没有捷径,但正确的方法能让你的学习事半功倍!






