1. 二叉树基础概念与核心性质
二叉树是每个节点最多有两个子节点(左子节点和右子节点)的有序树形结构。这种数据结构在计算机科学中应用广泛,从文件系统到数据库索引,从编译器语法分析到机器学习决策树,都能看到它的身影。
1.1 二叉树的基本术语
让我们先明确一些关键术语的定义:
- 节点(Node):树的基本组成单元,包含数据和指向子节点的指针
- 根节点(Root):没有父节点的顶层节点,是整棵树的入口
- 叶子节点(Leaf):没有子节点的末端节点
- 内部节点:至少有一个子节点的非叶节点
- 度(Degree):节点拥有的子节点数量(二叉树中每个节点的度≤2)
- 层(Level):根节点位于第1层,其子节点为第2层,以此类推
- 深度(Depth):从根到该节点的边数(根节点的深度为0)
- 高度(Height):从该节点到最远叶子节点的边数(叶子节点的高度为0)
理解这些术语对于后续讨论二叉树的性质和操作至关重要。特别是深度和高度的区别,很多初学者容易混淆。记住:深度是从上往下数,高度是从下往上数。
1.2 二叉树的五种重要性质
二叉树有一些非常重要的数学性质,这些性质不仅是理论分析的基础,也在实际应用中发挥着关键作用。
性质1:第i层最多有2^(i-1)个节点
这个性质可以通过归纳法证明:
- 第1层(根节点)最多有1=2^0个节点
- 假设第k层最多有2^(k-1)个节点
- 那么第k+1层最多有2×2^(k-1)=2^k个节点
这个性质在计算二叉树的最大容量时非常有用。
性质2:高度为h的二叉树最多有2^(h+1)-1个节点
这是对前h+1层节点数的求和(等比数列求和公式):
总和 = 2^0 + 2^1 + ... + 2^h = 2^(h+1) - 1
性质3:叶子节点数n₀与度为2的节点数n₂的关系
对于任意二叉树,都有:
n₀ = n₂ + 1
这个性质的证明可以从边的角度考虑:
- 总边数 = n₁ + 2n₂(度为1的节点贡献1条边,度为2的贡献2条)
- 除根节点外,每个节点都有1条来自父节点的边,所以总边数 = n - 1
- 联立两个等式可得n₀ = n₂ + 1
性质4:含n个节点的完全二叉树高度为⌊log₂n⌋
完全二叉树的高度计算在堆等数据结构中非常重要。这个性质可以通过不等式推导得出:
2^h ≤ n < 2^(h+1) ⇒ h = ⌊log₂n⌋
性质5:完全二叉树的父子节点编号关系
对于按层序从1开始编号的完全二叉树:
- 节点i的父节点编号为⌊i/2⌋
- 左子节点编号为2i(如果存在)
- 右子节点编号为2i+1(如果存在)
- 叶子节点的编号满足i > ⌊n/2⌋
这个性质是数组实现堆的基础。
1.3 四种特殊二叉树
在实际应用中,我们会遇到几种特殊的二叉树形式:
满二叉树(Full Binary Tree)
每个内部节点都有恰好两个子节点,所有叶子节点都在同一层。满二叉树的节点总数与高度的关系为:节点数=2^(h+1)-1。
完全二叉树(Complete Binary Tree)
除最后一层外,其他层都完全填满,且最后一层的节点都集中在左侧。完全二叉树可以用数组紧凑存储,是堆实现的基础。
完美二叉树(Perfect Binary Tree)
所有内部节点都有两个子节点,且所有叶子节点都在同一层。完美二叉树是满二叉树的特例。
退化二叉树(Degenerate Binary Tree)
每个内部节点只有一个子节点,实际上退化为链表结构。这是BST在最坏情况下的形态。
1.4 二叉树的四种遍历方式
遍历是二叉树最基本的操作之一,主要有四种方式:
- 前序遍历(Pre-order):根→左→右
- 中序遍历(In-order):左→根→右
- 后序遍历(Post-order):左→右→根
- 层序遍历(Level-order):按层次从上到下,每层从左到右
每种遍历方式都有其特定的应用场景:
- 前序遍历常用于树的序列化
- 中序遍历对BST会得到有序序列
- 后序遍历常用于计算子树信息
- 层序遍历常用于广度优先搜索
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 二叉搜索树(BST)详解
二叉搜索树(Binary Search Tree, BST)是一种特殊的二叉树,它满足以下性质:
对于树中的任意节点:
- 左子树中所有节点的值都小于该节点的值
- 右子树中所有节点的值都大于该节点的值
- 左右子树也都是BST
2.1 BST的核心操作
查找操作
BST的查找操作类似于二分查找:
cpp复制bool search(Node* node, int key) {
if (!node) return false;
if (key == node->val) return true;
return key < node->val ? search(node->left, key)
: search(node->right, key);
}
时间复杂度:最好O(log n),最坏O(n)
插入操作
插入操作需要保持BST的性质:
cpp复制Node* insert(Node* node, int key) {
if (!node) return new Node(key);
if (key < node->val)
node->left = insert(node->left, key);
else if (key > node->val)
node->right = insert(node->right, key);
return node;
}
删除操作
删除操作较为复杂,需要考虑三种情况:
- 删除叶子节点:直接删除
- 删除只有一个子节点的节点:用子节点替代
- 删除有两个子节点的节点:用后继节点替代
cpp复制Node* deleteNode(Node* root, int key) {
if (!root) return nullptr;
if (key < root->val
