【数据结构与算法】第19篇:树与二叉树的基础概念

一、什么是树

1.1 树的定义

树是 n(n ≥ 0)个节点的有限集合。当 n = 0 时称为空树。任意非空树满足:

  • 有且仅有一个根节点

  • 其余节点可分为 m 个互不相交的子树

现实中的例子:文件系统、公司组织架构、网页DOM树。

1.2 树的术语

画一棵树来理解:

text

复制代码
         A          ← 根节点
       / | \
      B  C  D       ← 子树
     / \    |
    E   F   G       ← 叶子节点
术语 说明 示例
根节点 树的最顶层节点 A
父节点 直接上层节点 A是B、C、D的父节点
子节点 直接下层节点 B、C、D是A的子节点
兄弟节点 同一父节点的节点 B、C、D互为兄弟
叶子节点 没有子节点的节点 E、F、G
度 节点拥有的子节点个数 A的度是3,B的度是2
树的度 所有节点度的最大值 树的度是3
深度 从根到该节点的层数(根深度为1) B的深度是2
高度 从该节点到最远叶子的距离 A的高度是3

二、二叉树

2.1 二叉树的定义

二叉树是每个节点最多有两个子树的树结构,子树分为左子树和右子树,左右有序。

c

复制代码
typedef struct TreeNode {
    int data;
    struct TreeNode *left;
    struct TreeNode *right;
} TreeNode;

2.2 二叉树的特点

  1. 每个节点最多有两棵子树

  2. 左右子树有顺序,不能颠倒

  3. 二叉树可以是空树

2.3 特殊的二叉树

满二叉树:所有分支节点都有左右子树,且所有叶子节点都在同一层。

text

复制代码
      1
     / \
    2   3
   / \ / \
  4  5 6  7

完全二叉树:除了最后一层,其他层都是满的,且最后一层的叶子节点都靠左排列。

text

复制代码
      1
     / \
    2   3
   / \  /
  4  5 6

2.4 二叉树的性质

性质 内容
性质1 第 i 层最多有 2^(i-1) 个节点
性质2 深度为 k 的二叉树最多有 2^k - 1 个节点
性质3 叶子节点数 = 度为2的节点数 + 1
性质4 n 个节点的完全二叉树深度为 ⌊log₂n⌋ + 1

性质3的推导:

  • 设 n0 为叶子节点数,n1 为度为1的节点数,n2 为度为2的节点数

  • 总节点数:n = n0 + n1 + n2

  • 总边数:n - 1 = n1 + 2n2

  • 相减得:n0 = n2 + 1


三、二叉树的顺序存储

3.1 存储方式

用数组存储二叉树,按完全二叉树的编号规则,将节点放入数组对应位置。

编号规则(根节点编号为1):

  • 编号 i 的左孩子:2i

  • 编号 i 的右孩子:2i + 1

  • 编号 i 的父节点:⌊i/2⌋

text

复制代码
        1(0)
       /    \
    2(1)    3(2)
   /   \    /
 4(3) 5(4) 6(5)

数组存储:[0, 1, 2, 3, 4, 5, 6]
下标:    0  1  2  3  4  5  6

3.2 顺序存储的优缺点

优点:

  • 随机访问,通过下标快速定位节点

  • 适合满二叉树和完全二叉树

缺点:

  • 空间浪费:非完全二叉树会留下大量空位

  • 插入删除不方便

3.3 代码实现

c

复制代码
#include <stdio.h>
#include <stdlib.h>

#define MAX_SIZE 100

typedef struct {
    int data[MAX_SIZE];
    int size;  // 实际节点个数
} SeqBinaryTree;

// 初始化
void initTree(SeqBinaryTree *tree) {
    for (int i = 0; i < MAX_SIZE; i++) {
        tree->data[i] = 0;  // 0表示空节点
    }
    tree->size = 0;
}

// 设置根节点
void setRoot(SeqBinaryTree *tree, int value) {
    tree->data[1] = value;
    tree->size = 1;
}

// 设置左孩子(父节点索引为p,从1开始)
void setLeft(SeqBinaryTree *tree, int parent, int value) {
    int idx = parent * 2;
    if (idx >= MAX_SIZE) {
        printf("索引越界\n");
        return;
    }
    tree->data[idx] = value;
    if (idx > tree->size) tree->size = idx;
}

// 设置右孩子
void setRight(SeqBinaryTree *tree, int parent, int value) {
    int idx = parent * 2 + 1;
    if (idx >= MAX_SIZE) {
        printf("索引越界\n");
        return;
    }
    tree->data[idx] = value;
    if (idx > tree->size) tree->size = idx;
}

// 获取左孩子
int getLeft(SeqBinaryTree *tree, int parent) {
    int idx = parent * 2;
    if (idx > tree->size) return 0;
    return tree->data[idx];
}

// 获取右孩子
int getRight(SeqBinaryTree *tree, int parent) {
    int idx = parent * 2 + 1;
    if (idx > tree->size) return 0;
    return tree->data[idx];
}

