《算法笔记》9.7 堆
9.7 堆
9.7.1 堆的定义与基本操作
堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子结点的值。其中,如果父亲结点的值大于或等于孩子结点的值,那么称这样的堆为大顶堆,这时,每个结点的值都是以它为根结点的子树的最大值。相反,则为小顶堆。
那么对于一个给定的初始序列,怎样把它建成一个堆呢?
从最后一个元素开始,从下往上,从右往左。假设当前元素X,让x与X的孩子结点比较,如果发现比X更大的元素Y,则交换X与Y的位置,这样Y就成了根结点,而X则成为了孩子结点。交换之后让X继续与其孩子结点比较,直到X的孩子结点都比X小或没有孩子节点为止。
例如:
从下往上,从右往左
- 第一个有孩子结点的是巨门,但是天同比巨门小,不同调整。
- 天机:七杀比太阴大,天机与七杀相比七杀大,交换七杀与天机的位置。交换后天机没有孩子结点,调整结束。
- 贪狼:紫薇比破军大,且比贪狼大,交换贪狼和紫薇。交换后贪狼没有孩子结点,调整结束。
- 武曲:七杀比巨门大,也比武曲大,交换武曲与七杀,交换后武曲有孩子天机和太阴,继续调整。太阴比天机大,也比武曲大,交换武曲与太阴的位置,交换后的武曲没有孩子节点,调整结束。
- 廉贞:紫薇比七杀大,比廉贞大,交换廉贞与紫薇的位置,交换后廉贞有孩子破军和贪狼,继续调整。破军比贪狼,廉贞大,交换廉贞与破军的位置,交换后廉贞没有孩子结点,调整结束。
至此,建堆完成。
那具体是怎么实现的呢,对完全二叉树来说,使用数组存储完全二叉树。这样结点就按层序存储在数组中,其中第一个节点将存储于数组中的1号位,并且数组i号位表示的结点的左孩子是(2i)号位,有孩子则是(2i+1)号位。则可以这样定义数组来表示堆
const int maxn = 100 ;
//head为堆,n为元素个数
int heap[maxn] , n = 10 ;
根据上面建堆的过程,每次调整都是把结点从上往下调整。针对这种向下调整,调整的方法是这样的:总是将当前结点V与它的左右孩子比较(如果有的话),假如孩子中存在比当前结点V权值大的,就将其中权值最大的孩子结点与结点V交换;交换完毕后继续让结点V和孩子比较,直到结点V的孩子的权值都比结点V的权值小或结点V没有孩子结点。
代码如下,时间复杂度为O(logN):
//对heap数组在[low,high]范围内进行调整
//low为欲调整结点的数组下标,high一般为堆的最后一个元素数组下标
void downAdjust( int low , int high ){int i = low , j = 2 * i ; //i为欲调整结点,j为其左孩子结点while( j <= high ){ //存在孩子结点//右孩子存在并且比左孩子大if( j+1 <= high && heap[j+1] > heap[j] ){j = j+1 ; //j记录右孩子的数组下标}//孩子节点比当前节点大if( heap[j] > heap[i] ){swap( heap[j] , heap[i] ) ; //交换两个结点i = j ; //保持i为欲调整结点、j为i的左孩子j = 2 * i ;}else{break ; //孩子结点的权值均比欲调整结点i小,调整结束}}}
那么建堆的过程就很容易了。假设序列中元素个数为n,由于完全二叉树的叶子节点个数为向上取整(n/2),因此数组下标在 [ 1 , 向下取整(n/2) ] 范围内的结点都是非叶子结点。于是可以从 向下取整(n/2) 号位开始 倒着 枚举结点,对每个遍历到的结点i进行[i,n]范围内的调整。
为什么要倒着枚举呢?
这是因为每次调整完一个结点后,当前子树中权值最大的结点就会处在根结点的位置,这样当遍历到其父亲节点时,就可以直接使用这个结果。符合从下往上,从右往左的规则
建堆代码如下,时间复杂度位O(N)
//建堆
void createHeap(){for( int i = n / 2 ; i >= 1 ; i-- ){downAdjust(i , n) ;}
}
另外,如果要删除堆中的最大元素(也就是堆顶元素),并让其仍然保持堆的结构,那么只需要将最后一个元素覆盖堆顶元素,然后对根结点进行调整,时间复杂度为O(logN)
//删除堆顶元素
void deleteTop(){heap[1] = heap[n--] ; //用最后一个元素覆盖堆顶元素,并让元素个数减一downAdjust(1 , n) ; //向下调整元素}
如果想要往堆里添加一个元素,可以把想要添加的元素放在数组最后(也就是完全二叉树的最后一个节点后面),然后进行向上调整。 向上调整总是把欲调整结点与父亲结点比较,如果权值比父亲节点大,那么就交换其与父亲结点,这样反复比较,直到到达顶堆或父亲结点权值较大为止。
//对heap数组在[low,high]范围进行向上调整
//其中low一般设置为1,high表示欲调整结点的数组下标
void upAdjust( int low , int high ){int i = high , j = i / 2 ; //i为欲调整结点,j为其父亲结点while( j >= low ){ //存在父亲结点if( heap[j] < heap[i] ){ //父亲结点权值小于欲调整结点swap( heap[j] , heap[i] ) ; //交换i = j ;j = i / 2 ;}else{break ;}}
}
在此基础之上,很容易实现添加元素的代码
//添加元素x
void insert( int x ){heap[++n] = x ; //让元素个数加1,然后将数组末位赋值为xupAdjust(1 , n) ; //向上调整加入的结点n
}
9.7.2 堆排序
堆排序是指使用堆结构对一个序列进行排序。现在讨论递增排序的情况。
考虑对一个堆来说,堆顶元素是最大的,因此在建堆完毕后,堆排序的直观思路就是取出堆顶元素,然后将堆的最后一个元素替换至堆顶,再进行一次针对堆顶元素的向下调整——如此重复,直到堆中只剩下一个元素为止。
//堆排序
void heapSort(){createHeap() ; //建堆for( int i = n ; i > 1 ; i-- ){ //倒着枚举,直到堆中只有一个元素swap( heap[i] , heap[1] ) ; //交换heap[i]与堆顶downAdjust(1 , i-1) ; //调整堆顶
《算法笔记》9.7 堆相关推荐
- 漫画算法笔记 二叉堆基本操作
漫画算法笔记 二叉堆基本操作 #include <iostream> #include <stdlib.h> #include <vector> using nam ...
- 《算法笔记》中文版 - 包括数组,链表,树,图,递归,DP,有序表等相关数据结构与算法的讲解及代码实现...
来源:专知本文为资源,建议阅读5分钟本文为你分享<算法笔记>中文版. https://github.com/Dairongpeng/algorithm-note 目录概览 第一节 复杂度. ...
- java 寻找和为定值的多个数_算法笔记_037:寻找和为定值的两个数(Java)
1 问题描述 输入一个整数数组和一个整数,在数组中查找两个数,满足他们的和正好是输入的那个整数.如果有多对数的和等于输入的整数,输出任意一对即可.例如,如果输入数组[1,2,4,5,7,11,15]和 ...
- 算法笔记(胡凡)学习笔记@Kaysen
本文旨在记录算法笔记学习过程中的收获和一些知识点,部分易错知识点只针对个人而言,CCF-CSP考试冲鸭!!! Chapter 2 C/C++快速入门(易错知识点) 2.1 基本数据类型 变量定义注意区 ...
- 数据结构与算法笔记(青岛大学王卓老师视频)
写在前面的话: 因为在学习数据结构之前,学习过一年的算法,所以有一些基础,一些我觉得 没必要的代码或知识就没写上,记得多是一些知识点,写的可能对于别人来说 很难接受,望谅解.我学习算法是在Acwing ...
- codeup墓地目录(算法笔记习题刷题笔记)
在线codeup contest 地址:http://codeup.cn/contest.php Contest100000575 - <算法笔记>3.1小节--入门模拟->简单模拟 ...
- 【算法笔记】极客时间 算法面试通关40讲 笔记 覃超
[算法笔记]极客时间 算法面试通关40讲 覃超 [算法笔记]极客时间 算法面试通关40讲 覃超 相关链接 在leetcode 上的题号 数组.链表: (堆)栈stack.队列queue 优先队列 哈希 ...
- 算法笔记知识点整理大全
每次刷题都觉得自己吃了知识点不全,基础不牢固的亏,刷题的时候目标也不明确,于是看完了算法笔记并把知识点归纳了一下,当然直接看书会更加详细,这个归纳只是学习时加深印象以及方便自己之后回顾而已:之后刷题大 ...
- 算法笔记学习PAT甲级解题记录
算法笔记学习记录 2019.06.26 float&&double 推荐全部使用double,注意区分scanf("%lf",&double1);与prin ...
- 考研算法笔记(排序)
考纲 (只考虑内部排序) 1插入排序(直插(稳),希尔) 2交换排序(冒泡(稳),快排) 3选择排序(简选,堆排) 4归并排序(稳) 5基数排序(稳) 6算法笔记 对任意n个关键字排序的比较次数至少为 ...
最新文章
- yocto linux dns,yocto-sumo源码解析(一): o
- tensorflow从入门到放弃-0
- 利用opencv读取图片将其作为opengl的纹理图片的实现方法
- 洛谷 - P2444 - 病毒 - AC自动机
- QML资源加载和网络透明度
- 学习 shell —— 编写基本脚本
- 一步步编写操作系统 1 部署工作环境 1
- CVS配置过程 (部分转)
- linux如何手动释放内存吗,Linux如何手动清理内存中cache信息
- 基于Python+tkinter+pygame的音乐播放器完整源码
- 小技巧:Mac下快速锁屏
- 常用圆圈数字序号(1~50)
- 服装进销存管理软件榜单前十排名
- 差距越来越大, 直播行业割终结束, 虎牙、斗鱼平分天下?
- helm 简介与入门
- word中安装Zotero插件
- 支持H.265视频网页Web播放的EasyPlayer.js设计理念与功能计划
- 2022年最新前端零基础学习路径
- linux内存占用过高怎么解决,centos7内存占用过高处理方法
- 快手:看见每一种生活(一面凉经)