堆(优先队列)与堆排序通俗介绍(c++实现)

0 简介

堆是一种比较精妙的数据结构,一般所使用的二叉堆,其本质上来说,是一棵完全二叉树,所以一般使用一个数组就可以模拟堆的各种操作。对于任意一个节点来说,若它的节点编号为 i i i( 1 < = i < = n 1<=i <= n 1<=i<=n),那么它的左儿子节点编号就是 2 i 2i 2i,右儿子节点编号就是 2 i + 1 2i+1 2i+1,父亲节点就是 i / 2 i/2 i/2。最小堆中,每一个节点的值均小于父亲节点,大于它的儿子节点。堆一般支持插入元素、返回和删除最值元素等操作,下面以二叉最小堆为例讲讲这两个操作。

1 堆的操作

1.1 插入元素

在堆中插入一个元素很简单,对于一个新元素x,若当前堆的大小为size,首先将元素放入数组末端,h[++size] = x;,然后执行上滤操作即可。上滤的操作如下,也就是说如果它的父亲节点大于它的话已知向上交换元素即可。

void up(int u)
{
    
    
    while (u / 2 && h[u] < h[u / 2])
    {
    
    
        swap(h[u], h[u / 2]);
        u /= 2;
    }
}

1.2 删除最值元素

最小堆的第一个元素总是最小元素,所以删除最值元素的思路是这样的,令数组的第一个元素等于最后一个元素,若当前堆的大小为size,size–,然后对第一个元素进行下滤操作即可。所谓下滤操作,如果发现当前节点大于它的儿子节点,就将它和子节点交换,直到它达到正确的位置。代码如下所示。

void down(int u)
{
    
    
    int t = u;
    if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;
    if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
    if (u != t)
    {
    
    
        swap(h[u], h[t]);
        down(t);
    }
}

1.3 建堆

一种常见的建堆思路是不断将数组元素插入一个堆中,这样的操作最坏情况下算法复杂度将达到 n l o g n nlogn nlogn。有一种 o ( n ) o(n) o(n)的建堆算法如下所示,只需从 n / 2 n/2 n/2处开始,从后往前不断下滤即可。

for (int i = n / 2; i; i -- ) 
	down(i);

2 堆排序

根据前面所说的三种操作,我们就可以完成堆排序了。对于输入的数组,我们首先建堆,然后循环输出最小元素,删去最小元素即可。以acwing838题为例。题目要求如下:
在这里插入图片描述
实现代码如下:

#include <stdio.h>
#include <algorithm>

using namespace std;

int mySize;
const int N = 1e5+10;
int nums[N];

void down(int i)
{
    
    
    int d = i;
    if(2*i <= mySize && nums[d] > nums[2*i]) d = 2*i;
    if(2*i+1 <= mySize && nums[d] > nums[2*i+1]) d = 2*i + 1;
    if(i != d)
    {
    
    
        swap(nums[i],nums[d]);
        down(d);
    }
}

int main(void)
{
    
    
    int n,m;
    scanf("%d%d",&n,&m);
    mySize = n;
    for(int i = 1; i <= n; i++)
        scanf("%d",&nums[i]);
    for(int i = n/2; i ; i--)
        down(i);
    for(int i = 1; i <= m; i++)
    {
    
    
        printf("%d ",nums[1]);
        nums[1] = nums[mySize--];
        down(1);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/MoonWisher_liang/article/details/108711146