欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

堆(优先队列)与堆排序实现

程序员文章站 2024-02-16 08:56:16
...

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;
}
相关标签: 数据结构 算法