编程基础--数据结构

文章目录

一、 数据结构中的「堆」:核心概念与核心特性

数据结构中的「堆(Heap)」是一种基于完全二叉树的特殊树形结构,核心特征是「结构上满足完全二叉树,顺序上满足堆序性」------需注意和编程中「内存堆区」(动态内存分配区域)区分,二者无直接关联。

堆是实现「优先队列」「堆排序」的核心数据结构,广泛用于TopK、任务调度、中位数维护等场景。

一、堆的核心定义

堆是满足以下两个条件的完全二叉树

  1. 结构条件:必须是「完全二叉树」(层序遍历填满,最后一层节点从左到右连续排列,无空缺);
  2. 顺序条件(堆序性):任意父节点与子节点满足固定的大小关系(分两种类型)。

1. 堆的两种类型(按堆序性划分)

堆类型 核心规则 示例(数组存储) 可视化二叉树结构
大顶堆(最大堆) 任意父节点的值 ≥ 所有子节点的值 [9,7,8,5,3,4] 根节点9,左子7、右子8;7的子5/3,8的子4
小顶堆(最小堆) 任意父节点的值 ≤ 所有子节点的值 [2,5,3,7,6,4] 根节点2,左子5、右子3;5的子7/6,3的子4

⚠️ 关键:堆仅约束「父-子」关系,不约束兄弟节点的大小关系(比如大顶堆中7和8是兄弟,8>7仍符合规则)。

二、堆的存储结构(数组是最优选择)

堆本质是完全二叉树,而完全二叉树的节点可通过「数组下标」直接映射,无需额外存储左右子节点指针,空间效率极高。

数组与完全二叉树的下标映射规则

假设堆用数组arr存储,数组下标从0开始,对任意节点i

  • 父节点下标:parent(i) = (i - 1) // 2(整数除法,如i=5→parent=2);
  • 左孩子下标:left(i) = 2 * i + 1(如i=1→left=3);
  • 右孩子下标:right(i) = 2 * i + 2(如i=1→right=4)。
示例:大顶堆的数组映射

数组[9,7,8,5,3,4]对应的完全二叉树:

复制代码
        9 (i=0)
       /  \
 7(i=1)    8(i=2)
 /  \      /
5(3) 3(4) 4(5)

验证规则:

  • 节点8(i=2)的父节点:(2-1)//2=0 → 9(正确);
  • 节点7(i=1)的左孩子:21+1=3 → 5(正确),右孩子:21+2=4 →3(正确)。

三、堆的核心操作(基础+高频)

堆的操作围绕「维护堆序性」展开,核心是「上浮(sift up)」和「下沉(sift down)」两个基础动作:

  • 上浮:节点值违反堆序性(如小顶堆中子节点<父节点),向上交换至正确位置;
  • 下沉:节点值违反堆序性(如大顶堆中父节点<子节点),向下交换至正确位置。

1. 插入元素(上浮操作)

核心思路:先把新元素放到数组末尾(完全二叉树最后一个位置),再从下往上上浮,修复堆序性。

步骤(以大顶堆为例):
  1. 将新元素val添加到数组末尾;
  2. 计算新元素下标i = 数组长度-1
  3. 循环:
    • 计算父节点下标p = (i-1)//2
    • arr[i] > arr[p](大顶堆规则),交换arr[i]arr[p]
    • 更新i = p,直到i=0(根节点)或arr[i] ≤ arr[p]

2. 删除堆顶元素(下沉操作)

堆仅支持删除「根节点(堆顶)」,这是堆的核心特性(优先队列取最值)。
核心思路:用数组最后一个元素替换堆顶,再从上往下下沉,修复堆序性。

步骤(以大顶堆为例):
  1. 保存堆顶值(arr[0]),用于返回;
  2. 将数组最后一个元素移到堆顶(arr[0] = arr[len-1]),删除最后一个元素;
  3. 初始化当前节点下标i=0
  4. 循环:
    • 计算左孩子l=2i+1、右孩子r=2i+2
    • 找到arr[i]、arr[l]、arr[r]中的最大值下标max_idx
    • max_idx != i(当前节点不是最大值),交换arr[i]arr[max_idx]
    • 更新i = max_idx,直到i无孩子或arr[i] ≥ 子节点

