Heaps | 优先队列

本文目录

一、优先队列的基本概念

 

二、优先队列的实现原理

三、优先队列的代码实现

 1、优先队列的结构

2、初始化优先队列

3、插入元素

4、删除最小元素

End


一、优先队列的基本概念

对于可以实现以下两种操作的数据结构:

1、Insert:插入新元素

2、DeleteMin:(快速)找到、返回并删除一个元素

我们称之为优先队列(Priority Queues),也可以说是堆的一种。

 

二、优先队列的实现原理

我们一般通过二叉堆(Binary Heaps)来实现优先队列。

  • 堆是一颗完全二叉树,通过数组实现。
  • 堆重要的性质:父节点一定是其所有子孙节点的最值
  • 堆中任意一棵子树仍然是堆

根据我们的需要,我们可以用大根堆或者小根堆实现优先队列。

如果每棵子树的根节点都是最大值,那么它就是大根堆;每棵子树的根节点都是最小值,那么它就是小根堆

 注意,下面我们均以小根堆为例讲解。


我们理解堆,可以将它的外形理解为一棵完全二叉树,但是其数据本质均是存储在一个一维数组中的。存储的顺序是:对于完全二叉树,从1开始按照层次编号,和数组的下标一一对应。2

下图是一个堆的实现模型:

▲需要注意是:数组的储存从1开始,0的位置留作哨兵元素,具体作用后面再说。


那么这样编写堆,又如何实现二叉树的特点,从父节点迅速找到两个儿子节点呢?

我们很容易找到父子对应数组下标的数学关系:

对于一个下标为 i 的节点,它的父亲下标为 i / 2(整数部分),左儿子下标为 2*i,右儿子下标为 2*i + 1:

三、优先队列的代码实现

 1、优先队列的结构

typedef struct HeapStruct{
    int Capacity;  //容积
    int Size;  //当前大小
    int *Elements;  //数据域,数组指针
} *PRIORITY_HEAP, priority_heap;

2、初始化优先队列

/* 传入参数:所需优先队列的容积
 * 返回:初始化好的堆 */
PRIORITY_HEAP Initialize(int MaxElements) {
    PRIORITY_HEAP H = (PRIORITY_HEAP)malloc(sizeof(priority_heap));
    H->Elements = (int*)malloc((MaxElements + 1) * sizeof(int));
    H->Capacity = MaxElements;
    H->Size = 0;
    H->Elements[0] = MinData;   //设置哨兵元素,必须是堆中的最小值

    return H;
}

3、插入元素

可以说,堆的建立也是在插入的基础上的。和二叉查找树一样,堆的最终模样和我们输入的数据相关,就算是一样的数据,每个数的输入先后顺序不同,最终也很有可能是不一样的优先队列,当然啦,功能还是不变的。

堆的插入算法并不难实现:

🔺 先在最后为新元素开辟一个位置

🔺 该位置和其父节点 P 比较

              如果比 P 中元素小,则父子交换位置

                   再循环与父亲比较

              如果不比 P 中元素小,存入当前位置,插入完成

 

第二个🔺循环过程,我们称为上滤(percolat up)

同时为了防止死循环,我们将H->Elements[0]设置为了哨兵元素,小于所有元素。


如下图展示了插入新元素 14 的完整过程:

 具体实现代码如下:

/* 传入参数:
 * 待插入元素x
 * 插入的优先队列 H */
void Insert(int x, PRIORITY_HEAP H) {
    int i;
    
    if(H->Size >= H->Capacity)  //没有空间插入了,不插入直接返回
        return;
    
    for(i = ++H->Size; x < H->Elements[i / 2]; i = i / 2) {
        H->Elements[i] = H->Elements[i / 2];  //父节点下移
    }
    H->Elements[i] = x;   //将元素插入适当位置
}

4、删除最小元素

对于优先队列来说,我们可以很快的找到且删除其最小元素,因为最小元素永远在堆顶,即存储在H->Elements[0]的位置。

堆的删除堆顶元素的算法比插入复杂一点:

(先记堆顶节点为空缺节点E

🔺 先记录堆中最后的元素x,然后删去最后的位置

🔺 x 和 E 的左右孩子比较

              如果比两个孩子都小,将 x 存入E中即可

              ▲ 否则,将更小孩子存入E

                   E改为该孩子原先所在节点处,继续循环 

 

 第二个🔺循环过程,我们称为下滤(percolat down)。  


如下图展示了删除最小元素 13 的完整过程:

 具体实现代码如下:

/* 传入参数:
 * 待删除堆顶的优先队列 H
 * 返回:
 * 删除的元素 */
int DeleteMin(PRIORITY_HEAP H) {

    if(!H->Size)
        return H->Elements[0];  //对空的优先队列执行删除操作,返回哨兵元素

    int LastElement = H->Elements[H->Size --];
    int MinElement = H->Elements[1];

    int i;  //空缺节点下标
    int child;  //更小的儿子节点下标

    for(i = 1; ; i = child) {
        child = 2 * i;  //暂存左儿子
        if(child != H->Size && H->Elements[child] > H->Elements[child + 1] )  //右儿子存在且更小
            child++;  //存入右儿子
            
        if(LastElement > H->Elements[child]) 
            H->Elements[i] = H->Elements[child]; //孩子上移
        else
            break;
    }
    H->Elements[i] = LastElement; //插入空缺节点
    return MinElement;
}

 



End

欢迎关注个人公众号“鸡翅编程”,这里是认真且乖巧的码农一枚,旨在用心写好每一篇文章,平常会把笔记汇总成推送更新~

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值