// 获取父节点
int getParent(SeqBinaryTree *tree, int child) {
    if (child <= 1) return 0;
    int idx = child / 2;
    return tree->data[idx];
}

// 打印树(按层)
void printTree(SeqBinaryTree *tree) {
    printf("二叉树数组存储(下标从1开始):\n");
    for (int i = 1; i <= tree->size; i++) {
        if (tree->data[i] != 0) {
            printf("data[%d]=%d", i, tree->data[i]);
            // 打印父节点信息
            if (i > 1) {
                printf(" (父节点: %d)", getParent(tree, i));
            }
            printf("\n");
        }
    }
}

int main() {
    SeqBinaryTree tree;
    initTree(&tree);
    
    // 构建一个完全二叉树
    setRoot(&tree, 1);
    setLeft(&tree, 1, 2);
    setRight(&tree, 1, 3);
    setLeft(&tree, 2, 4);
    setRight(&tree, 2, 5);
    setLeft(&tree, 3, 6);
    
    printTree(&tree);
    
    printf("\n节点2的左孩子: %d\n", getLeft(&tree, 2));
    printf("节点3的右孩子: %d\n", getRight(&tree, 3));
    printf("节点6的父节点: %d\n", getParent(&tree, 6));
    
    return 0;
}

运行结果:

text

复制代码
二叉树数组存储(下标从1开始):
data[1]=1
data[2]=2 (父节点: 1)
data[3]=3 (父节点: 1)
data[4]=4 (父节点: 2)
data[5]=5 (父节点: 2)
data[6]=6 (父节点: 3)

节点2的左孩子: 4
节点3的右孩子: 0
节点6的父节点: 3

四、满二叉树与完全二叉树的性质

4.1 满二叉树

性质 说明
节点数 深度为k,节点数 = 2^k - 1
叶子节点 都在最后一层,叶子数 = 2^(k-1)
度为1的节点 0个

4.2 完全二叉树

性质 说明
节点数 深度为k,节点数在 2\^(k-1), 2\^k-1 之间
叶子节点 只能在最后两层
顺序存储 适合用数组存储,没有空间浪费

完全二叉树的顺序存储特点:

  • 按层序遍历的顺序存入数组

  • 下标 i 的节点,左孩子下标 2i,右孩子下标 2i+1

  • 如果 2i > n,则没有左孩子

  • 如果 2i+1 > n,则没有右孩子


五、二叉树与树的区别

对比项 树 二叉树
节点度数 无限制 最多2
子树顺序 无序 有序(左右区分)
空树 通常不允许 允许

六、小结

这一篇我们学习了树和二叉树的基础概念:

要点 说明
树术语 根、叶子、度、深度、高度
二叉树 每个节点最多两个子节点,左右有序
满二叉树 所有层满,节点数 2^k-1
完全二叉树 最后一层靠左排列,适合数组存储
顺序存储 用数组下标模拟父子关系:2i, 2i+1

核心公式:

  • 第i层最多节点数:2^(i-1)

  • 深度k的二叉树最多节点数:2^k - 1

  • 叶子节点数 = 度为2的节点数 + 1

下一篇我们讲二叉树的链式存储和四种遍历方式。


七、思考题

  1. 一棵完全二叉树有100个节点,它的叶子节点有多少个?

  2. 一棵二叉树的叶子节点数为20,度为2的节点数是多少?

  3. 顺序存储中,如何判断一个节点是否有左孩子?如何判断是否有右孩子?

  4. 为什么完全二叉树适合用数组存储,而普通二叉树不适合?

欢迎在评论区讨论你的答案。

相关推荐
第九序言T7 小时前
Python 数据处理进阶(十八)批量处理同结构文件:一个文件夹一次跑完
开发语言·python·自动化·pandas·数据处理
m0_639906947 小时前
Redis入门|五种数据结构、使用场景、持久化RDB&AOF
数据结构·数据库·redis
Sakura_j_h_7 小时前
EasyEDA PCB 快速设计Skills
c++·python·github
ZhengEnCi8 小时前
Agent 架构怎么选:从单 Agent 到多 Agent 编排的选型决策指南
算法·架构
龙亘川8 小时前
构建城市公共安全数字底座:亘川智城智慧安防系统全维度解读
java·大数据·开发语言·人工智能·信息可视化
程序喵大人8 小时前
【C++入门】内存模型与资源管理 - 09 shared_ptr 和 weak_ptr 解决共享,也带来代价
开发语言·c++·资源管理·智能指针·内存模型
秋天的怀念_David8 小时前
第二十二夜 · 数字迷雾
java·程序人生·算法
Raas1009 小时前
AI网关部署方案?从部署到上线全流程
java·开发语言·人工智能·gateway·ai网关·企业级ai网关·企业级ai产品
Pniubi9 小时前
力扣283移动零(双指针法)
算法·leetcode·职场和发展
hhb_6189 小时前
大模型落地三重优化实战
人工智能·算法·机器学习