3. 建堆(从无序数组生成堆)

核心思路:从最后一个非叶子节点开始,逐个向下下沉,避免从根节点开始的低效操作。

步骤(以大顶堆为例):
  1. 计算最后一个非叶子节点下标:last_non_leaf = (len(arr)-1 -1)//2 = len(arr)//2 -1
  2. last_non_leaf到0逆序遍历每个节点i
  3. 对每个i执行「下沉操作」,最终生成大顶堆。

四、堆的关键特性总结

特性 说明
结构特性 完全二叉树,可用数组高效存储,无需指针
顺序特性 仅约束父-子关系,兄弟节点无顺序要求
最值特性 大顶堆顶是最大值,小顶堆顶是最小值,取最值时间复杂度O(1)
操作复杂度 插入/删除堆顶:O(log n)(树高度为log₂n);建堆:O(n)(而非O(n log n))
稳定性 堆排序是不稳定排序(交换操作会破坏相等元素的相对位置)

五、堆的典型应用场景

  1. 堆排序:利用大顶堆/小顶堆实现O(n log n)的排序,原地排序(空间复杂度O(1));
  2. 优先队列:任务调度(高优先级任务先执行)、事件驱动系统(按时间戳处理事件);
  3. TopK问题:找数组中前K大/小的元素(用小顶堆存前K大元素,效率高于排序);
  4. 中位数维护:用大顶堆存左半部分(≤中位数),小顶堆存右半部分(≥中位数),快速取中位数;
  5. 多路归并:合并多个有序数组(如外部排序、数据库查询结果合并)。

示例:大顶堆的简单C语言表示

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

#define MAX_HEAP_SIZE 100
int heap[MAX_HEAP_SIZE];
int heap_size = 0;

// 交换两个数
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 上浮操作(插入时用)
void sift_up(int i) {
    while (i > 0) {
        int p = (i - 1) / 2; // 父节点下标
        if (heap[i] > heap[p]) { // 大顶堆:子>父则交换
            swap(&heap[i], &heap[p]);
            i = p;
        } else {
            break;
        }
    }
}

// 插入元素
void insert(int val) {
    if (heap_size >= MAX_HEAP_SIZE) return;
    heap[heap_size++] = val;
    sift_up(heap_size - 1);
}

int main() {
    // 插入元素构建大顶堆
    insert(5);
    insert(9);
    insert(7);
    insert(3);
    insert(8);
    
    // 输出堆数组:[9,8,7,3,5](大顶堆)
    for (int i = 0; i < heap_size; i++) {
        printf("%d ", heap[i]);
    }
    return 0;
}

总结

堆的核心是「完全二叉树结构 + 堆序性规则」,其价值在于快速获取最值 (O(1))和高效更新堆(O(log n)),是处理"最值优先"类问题的最优数据结构。理解堆的关键是掌握「上浮/下沉」操作,以及数组与完全二叉树的下标映射关系。

二、 数据结构中的「树」:核心概念与核心特性

树(Tree)是非线性层次化数据结构,核心特征是「一对多」的节点关系(区别于数组、链表等「一对一」的线性结构),通过「节点」和「边」组织数据,能高效表达层级、嵌套、分类关系(如文件系统、族谱、数据库索引)。

树是数据结构的核心基础,二叉树、堆、红黑树、B+树等都是其衍生结构,广泛应用于工程场景。

一、树的核心定义(形式化+通俗解释)

1. 形式化定义

树是 n(n≥0) 个节点的有限集合:

  • n=0:称为「空树」;
  • n>0
    ① 有且仅有一个根节点(Root) (无父节点的唯一节点);
    ② 其余 n-1 个节点分为 m(m≥0) 个互不相交的子集,每个子集本身是一棵独立的树(称为「子树」),且子树之间无连接。

2. 通俗理解

