【数据结构与算法】之堆及其实现

微软技术分享 微软全球最有价值专家
全栈领域优质创作者
博客专家认证
2025-01-23 20:40:58

1、堆的概念及结构

如果有一个关键码的集合K = { , , ,…, },把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足: 且 = 且 >= ) i = 0,1, 2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

堆的性质:

堆中某个节点的值总是不大于或不小于其父节点的值;

堆总是一棵完全二叉树。

https://i-blog.csdnimg.cn/blog_migrate/4ed034a1bff016b85bdfbd6938acefbc.png

2、堆的实现

2.1 堆向下和向上调整算法

现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整 成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。

int array[] = {27,15,19,18,28,34,65,49,25,37};

https://i-blog.csdnimg.cn/blog_migrate/12f0dd595512536eb4d88013fa61d253.png

代码实现:

1、向上调整算法:

//交换
void swap(HPDataType* p1, HPDataType* p2)
{
    HPDataType tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
}


//向下调整
void AdjustDown(HPDataType* a, int size, int parent)
{
    //先假设做孩子小
    int child = parent * 2 + 1;
    while (child < size)//当孩子超过最后一个叶子时结束循环
    {
        if ((child + 1 < size) && a[child + 1] < a[child])//如果右孩子小于左孩子,右孩子与父亲比较
        {
            child++;
        }
        if (a[child] < a[parent])//建小堆,即小的当父亲
        {
            swap(&a[child] , &a[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
}

2、向下调整算法

//向下调整
void AdjustDown(HPDataType* a, int size, int parent)
{
    //先假设做孩子小
    int child = parent * 2 + 1;
    while (child < size)//当孩子超过最后一个叶子时结束循环
    {
        if ((child + 1 < size) && a[child + 1] < a[child])//如果右孩子小于左孩子,右孩子与父亲比较
        {
            child++;
        }
        if (a[child] < a[parent])//建小堆,即小的当父亲
        {
            swap(&a[child] , &a[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
}

2.2 堆的创建

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算 法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的 子树开始调整,一直调整到根节点的树,就可以调整成堆。

int a[] = {1,5,3,8,7,6}; 

https://i-blog.csdnimg.cn/blog_migrate/4a08a4a1418649c442810327935d373a.png

//建堆算法一(从第一个孩子向上调整)
//这种写法的时间复杂度为n*logn
//for (int i = 1; i < len; i++)
//{
//    AdjustUp(a, i);//从第一个孩子向上调整建堆
//}
// 
//建堆算法二(从倒数第一个根向下调整)
//这种写法的时间复杂度为O(N)[更优]
for (int i = (len - 1 - 1) / 2; i >= 0; i--)
{
    AdjustDown(a, len, i);//从倒数第一个根向下调整建堆
}

2.3 建堆时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的 就是近似值,多几个节点不影响最终结果):

https://i-blog.csdnimg.cn/blog_migrate/0a33063fcd259b7fbf87068053bd7e2b.png

因此:向下调整建堆的时间复杂度为O(N)。

2.4 堆的插入

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

https://i-blog.csdnimg.cn/blog_migrate/0bf586ab2c16070674908e6eea019279.png

// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
    //先判断容量大小是否足够
    if (hp->_size == hp->_capacity)
    {
        int newcapacity = hp->_capacity == 0 ? hp->_capacity = 4 : hp->_capacity * 2;
        //如果原来没有空间,就给上4,有的话就扩大为原来的两倍
        HPDataType* ptr = (HPDataType*)realloc(hp->_a, newcapacity * sizeof(HPDataType));//动态扩容
        if (ptr == NULL)
        {
            perror("realloc fail;");
            return;
        }
        //也可以用assert断言一下
        hp->_a = ptr;//开辟成功将地址传给arr
        hp->_capacity = newcapacity;//更新容量
    }
    //对堆进行插入操作
    hp->_a[hp->_size] = x;
    hp->_size++;
    //插入一个数据后就进行向上调整,保证堆的结构
    AdjustUp( hp->_a,  hp->_size-1);
}

2.5 堆的删除

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调 整算法。

https://i-blog.csdnimg.cn/blog_migrate/196acc493d80cb76d3b4f66b74c2a84e.png

// 堆的删除
void HeapPop(Heap* hp)
{
    assert(hp);
    assert(hp->_size > 0);
    //先交换根与叶子的位置,保证根的左右都是堆,方便后面的根先下调整
    swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
    //再删除最后一个数据,即是堆的最小或最大值
    hp->_size--;
    AdjustDown(hp->_a, hp->_size, 0);
}

2.6 完整代码

Heap.h

#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int HPDataType;
typedef struct Heap
{
    HPDataType* _a;
    int _size;
    int _capacity;
}Heap;
//堆的初始化
void HeapInit(Heap* hp);

// 堆的销毁
void HeapDestory(Heap* hp);

// 堆的插入
void HeapPush(Heap* hp, HPDataType x);

// 堆的删除
void HeapPop(Heap* hp);

// 取堆顶的数据
HPDataType HeapTop(Heap* hp);

// 堆的数据个数
int HeapSize(Heap* hp);

// 堆的判空
bool HeapEmpty(Heap* hp);

**Heap.c **

#include"Heap.h"

//堆的初始化
void HeapInit(Heap* hp)
{
    assert(hp);
    hp->_a = NULL;
    hp->_capacity = hp->_size = 0;
}

// 堆的销毁
void HeapDestory(Heap* hp)
{
    assert(hp);
    free(hp->_a);
    hp->_a = NULL;
    hp->_capacity = hp->_size = 0;
}

//交换
void swap(HPDataType* p1, HPDataType* p2)
{
    HPDataType tmp = *p1;
    *p1 = *p2;
    *p2 = 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]);
            child = parent;
            parent = (child - 1) / 2;
        }
        else
        {
            break;
        }
    }
}

// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
    //先判断容量大小是否足够
    if (hp->_size == hp->_capacity)
    {
        int newcapacity = hp->_capacity == 0 ? hp->_capacity = 4 : hp->_capacity * 2;
        //如果原来没有空间,就给上4,有的话就扩大为原来的两倍
        HPDataType* ptr = (HPDataType*)realloc(hp->_a, newcapacity * sizeof(HPDataType));//动态扩容
        if (ptr == NULL)
        {
            perror("realloc fail;");
            return;
        }
        //也可以用assert断言一下
        hp->_a = ptr;//开辟成功将地址传给arr
        hp->_capacity = newcapacity;//更新容量
    }
    //对堆进行插入操作
    hp->_a[hp->_size] = x;
    hp->_size++;
    //插入一个数据后就进行向上调整,保证堆的结构
    AdjustUp( hp->_a,  hp->_size-1);
}

//向下调整
void AdjustDown(HPDataType* a, int size, int parent)
{
    //先假设做孩子小
    int child = parent * 2 + 1;
    while (child < size)//当孩子超过最后一个叶子时结束循环
    {
        if ((child + 1 < size) && a[child + 1] < a[child])//如果右孩子小于左孩子,右孩子与父亲比较
        {
            child++;
        }
        if (a[child] < a[parent])//建小堆,即小的当父亲
        {
            swap(&a[child] , &a[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
}


// 堆的删除
void HeapPop(Heap* hp)
{
    assert(hp);
    assert(hp->_size > 0);
    //先交换根与叶子的位置,保证根的左右都是堆,方便后面的根先下调整
    swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
    //再删除最后一个数据,即是堆的最小或最大值
    hp->_size--;
    AdjustDown(hp->_a, hp->_size, 0);
}

// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
    assert(hp);
    assert(hp->_size > 0);
    return hp->_a[0];
}

// 堆的数据个数
int HeapSize(Heap* hp)
{
    assert(hp);
    return hp->_size;
}

// 堆的判空
bool HeapEmpty(Heap* hp)
{
    assert(hp);
    return hp->_size == 0;
}

文章来源: https://blog.csdn.net/2301_80221228/article/details/139175416
版权声明: 本文为博主原创文章,遵循CC 4.0 BY-SA 知识共享协议,转载请附上原文出处链接和本声明。


...全文
57 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

3,165

社区成员

发帖
与我相关
我的任务
社区描述
微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。
windowsmicrosoft 企业社区
社区管理员
  • 微软技术分享
  • 郑子铭
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。

予力众生,成就不凡!微软致力于用技术改变世界,助力企业实现数字化转型。

试试用AI创作助手写篇文章吧