堆(Heap)

 本文主要介绍以下内容:
  • Heap的实现
  • HeapSort(堆排序)
  • 完善各种堆的函数接口
  • TopK经典问题

堆就是一棵完全二叉树。因为它的某些性质,我们可以用数组存储。

  • 堆的性质
    1 完全二叉树的性质。
    (1)将根节点(树的最上面的结点)序号设为0,然后由左至右,由上至下编号。
    (2)父节点编号若为i,左孩子编号为(21+1),右孩纸为(21+2);
    (3)孩子的编号为i,父亲的编号则为 (i-1)/2 ;

2 父节点大于等于(或者小于等于)所有的子孙。
父节点大于等于子孙的堆叫大堆,反之叫做小堆。


不写代码的程序员不是好程序员,接下来,我们来实现Heap。

Heap的实现

//我们需要大量的函数接口
typedef int HeapDataType;  //增加代码的移植性
typedef struct Heap{      //堆的结构HeapDataType* _a;    //数组int _size;          //堆的实际数据个数int _capacity;      //数组容量
}Heap;//初始化堆//hp是Heap结构体变量, a是数组   n是数组大小void HeapCreat(Heap* hp ,HeapDataType* a ,int n);//入堆void HeapPush(Heap* hp ,HeapDataType x);//从堆中删除void HeapPop(Heap* hp);//为了减少大家的恐惧,我们先写这三个(后面多着呢)。

首先大家应该思考一个问题,我随意给你一个数组,你怎样实现完全二叉树到堆的建立?即,怎样建堆?
这里,我们给大家来一个算法,向下调整算法,我们以建立一个小堆为例。假设有这样一个完全二叉树:

显然这不是一个小堆,因为0号(即编号为0的结点,为了方便叙述)大于1号,所以我将他俩换下位置。

我们先不管其他的,先盯着值为20的结点。我们发现,这时候1号比3和4都大,那么换谁呢?显然换3号,因为3号比4号小!
所以有,

同样的,3号大于7号和8号,交换7号(较小的)。

好了,经过这几步交换,看看我们得到了什么。
好像什么都没得到?不,我们将一个比较大的数字移到了下边,我们用的方法也值得我们将它写下来。

//向下调整算法
//三个参数:数组  ,数组大小  ,  根节点
void AdjustDown(HeapDataType* a, int n, int root)
{int parent = root;int child = parent*2 + 1; //二叉树的性质while(child < n)if(child+1 < n && a[child] > a[child+1])//找出小的孩子{++child;}if(a[parent] > a[child])  //如果父亲大于孩子,则交换{int temp = a[parent];    a[parent] = a[child];a[child] = temp;parent = child;   //迭代child = parent*2 + 1;}else {           //否则任务完成,直接跳出循环。break;}
}

我们在写的时候巧妙运用了 ++child,避免分左右孩子,接下来我们将用这个算法建堆。
我们意识到,如果从根节点向下调整不能建成小堆,但是,可以移动一个较大的数下沉,那么如果我从下开始用这个算法呢?

我们从最后一个有孩子的父节点(3号)开始会怎么样呢?

然后我们到第二个父节点,2号。

接着是1号父节点。

最后0号结点。

我们发现,成了!!!!
这就是我们想要的结果!
代码如下:

void HeapCreat(Heap* hp ,HeapDataType* a ,int n)
{hp->_size = hp->_capacity = n;hp->_a = (HeapDataType*)malloc(sizeof(HeapDataType));if(hp->_a == NULL) return ;  //防止开辟失败memcpy(ph->_a,a,sizeof(HeapDataType)*n);//拷贝数据//n-1是最后一片叶子的下标,然后利用性质求父节点,依次遍历前面的父节点。for(int i = (n-1-1)/2 ; i >= 0; --i){AdjustDown(hp->_a,hp->_size,i);}
}

好了,小堆也已经建好,那么大堆呢? 显然易得,只需改变两个符号即可。

//   a[child] < a[child+1];
//   a[child] > a[parent] ;

好了,接下来,我们来看看堆能干什么呢?找最值!而且是连续的找!
那么这不就是排序吗?
所以接下来为大家介绍堆排序。

HeapSort ( 堆排序 )

我们以降序来举栗子。
首先有一个大问题摆在我们面前,降序使用大堆还是小堆?
这里给大家思考时间。

1

2

3

4

5

我就知道你会选择用大堆!降序不就是每次将最大的值选出来,排在数组头不久ok了?but,真是这样吗?

如果拿走100,然后呢?我们肯定想要那第二大的数作为根,于是70被拿了上来,
But,这样关系全乱了!!!

所以我们还得用向下调整法,但是时间复杂度相当高!
所以,我们用小堆。

我们先排成小堆,