把树倒过来看(根在上,叶子在下):

  • 根节点是"树干顶端",唯一且无上级;
  • 子节点是"树枝分叉",每个节点的子节点数量称为「度」;
  • 叶子节点是"树枝末端的叶子",无下级节点;
  • 边是"连接树枝的部分",节点数 = 边数 + 1(核心特性)。
示例:简单树的结构
复制代码
        根节点(A)
       /   |   \
      B    C    D  ------ A的子节点(B/C/D互为兄弟节点)
     / \   |
    E   F  G      ------ B的子节点(E/F是叶子节点);C的子节点G

二、树的核心术语(必懂)

术语 定义(结合上面示例)
根节点 树的起始节点,无父节点(示例中A)
叶子节点 度为0的节点(无子女),示例中E、F、G、D
父节点/子节点 若节点X直接指向Y,则X是Y的父节点,Y是X的子节点(A是B的父,B是A的子)
兄弟节点 同一父节点的子节点(B、C、D互为兄弟;E、F互为兄弟)
节点的度 节点拥有的子节点个数(A的度=3,B的度=2,E的度=0)
树的度 树中所有节点的度的最大值(示例树的度=3)
层(深度) 根节点为第1层(或第0层,两种计数方式均常见),子节点为下一层(A=1层,B/C/D=2层,E/F/G=3层)
高度 从节点到叶子节点的最长路径的层数(A的高度=3,B的高度=2,E的高度=1)
路径 从一个节点到另一个节点的节点序列(A→B→E 是一条路径,长度=2,路径长度=边数)
祖先/后代 路径上的所有上层节点是祖先,下层节点是后代(A是E的祖先,E是A的后代)
森林 m(m≥0) 棵互不相交的树的集合(若删除根节点A,B、C、D各自的子树构成森林)

三、树的基本特性(核心规律)

  1. 节点与边的关系:任意树中,节点数 = 边数 + 1(每条边连接一个父节点和子节点,根节点无父边);
  2. 度的总和:所有节点的度之和 = 边数 = 节点数 - 1(每个边对应一个子节点,度的总和即子节点总数);
  3. 空树特性:空树无节点、无边,是树的特殊情况;
  4. 层级限制 :若树的高度为h(根为1层),则节点数最少为h(每层1个节点),最多为 2 h − 1 2^h - 1 2h−1(满二叉树的情况)。

四、树的常见分类(按结构/用途划分)

树的衍生结构均基于「二叉树」扩展,以下是工程中最常用的类型:

树类型 核心定义 关键特性 典型应用
二叉树 每个节点最多有2个子节点(左子树、右子树),子树有左右顺序(不能互换) 任意二叉树,第k层最多有 2 k − 1 2^{k-1} 2k−1 个节点;高度h的二叉树最多有 2 h − 1 2^h -1 2h−1 个节点 所有二叉衍生树的基础
满二叉树 二叉树中,所有层的节点都被填满(除叶子层外,每个节点都有2个子节点) 叶子节点全部在最后一层;节点数= 2 h − 1 2^h -1 2h−1(h为高度) 理论研究、完美二叉树场景
完全二叉树 二叉树中,最后一层节点从左到右连续排列(前h-1层是满二叉树) 可通过数组高效存储(无需指针);堆的底层结构 堆、优先队列
二叉搜索树(BST) 二叉树 + 有序性:左子树所有节点值 < 根节点值 < 右子树所有节点值 查找/插入/删除平均时间复杂度O(log n),最坏O(n)(退化为链表) 简单有序数据查询
平衡二叉树(AVL) BST + 平衡性:任意节点的左右子树高度差 ≤ 1 严格平衡,查询效率稳定O(log n);插入/删除需旋转调整 高频查询、低修改的场景
红黑树 BST + 红黑规则(节点标红/黑,确保最长路径≤2×最短路径) 近似平衡,插入/删除调整成本低;查询略慢于AVL,但修改更高效 Java TreeMap、Linux内核调度
B树/B+树 多路平衡查找树(节点可存多个关键字,有多个子树) B+树所有数据在叶子节点,叶子节点链式连接;B树数据分布在所有节点 数据库索引(MySQL)、文件系统
完全二叉树 + 堆序性(大顶堆:父≥子;小顶堆:父≤子) 取最值O(1),插入/删除O(log n);不稳定排序 堆排序、TopK、优先队列
Trie树(字典树) 按字符串字符分层的树,每个节点存一个字符,路径对应字符串 字符串前缀匹配效率极高(O(字符串长度)) 拼音输入法、路由表、关键词检索
哈夫曼树 带权路径长度最短的二叉树(权值大的节点离根近) 构建哈夫曼编码,无冗余压缩 数据压缩(JPG/PNG)、编码

