【数据结构与算法】第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. 为什么完全二叉树适合用数组存储,而普通二叉树不适合?

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

相关推荐
副露のmagic2 小时前
哈希章节 leetcode 思路&实现
算法·leetcode·哈希算法
csuzhucong2 小时前
puzzle(1037)黑白、黑白棋局
算法
XiYang-DING2 小时前
【LeetCode】链表 + 快慢指针找中间 | 2095. 删除链表的中间节点
算法·leetcode·链表
Zarek枫煜2 小时前
[特殊字符] C3语言:传承C之高效,突破C之局限
c语言·开发语言·c++·单片机·嵌入式硬件·物联网·算法
寻寻觅觅☆3 小时前
东华OJ-基础题-30-求最晚和最早日期(C++)
数据结构·c++·算法
是Smoky呢3 小时前
springAI+向量数据库+RAG入门案例
java·开发语言·ai编程
羊小蜜.3 小时前
Mysql 03: 连接查询全解——内连接、外连接与复合条件查询
数据库·mysql·算法·连接查询
_Twink1e3 小时前
[算法竞赛]九、C++标准模板库STL常用容器大全
开发语言·c++
vivo互联网技术3 小时前
CVPR 2026 | C²FG:用分数差异分析提高条件生成中CFG的引导
人工智能·算法·aigc