【数据结构】堆

一、定义

堆(Heap)是 计算机科学 中一类特殊的数据结构,是最高效的优先级队列。堆通常是一个可以被看做一棵 完全二叉树 的数组对象。

二、功能实现

<0>头文件

#ifndef HEAP_H
#define HEAP_H
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <string.h>
#endif

typedef int HPDatatype;
typedef struct Heap
{
    HPDatatype *a;
    int size;
    int capacity;
} HP;

void HPCreat(HP *php,HPDatatype *a,int n);//堆的构建
void HPInit(HP *php);//初始化堆
void HPPrint(HP *php);//打印堆的数据
void HPDestroy(HP *php);//销毁堆
void Swap(HPDatatype *p1,HPDatatype *p2);//交换元素
void AdjustUp(HPDatatype *a,int child);//向上调整
void HPPush(HP *php,HPDatatype x);//往堆插入元素
void AdjustDown(HPDatatype *a,int size,int parent);//向下调整
void HPPop(HP *php);//删除堆顶数据
HPDatatype HPTop(HP *php);//返回堆顶数据
int HPSize(HP *php);//返回堆的大小
bool HPEmpty(HP *php);//判断堆是否为空
void HeapSort(int *a,int n);//堆排序算法

<1>堆的构建

void HPCreat(HP *php,HPDatatype *a,int n)//堆的构建(利用一个数组a)
{
    assert(php);
    php->a=(HPDatatype*)malloc(sizeof(HPDatatype)*n);
    if(!php->a)
    {
        perror("malloc fail");
        exit(-1);
    }
    //自底向上建堆算法
    //只需要复制数组数据,然后自底向上(bottom-up)把树由小到大不断向下调整,最终就可以得到堆
    //时间复杂度为O(N)
    //如果把数组里的数据一个个往堆里插,想当于自顶向下(top-down建堆算法)不断向上调整,时间复杂度为O(N*log N)
    memcpy(php->a,a,n*sizeof(HPDatatype));
    php->size=php->capacity=n;
    for(int i=(n-1-1)/2;i>=0;i--)
    {
        AdjustDown(php->a,n,i);
    }
}
建堆有两种方法:
使用向上调整自顶向下(top-down)建堆,时间复杂度为O(N*logN)
使用向下调整自底向上(bottom-up)建堆,时间复杂度为O(N)
注:使用向上调整不可以自底向上建堆,使用向下调整不可以自顶向下建堆,无法实现大小堆的要求,可以自己找几个例子试一试

<2>初始化堆

void HPInit(HP *php)//初始化堆
{
    assert(php);
    php->a=NULL;
    php->size=php->capacity=0;
}

<3>打印堆的数据

void HPPrint(HP *php)//打印堆的数据
{
    assert(php);
    for(int i=0;i<php->size;i++)
    {
        printf("%d ",php->a[i]);
    }
    printf("\n");
}

<4>销毁堆

void HPDestroy(HP *php)//销毁堆
{
    assert(php);
    free(php->a);
    php->a=NULL;
    php->size=php->capacity=0;
}

<5>交换元素

void Swap(HPDatatype *p1,HPDatatype *p2)//交换元素
{
    HPDatatype tmp=*p1;
    *p1=*p2;
    *p2=tmp;
}

<6>向上调整

void AdjustUp(HPDatatype *a,int child)//向上调整,时间复杂度为log N
{
    int parent=(child-1)/2;
    while(child>0)
    {
        if(a[child]<a[parent])//此处>为大堆,<为小堆
        {
            Swap(&(a[child]),&(a[parent]));
            child=parent;
            parent=(parent-1)/2;
        }else
                break;
    }
}

<7>往堆插入元素

void HPPush(HP *php,HPDatatype x)//往堆插入数据
{
    //堆的插入操作时间复杂度为log N
    assert(php);
    if(php->size==php->capacity)
    {
        int newcapacity=(php->capacity == 0 ? 4 : php->capacity*2);
        HPDatatype *tmp=(HPDatatype*)realloc(php->a,sizeof(HPDatatype)*newcapacity);
        if(!tmp)
        {
            perror("realloc fail");
            exit(-1);
        }
        php->a=tmp;
        php->capacity=newcapacity;
    }
    php->a[php->size++]=x;
    AdjustUp(php->a,php->size-1);//每次往堆插入数据时,都要判断向上调整一次,这样不管插入的数据的大小顺序怎么样,最终建立起来的堆都直接排好了顺序
}

<8>向下调整

void AdjustDown(HPDatatype *a,int size,int parent)//向下调整,时间复杂度为log N
{
    int child=parent*2+1;
    while(child<size)
    {
        //此处需要判断哪个是数据更小的孩子
        if(child+1<size&&a[child]>a[child+1])
                child++;
        if(a[parent]>a[child])
        {
            Swap(&(a[child]),&(a[parent]));
            parent=child;
            child=child*2+1;
        }else
                break;
    }
}

<9>删除堆顶数据

