深入理解堆数据结构:从基础到应用

什么是堆

堆(Heap)是一种特殊的完全二叉树数据结构,它满足堆属性:对于最大堆,每个节点的值都大于或等于其子节点的值;对于最小堆,每个节点的值都小于或等于其子节点的值。
在这里插入图片描述
在这里插入图片描述

堆的基本特性

结构特性

  • 堆是一棵完全二叉树
  • 通常使用数组来实现,空间效率高
  • 对于位置i的节点:父节点位置:floor((i-1)/2),左子节点位置:2i + 1,右子节点位置:2i + 2

类型分类

  • 最大堆:根节点是所有节点的最大值
  • 最小堆:根节点是所有节点的最小值

代码实现和细节

堆的数据结构定义

typedef int HPDataType;

typedef struct Heap {
    HPDataType* _a;      // 存储堆元素的数组
    int _size;           // 当前堆中元素个数
    int _capacity;       // 堆的容量
} Heap;

初始化堆

void HeapInit(Heap* php) {
    php->_a = NULL;
    php->_capacity = php->_size = 0;
}

向上调整算法(AdjustUp)

void AdjustUp(HPDataType* arr, int child)
{
    int parent = (child - 1) / 2;
    while (child > 0)
    {
        if (arr[child] < arr[parent])  // 最小堆:子节点小于父节点时交换
        {
            Swap(&arr[child], &arr[parent]);
            child = parent;
            parent = (child - 1) / 2;
        }
        else {
            break;  // 堆属性已满足,退出循环
        }
    }
}

将新数据插⼊到数组的尾上,再进⾏向上调整算法,直到满⾜堆。

先将元素插⼊到堆的末尾,即最后⼀个孩⼦之后
插⼊之后如果堆的性质遭到破坏,将新插⼊结点顺着其双双亲往上调整到合适位置即可
时间复杂度:O(log n),因为最坏情况下需要从叶子节点调整到根节点(调整一个节点)。

在这里插入图片描述
那我们什么时候使用向上调整呢?
    如果从空堆开始逐步添加数据,使用插入操作(内部使用向上调整,每次 O(log N))。

向下调整算法(AdjustDown)

void AdjustDown(HPDataType* arr, int parent, int n) {
    int child = parent * 2 + 1; // 左孩子
    while (child < n) {
        // 找出左右孩子中较小的那个(最小堆)
        if (child + 1 < n && arr[child + 1] < arr[child]) {
            child++;
        }
        // 如果父节点大于较小的孩子,则交换
        if (arr[child] < arr[parent]) {
            Swap(&arr[child], &arr[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else {
            break;  // 堆属性已满足,退出循环
        }
    }
}

那我们什么时候使用向下调整算法呢?
    当删除堆顶元素时,我们需要将最后一个元素移到堆顶,然后向下调整。
    如果一开始就有所有数据,使用向下调整批量建堆:如堆排序
在这里插入图片描述

向下调整算发时间复杂度为 O(longn)(调整一个节点)

插入操作(HeapPush)

void HeapPush(Heap* php, HPDataType x) {
    assert(php);
    // 检查并扩容
    if (php->_size == php->_capacity)
    {
        int newCapacity = php->_capacity == 0 ? 4 : 2 * php->_capacity;
        HPDataType* tmp = (HPDataType*)realloc(php->_a, newCapacity * sizeof(HPDataType));
        if (tmp == NULL)
        {
            perror("realloc fail!");
            exit(1);
        }
        php->_a = tmp;
        php->_capacity = newCapacity;
    }
    
    // 插入元素到末尾
    php->_a[php->_size] = x;
    // 向上调整恢复堆属性
    AdjustUp(php->_a, php->_size);
    ++php->_size;
}

删除操作(HeapPop)

void HeapPop(Heap* php) {
    assert(!HPEmpty(php));
    // 将堆顶元素与最后一个元素交换
    Swap(&php->_a[0], &php->_a[php->_size - 1]);
    --php->_size;
    // 对新的堆顶元素进行向下调整
    AdjustDown(php->_a, 0, php->_size);
}

其他辅助操作

// 获取堆顶元素
HPDataType HeapTop(Heap* php) {
    assert(!HPEmpty(php));
    return php->_a[0];
}

// 获取堆的大小
int HeapSize(Heap* php) {
    assert(php);
    return php->_size;
}

// 检查堆是否为空
bool HPEmpty(Heap* php) {
    assert(php);
    return php->_size == 0;
}

// 销毁堆
void HeapDestroy(Heap* php) {
    assert(php);
    free(php->_a);
    php->_a = NULL;
    php->_capacity = php->_size = 0;
}

堆的应用示例

堆排序

void HeapSort(int* arr, int n) {
    // 建堆:从最后一个非叶子节点开始向下调整
    for (int i = (n-2)/2; i >= 0; i--) {
        AdjustDown(arr, i, n);
    }
    
    // 排序:不断取出堆顶元素
    for (int i = n-1; i > 0; i--) {
        Swap(&arr[0], &arr[i]);
        AdjustDown(arr, 0, i);
    }
}

总结

堆是一种高效的数据结构,特别适合需要频繁访问最大或最小元素的场景。通过向上调整和向下调整算法,堆能够在对数时间内完成插入和删除操作,同时保持堆属性的完整性。

本文实现的是最小堆,要改为最大堆只需要修改AdjustUp和AdjustDown中的比较条件即可。理解堆的原理和实现对于掌握更复杂的数据结构和算法具有重要意义。

在实际应用中,堆被广泛用于操作系统调度、网络路由算法、数据压缩等众多领域,是每个程序员都应该熟练掌握的基础数据结构之一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值