然后怎么做呢?我们只知道4是最小的,它又要求降序,所以我们拿走4,
与最后一个结点交换。
就是

20 7 25 8 14 30 40 10 4

这时候最后一个数最小,然后我们当然要找第二小的。所以我们对除最后一个数进行重建小堆,然后再将根拿到倒数第二个位置,以此类推。堆排序就完成了。

//代码实现
void HeapSort(HeapDataType* hp->_a,int hp->_size)
{for(int i = (hp->_size-2)/2;i>=0;i--){AdjustDown(hp->_a,hp->_size,i);   //建小堆}int end = hp->_size - 1;while(end > 0){              Swap(&hp->_a[0],&hp->_a[end]);  //交换函数 (我懒得写了(嘻嘻))AdjustDown(hp->_a,end,0);     //重建小堆--end;     }
}


result。此时时候已经不早了,是时候来完善我们接下来的接口。

//void HeapSort(HeapDataType* a, int len);堆排
//void AdjustDown(HeapDataType* a, int n, int root);  向下调整
void AdjustUp(HeapDataType* a, int child);  //向上调整
//void HeapCreat(Heap* hp, HeapDataType* a, int n);  建堆
void HeapPush(Heap* hp, HeapDataType x);  //入堆
void HeapPop(Heap* hp);   //从堆中移除
HeapDataType HeapTop(Heap* hp);  //返回堆的根值
int HeapEmpty(Heap* hp);   //判断堆是否为空

注释掉的是我们已经KO掉的,接下来我们挨个解决剩下的。
首先先来入堆。
怎么入堆呢?从根结点上入吗?那么原来的根结点放在哪里呢?
这里我们再来一种算法,向上调整法。

我们将x插入到堆的尾部,然后逐层向上调整。

结果:

//向上调整法
void AdjustUp(hp->_a,child)
{int parent = (child - 1)/2;while(child > 0) {if(a[child] < a[parent]){Swap(&hp->_a[child],&hp->_a[parent]);child = parent;parent = (child - 1)/2;}else break;}
}
  //我们想要将x插入到堆中,使得插入后还是一个堆。//同样的,假设之前是一个小堆。
void HeapPush(Heap* hp, HeapDataType x)
{//首先一个小问题,万一hp->_a 没空间怎么办?if(hp->_size == hp->_capacity){  //空间满了。需要callocint newcapacity = hp->_capacity*2;HeapDataType* HP = (HeapDataType*)calloc(hp->_a,sizeof(HeapDataType)*newcapacity);if(HP == NULL){    //开辟失败return ;}hp->_a = HP;hp->_capacity = newcapacity;   //开辟完成}hp->_a[hp->_size - 1] = x;  //放入数据hp->_size++;        //size加一AdjustUp(hp->_a,hp->_size-1);
}

出堆就比较简单,我们利用堆排的思想,将根节点与尾结点交换,然后向下调整即可。

void HeapPop(Heap* hp)
{Swap(&hp->_a[0],&hp->_a[hp->_size-1]);   //交换根和尾元素hp->_size--;   //干掉尾元素AdjustDown(hp->_a,hp->_size,0); //向下调整。
}

剩下2个更是简单的一批。

HeapDataType HeapTop(Heap* hp)
{return hp->_a[0];
}
int HeapEmpty(Heap* hp)
{return hp->_size == 0 ? 1 : 0;
}

最后我们用堆来解决一道经典问题,TopK问题。
何为TopK问题? 从一堆数(大于k)中选出前k个大数(或小数)。
比如英雄联盟有一天说要给南京市第一亚索,一直到南京市第10亚索,给这10个玩家颁发奖励,他们先用一套算法算出每个压缩玩家的得分,然后怎么选出前k个呢?

自然而然的想法

  • 排序
    我不管你要求什么,只要是数组,排序可以解决大部分问题。实际上这题确实可以排序,但时间复杂度不尽人意,还有更优的解吗?能用堆吗?
  • 堆的性质
    我先建一个大堆,找出最高分,然后再找次小的,知道k个玩家找完。这已经不错了,但是还有没有更优的解?或者说,我能不能建造小点的堆(堆的建立算法时间复杂度不是很easy)?
  • 更优的解法

任意取k个玩家的分数建造一个k的小堆,用其他玩家与根节点比较,如果比根节点大,则换掉根节点。然后向下调整成小堆,知道所有玩家比较完毕。

这里要注意的是,我只是找出前k个,并不知道这k个玩家谁更厉害(我没有给这k个玩家排序)。
而且,这并不是绝对的公平,想一想,如果所有人的分数都相同,这个算法不就是随机抽10个人吗?
这就是堆的大部分问题,由于是晚上写的博客,加上对截图工具使用实在粗糙,图有点模糊,如有问题,希望不要介意。
(全文完)

数据结构--堆(Heap)相关推荐

  1. 数据结构-堆(Heap)

    数据结构-堆(Heap) 我认识的堆: 1.建立在完全二叉树的基础上 2.排序算法的一种,也是稳定效率最高的一种 3.可用于实现STL中的优先队列(priority_queue)  优先队列:一种特殊 ...

  2. heap python_数据结构-堆(Heap) Python实现

    堆(Heap)可以看成近似完全二叉树的数组,树中每个节点对应数组中一个元素.除了最底层之外,该树是完全充满的,最底层是从左到右填充的. 堆包括最大堆和最小堆:最大堆的每一个节点(除了根结点)的值不大于 ...

  3. 数据结构--堆Heap

    数据结构:堆(Heap) 堆就是用数组实现的二叉树,所以它没有使用父指针或者子指针.堆根据"堆属性"来排序,"堆属性"决定了树中节点的位置. 堆的常用方法: 构 ...

  4. 数据结构--堆 Heap

    文章目录 1. 概念 2. 操作和存储 2.1 插入一个元素 2.2 删除堆顶元素 3. 堆排序(不稳定排序) 3.1 建堆 3.2 排序 3.3 思考:为什么快速排序要比堆排序性能好?两者都是O(n ...

  5. 数据结构 堆 heap

    一种二叉树的结构  →  完全二叉树 每个节点 ≥   or   ≤ 孩子节点 最大堆                     最小堆 特点 最大堆:最大值  就是  堆顶元素 最小堆: 最小值  就 ...

  6. 数据结构堆Heap实现思路

    文章目录 一.了解堆 介绍 数组存放 实现思路 二.实现堆(大顶堆为例,即书中所说) 向下调整函数 构建和删除操作 向上调整函数和添加操作 实现堆排序 三.PriorityQueue 四.练习 参见& ...

  7. codeforces 贪心+优先队列_算法与数据结构基础 - 堆(Heap)和优先级队列(Priority Queue)...

    堆基础 堆(Heap)是具有这样性质的数据结构:1/完全二叉树 2/所有节点的值大于等于(或小于等于)子节点的值:

  8. 算法与数据结构基础 - 堆(Heap)和优先级队列(Priority Queue)

    堆基础 堆(Heap)是具有这样性质的数据结构:1/完全二叉树 2/所有节点的值大于等于(或小于等于)子节点的值: 图片来源:这里 堆可以用数组存储,插入.删除会触发节点shift_down.shif ...

  9. 数据结构之堆(Heap)及其用途

    本文采用图文码结合的方式介绍堆来实现优先队列 什么是优先队列? 队列是一种先进先出(FIFO)的数据结构.虽然,优先队列中含有队列两个字,但是,他一点也不像队列了.个人感觉,应该叫他优先群.怎么说那, ...

最新文章

  1. postfilter中文什么意思_Filterpost请求中文字符编码的过滤器 --学习笔记
  2. 微信小程序搜索功能!附:小程序前端+PHP后端
  3. fir.im Weekly - 让 iOS 应用更加安全
  4. 51nod 1499 (最小割)
  5. 前端白屏问题_前端优化-如何计算白屏和首屏时间
  6. let const缓存for循环的中间变量
  7. NVIDIA显卡驱动的重装
  8. 某网站字幕加密的wasm分析
  9. 什么是CSR证书申请文件?
  10. 烂土豆 (ms16-075) 提权方法
  11. 不错的json在线编辑器
  12. MATLAB相对声压分布彩图,矩形声化学反应器声场分布纵向共振研究
  13. ORA-01157: cannot identify/lock data file 6 - see DBWR trace file
  14. 论文查找: arXiv,论文阅读:知云文献翻译, 完美组合 !
  15. input搜索框在ios系统中兼容问题
  16. iOS开发-DES加密解密算法
  17. python执行cmd命令,并获得返回值
  18. PS入门基础教程之简单背景抠图讲解,建议收藏!
  19. 2023年MathorCup 高校数学建模挑战赛-A 题 量子计算机在信用评分卡组合优化中的应用-思路详解(模型代码答案)
  20. 无线通信原理及应用--阅读笔记一

热门文章

  1. 关于SAP的故事(转)
  2. 网络爬虫关键技术分析与实现
  3. CTO的职责和必备能力
  4. 商业海外社交媒体营销10步指南 [2023]
  5. matlab 卷积神经网络工具,CNN卷积神经网络MATLAB工具箱中文注释版
  6. java reverse 怎么实现,java实现reverse字符串
  7. ios中使用SegmentedControl来切换视图
  8. 【转载】matlab histogram直方图设置
  9. 〖Python自动化办公篇⑦〗- word文件自动化 - 实操之筛选简历
  10. 2023电博会:卡奥斯亮出了青岛高质量发展的新引擎