数据结构——堆

目录

一、前言

二、堆的概念及结构

三、堆的实现

四、堆的应用


一、前言

前面我们学习了二叉树,知道了二叉树一般用链式存储,但是还有一种存储方式是顺序存储,而
通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用
顺序结构存储。 现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储。

二、堆的概念及结构

n个元素的序列{k1,k2,ki,…,kn}当且仅当满足下关系时,称之为堆。K(i) <= K(2i) 且 K(i) <= K(2i+1)称为小堆 或者 K(i) >= K(2i+1)  且 K(i) >= K(2i+1) 称为大堆。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

堆的性质:
* 堆中某个节点的值总是不大于或不小于其父节点的值。
* 堆总是一棵完全二叉树。
如下图
大根堆(大堆)

小根堆(小堆)


三、堆的实现

均以小堆为例

* 首先我们先来定义一个堆

typedef int HPDataType

typedef struct Heap
{
    HPDataType* a; //数组
    int size; //元素个数
    int capacity; //容量大小
}HP;

接着我们再来实现堆的各种接口函数

void HeapInit(HP* php)
{
    assert(php);
    php->a = NULL;
    php->size = php->capacity = 0;
}

bool HeapEmpty(HP* php)
{
    return php->size = 0;
}

void HeapDestory(HP* php);
{
    assert(php);
    free(php->a);
    php->a = NULL;
    php->size = php->capacity = 0;
}

int HeapTop(HP* php) //取堆顶元素
{
    assert(php);
    assert(php->size > 0);
    return php->a[0];
}

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

接下来我们就来实现堆的插入数据和删除数据的函数

1、堆的插入:先插入一个数到数组的尾上,再进行向上调整算法,直到满足堆。

向上调整算法:

void swap(int* a, int* b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

void AdjustUp(HPDatatype* a, int child)
{
    int parent = (child - 1) / 2;
    while(child > 0)
    {
        if(a[child] < a[parent])
        {
            swap(&a[child], &a[parent]);
        }
        else
        {
            break;
        }
        child = parent;
        parent = (child - 1) / 2;
    }
}
​

插入函数

void HeapPush(HP* php, HPDatatype x)
{
    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 == NULL)
        {
            printf("realloc fail");
            exit(-1);
        }
        php->a = tmp;
        php->capacity = newcapacity;
    }

    php->a[php->size] = x;
    php->size++;
    AdjustUp(php->a, php->size - 1);
}
2、堆的删除:删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。
向下调整算法:
void AdjustDown(HeapDatatype* a, int size, int parent)
{
    int child = parent * 2 + 1; //默认左孩子最小
    while(child < size)
    {
        if(a[child] > a[child + 1] && child + 1 < size)
        {
            child++;
        }
        if(a[parent] > a[child])
        {
            swap(&a[parent], &a[child]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
}

删除函数

void HeapPop(HP* php) /*删除堆顶的数据,将第一个和最后一个交换,
删除最后一个利用向下调整算法将其重新调整为堆*/
{
    assert(php);
    assert(php->size > 0);
    swap(&php->a[0], &php->a[php->size-1]);
    php->size--;
    AdjustDown(php->a, php->size, 0);
}
​

四、堆的应用

1、TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决。

基本思路如下:

* 用数据集合中前K个元素来建堆 :前k个最大的元素,则建小堆 ,前k个最小的元素,则建大堆。

* 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素。
将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。
void PrintTopK(int* a, int n, int k)
{
    //1、建堆——用a中前k个元素建堆
    int* kMinHeap = (int*)malloc(sizeof(int) * k);
    assert(kMinHeap);
    for(int i = 0; i < k; i++)
    {
        kMinHeap[i] = a[i];
    }
    for(int i = (k-1-1) / 2; i>=0; i--)
    {
        AdjustDown(kMinHeap, k, i);
    }

    //2、将剩余n-k个元素依次与堆顶元素比较并交换
    for(int j = k; j < n; j++)
    {
        if(kMinHeap[0] < a[j])
        {
            kMinHeap[0] = a[j];
            AdjustDown(kMinHeap, k, 0);
        }
    }
    for (int i = 0; i < k; i++)
	{
		printf("%d ", kMinHeap[i]);
	}
}

2、堆还有一个应用就是应用在堆排序中,堆排序即利用堆的思想来进行排序。这里我们不做讲解,而会在排序中专门讲解。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值