数据结构——堆

简介: 数据结构——堆



一、前言

前面我们学习了二叉树,知道了二叉树一般用链式存储,但是还有一种存储方式是顺序存储,而

通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用

顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储。


二、堆的概念及结构

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

目录
相关文章
|
19天前
|
存储 算法 Java
散列表的数据结构以及对象在JVM堆中的存储过程
本文介绍了散列表的基本概念及其在JVM中的应用,详细讲解了散列表的结构、对象存储过程、Hashtable的扩容机制及与HashMap的区别。通过实例和图解,帮助读者理解散列表的工作原理和优化策略。
29 1
散列表的数据结构以及对象在JVM堆中的存储过程
|
21天前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
60 16
|
1月前
|
存储 JavaScript 前端开发
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
71 1
|
2月前
|
存储 Java
【数据结构】优先级队列(堆)从实现到应用详解
本文介绍了优先级队列的概念及其底层数据结构——堆。优先级队列根据元素的优先级而非插入顺序进行出队操作。JDK1.8中的`PriorityQueue`使用堆实现,堆分为大根堆和小根堆。大根堆中每个节点的值都不小于其子节点的值,小根堆则相反。文章详细讲解了如何通过数组模拟实现堆,并提供了创建、插入、删除以及获取堆顶元素的具体步骤。此外,还介绍了堆排序及解决Top K问题的应用,并展示了Java中`PriorityQueue`的基本用法和注意事项。
55 5
【数据结构】优先级队列(堆)从实现到应用详解
|
1月前
|
存储 算法 调度
数据结构--二叉树的顺序实现(堆实现)
数据结构--二叉树的顺序实现(堆实现)
|
1月前
|
存储 算法 分布式数据库
【初阶数据结构】理解堆的特性与应用:深入探索完全二叉树的独特魅力
【初阶数据结构】理解堆的特性与应用:深入探索完全二叉树的独特魅力
|
1月前
|
存储 算法
探索数据结构:分支的世界之二叉树与堆
探索数据结构:分支的世界之二叉树与堆
|
1月前
|
存储 算法 Java
【用Java学习数据结构系列】用堆实现优先级队列
【用Java学习数据结构系列】用堆实现优先级队列
31 0
|
1月前
|
存储 算法
【数据结构】二叉树——顺序结构——堆及其实现
【数据结构】二叉树——顺序结构——堆及其实现
|
1月前
【数据结构】大根堆和小根堆
【数据结构】大根堆和小根堆
33 0