五、树的存储方式

树的存储需表达「节点-子节点」的关系,常见两种方式:

1. 链式存储(通用,适合所有树)

每个节点包含「数据域 + 子节点指针域」:

  • 二叉树节点示例(C语言):

    c 复制代码
    typedef struct TreeNode {
        int data;               // 数据域
        struct TreeNode *left;  // 左子节点指针
        struct TreeNode *right; // 右子节点指针
    } TreeNode;
  • 多叉树节点示例(用指针数组存子节点):

    c 复制代码
    #define MAX_CHILD 3 // 树的度为3
    typedef struct MultiTreeNode {
        int data;
        struct MultiTreeNode *children[MAX_CHILD]; // 子节点指针数组
    } MultiTreeNode;

2. 顺序存储(仅适合完全二叉树/满二叉树)

利用数组下标映射节点关系(无需指针,空间高效),规则:

  • 根节点存于下标0(或1);
  • 若节点存于下标i(下标从0开始):
    • 左子节点:2*i + 1
    • 右子节点:2*i + 2
    • 父节点:(i - 1) / 2(整数除法)。
  • 示例:完全二叉树[A,B,C,D,E]存于数组arr[0]=A, arr[1]=B, arr[2]=C, arr[3]=D, arr[4]=E

六、树的典型应用场景

  1. 文件系统:根目录→子目录→文件,是树的经典层级结构;
  2. 数据库索引:MySQL用B+树作为索引结构,快速定位数据行;
  3. 编译器/解释器:语法分析生成「抽象语法树(AST)」,解析代码结构;
  4. 路由算法:网络路由表用Trie树存储IP地址,快速匹配路由规则;
  5. 人工智能:决策树用于分类/回归(如判断用户是否流失);
  6. 数据压缩:哈夫曼树生成哈夫曼编码,压缩文本/图片数据;
  7. 任务调度:堆(完全二叉树)实现优先队列,高优先级任务先执行。

总结

树的核心是「层次化、一对多」的节点关系,其价值在于:

  • 相比线性结构,能更高效地表达层级/嵌套数据(如文件系统);
  • 衍生结构(BST、红黑树、B+树)解决了"有序数据的快速查询/插入/删除"问题;
  • 堆、Trie树等专用树结构,针对性解决"最值优先""字符串匹配"等场景。

理解树的关键是掌握「节点、度、层、高度」等核心术语,以及二叉树的基本特性------所有复杂树结构均是二叉树的扩展或优化。

相关推荐
小毅&Nora1 小时前
【后端】【C++】泛型算法:从传统到C++20 Ranges的进化之旅
算法·c++20·泛函算法
mifengxing1 小时前
B树的定义以及插入和删除
数据结构·b树
ULTRA??1 小时前
最小生成树kruskal算法实现python,kotlin
人工智能·python·算法
sin_hielo1 小时前
leetcode 1523
数据结构·算法·leetcode
xu_yule1 小时前
数据结构(7)带头双向循环链表的实现
数据结构·链表
代码游侠1 小时前
复习——线性表
linux·c语言·数据结构·学习·算法
烛衔溟1 小时前
C语言图论:无向图基础
c语言·数据结构·图论·无向图
秋深枫叶红1 小时前
嵌入式第二十九篇——数据结构——树
数据结构·学习·算法·深度优先
能源系统预测和优化研究1 小时前
【原创代码改进】基于贝叶斯优化的PatchTST综合能源负荷多变量时间序列预测
算法·回归·transformer·能源