void HPPop(HP *php)//删除堆顶数据
{
    assert(php);
    assert(php->size>0);
    Swap(&(php->a[0]),&(php->a[--php->size]));
    AdjustDown(php->a,php->size,0);
}

<10>返回堆顶数据

HPDatatype HPTop(HP *php)//返回堆顶数据
{
    assert(php);
    assert(php->size>0);
    return php->a[0];
}

<11>返回堆的大小

int HPSize(HP *php)//返回堆的大小
{
    assert(php);
    return php->size;
}

<12>判断堆是否为空

bool HPEmpty(HP *php)//判断堆是否为空
{
    assert(php);
    return php->size==0;
}

<13>堆排序算法

void HeapSort(int *a,int n)//堆排序算法
{
//向下调整建堆
    for(int i=(n-1-1)/2;i>=0;i--)
    {
        AdjustDown(a,n,i);
    }
//排序
    int end=n-1;
    while(end>0)
    {
        Swap(&a[0],&a[end]);
        AdjustDown(a,end,0);
        end--;
    }
}
分析:若目标顺序为升序,感觉建小堆更好,但是发现建小堆也只能找到最小的数据,而其他数据仍然是乱序的。为了以升序的方式存储数据,没有办法把最小的元素插到堆尾,因此我们不得不再开一个堆的空间,空间复杂度为O(N),并且每次把堆顶元素pop掉再重新向下调整一次堆,时间复杂度为O(N*logN)。
而假如不开空间,即把小堆堆顶的数据从数组头部开始存储,我们需要不断地重新建堆,因为除去堆顶元素后,数组里剩余的数据是混乱的,整个树的结构都被破坏了,因此最后时间复杂度为O(N^2)。
因此实际上升序排序我们需要建的是大堆,才能最高效地解决问题。反之,降序排序我们需要建的是小堆。

升序排序:建大堆(O(N)),选出最大的数据,与堆的新堆尾处数据交换位置,堆不断缩小。并且每交换一次数据,从堆顶进行一次向下调整使新的堆仍然是大堆(因为两个最大的子树仍然为大堆)。最终,数组中存储的数据就变成了升序排序。每次向下调整的时间复杂度为O(logN),移动N个数据,则该算法时间复杂度为O(N*logN)

降序排序:建小堆(O(N))选出最小的数据,与堆的新堆尾处数据交换位置,堆不断缩小。并且每交换一次数据,从堆顶进行一次向下调整使新的堆仍然是小堆(因为两个最大的子树仍然为小堆)。最终,数组中存储的数据就变成了降序排序。每次向下调整的时间复杂度为O(logN),移动N个数据,则该算法时间复杂度为O(N*logN)

三、实例

main函数:
top k问题:
建一个N个数据的堆,取k次堆顶,同时pop k次堆顶,再向下调整,时间复杂度为O(logN)
当数据过大时,数据不能全部放到内存中。建一个k个数据的堆,然后遍历(N-k)个数据。
若取最大的k个数据,则堆为小堆,遍历中若数据大于堆顶数据则替换堆顶,并向下调整。
最终这个堆中的k个数据则为最大的k个数据。最小的k个数据同理。
#include "heap.h"

void Topk(int k)
{
    int maxheap[k];
    FILE *fout=fopen("heapdata.txt","r");
    if(!fout)
    {
        perror("fopen fail");
        exit(-1);
    }
    for(int i=0;i<k;i++)
    {
        fscanf(fout,"%d",&maxheap[i]);
    }
    for(int i=(k-1-1)/2;i>=0;i--)
    {
        AdjustDown(maxheap,k,i);
    }
    int val=0;
    while(fscanf(fout,"%d",&val)!=EOF)
    {
        if(val>maxheap[0])
        {
            maxheap[0]=val;
            AdjustDown(maxheap,k,0);
        }
    }
    for(int i=0;i<k;i++)
    {
        printf("%d ",maxheap[i]);
    }
    printf("\n");
    fclose(fout);
}

int main()
{
    HP hp;
    int arr[5]={5,4,3,2,1};
    HPCreat(&hp,arr,5);
    HPPrint(&hp);
    printf("heap size:%d\n",HPSize(&hp));
    printf("heap top:%d\n",HPTop(&hp));
    HPPush(&hp,0);
    printf("newtop:%d\n",HPTop(&hp));
    HPPop(&hp);
    HPPrint(&hp);
    HPPop(&hp);
    HPPop(&hp);
    HPPop(&hp);
    HPPop(&hp);
    HPPop(&hp);
    if(HPEmpty(&hp))
            printf("empty\n");
    Topk(5);
    HeapSort(arr,5);
    for(int i=0;i<5;i++)
    {
        printf("%d ",arr[i]);
    }
    printf("\n");
    HPDestroy(&hp);
    return 0;
}
heap.txt数据:
12
313
32
231
2001
21
1000
55
667
33
22
33
2000
44
34
76
2
9
2005
22
2000
99
80
67
4424
311
运行结果:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C.N.F

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值