[数据结构]——堆(Heap)、堆排序和TopK
堆(heap),是一种特殊的数据结构。之所以特殊,因为堆的形象化是一个棵完全二叉树,并且满足任意节点始终不大于(或者不小于)左右子节点(有别于二叉搜索树Binary Search Tree)。其中,前者称为小顶堆(最小堆,堆顶为最小值),后者为大顶堆(最大堆,堆顶为最大值)。然而更加特殊的是,通常使用数组去存储堆,而不是二叉树。关于完全二叉树,可以参见另一篇博文http://www.cnblogs.com/eudiwffe/p/6207196.html
// Heap is a sepcial complete binary tree(CBT)
/* Heap sketch is a CBT, but stored in Array
* 9 ---> maxtop 7 7
* / \ / \ / \
* / \ / \ / \
* 7 8 4 8 4 5
* / \ / \ / \ / /
* / \ / \ / \ / /
* 5 3 2 4 3 5 3 6
*
* (1) (2) (3)
* maxtop heap not maxtop(mintop) not heap(CBT)
* */
具体而言,对于长度为N的数组A中的任意一个元素i(0<=i<N/2),其左右子节点为i*2+1和i*2+2。以大顶堆为例,该堆始终满足:
A[i]>=A[i*2+1] && A[i]>=A[i*2+2]。(下文不做特殊说明均以大顶堆为例)
如何创建一个堆呢?对于给定的一个数组arr[]和长度n,一般使用在数组上就地堆化。堆化的过程实际是调整堆的过程。有自上到下和自下到上两种堆化方法。
1)自上到下构建堆
// Method 1
// Create (Initialize) Heap, from top to bottom
void heap_create(int arr[], int n)
{
int i; // from top to bottom
for(i=1; i<n; heap_adjust(arr,i++));
}
自上到下很好理解,首先假设当前数组arr的前i个元素已经满足堆性质(arr[0]只有一个元素肯定满足);然后每次在数组之后添加一个元素A[i],使得新的数组A[0~i]满足堆化性质,其中heap_adjust可以调整当前节点i使其满足堆化;直到i为n时,调整完毕,即堆化完毕。其中heap_adjust如下:
void heap_adjust(int arr[], int c)
{ // c - children, p - parent
int p = (c-1)>>1, temp;
// heap adjust from maxtop, from bottom to top
for(; arr[p]<arr[c]; c=p, p=(c-1)>>1){
temp = arr[p];
arr[p] = arr[c];
arr[c] = temp;
}
} // Time O(logn)
调整代码也很好理解,首先找到当前节点c的父节点p,如果arr[p]<arr[c],则交换,然后继续寻找p的父节点进行调整;否则,调整完毕(因为前文已经假设,数组的前i-1已经满足堆化,新添一个元素i进行调整)。
很有意思,构建堆时使用自上到下,那么调整堆就必须自下到上。
2)自下到上构建堆
// Method 2
// Create (Initialize) Heap, from bottom to top
void heap_create(int arr[], int n)
{
int i; // from bottom to top
for(i=(n>>1)-1; i>-1; heap_adjust(arr,i--,n));
}
此处自下到上的“下”,并不是数组最后一个元素,而是最后一个父节点n/2-1。也就是以父节点为线索,逐渐调整该节点的子节点。因此,此处heap_adjust是自上到下的调整,如下
void heap_adjust(int arr[], int p, int n)
{ // c - children, p - parent
int maxid=p, temp;
// heap_adjust for maxtop, from top to bottom
for(; p<(n>>1); p=maxid){
if ((p<<1)+1<n && arr[(p<<1)+1]>arr[maxid])
maxid = (p<<1)+1;
if ((p<<1)+2<n && arr[(p<<1)+2]>arr[maxid])
maxid = (p<<1)+2;
if (maxid == p) break;
// swap arr[maxid] and arr[p]
temp = arr[maxid];
arr[maxid] = arr[p];
arr[p] = temp;
}
} // Time O(logn)
首先保证当前p节点是作为父节点,然后在找到其子节点p*2+1和p*2+2,在三者中选择最大的一个maxid,然后交换;否则调整结束。
两种构建堆的方法各有利弊,方法1)是逐渐增加新节点,堆的节点增加方法数组尾部;方法2)是逐渐删除堆顶节点,然后在剩下的节点中寻找最大的放在堆顶(一般会将数组尾元素与堆顶交换,以保证其符合完全二叉树结构)。堆的调整时间复杂度均为O(logn),堆的创建时间复杂度均为O(nlogn)。
3)堆排序
堆的常见应用是堆排序。堆排序方法十分巧妙,无须额外空间,直接在原数组中进行堆排序。对于给定的数组arr[]以及其长度n,首先进行原地堆化,上面两种方法均可,推荐第二种;然后每次将堆顶元素与数组尾元素交换,即arr[0]与arr[n-1]交换;将数组arr[]以及其长度n-1进行堆调整,此调整使用2)中的调整方法;反复迭代,直到调整数组的长度为1为止,排序完毕。
以非降序排序为例,每次删除堆顶的元素放入数组尾部,所以需要使用大顶堆。
// Heap Sort - ascending order
void heap_sort(int arr[], int n)
{
int i, temp;
// init maxtop heap, using method 2 (from bottom to top)
for (i=(n>>1)-1; i>-1; heap_adjust(arr,i--,n));
for (i=n-1; i>0; heap_adjust(arr,0,i--)){
// mv heap top to end (heap top is max)
temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
}
} // Time O(nlogn)
每次调整堆,只需将堆顶调整即可。堆化时间复杂度为O(nlogn),排序时间复杂度为O(nlogn),总的时间复杂度为O(nlogn)。因为调整堆必须使用自上到下的方法调整heap_adjust,所以使用方法2)进行堆化和调整,十分巧妙。
4)TopK问题
TopK问题描述:在N个无序元素中,找到最大的K个(或最小的K)。
如果使用排序类似的算法,其时间复杂度为O(NlogN)+O(K)。当N远大于K时,例如N为1e9,而K为10时,这种方法显然太慢。使用堆化和堆调整则可以快速解决。以下以寻找最小的K个元素为例。
设有一个K长度的最大堆,如果在数组中有一个元素小于该堆顶,则该元素有可能为寻找的最小K元素之一。则将该元素替换堆顶,然后进行堆调整。反复迭代,直到遍历了数组中的所有元素。此时,该长度为K的最大堆就是待寻找的TopK。
// TopK problem : find max k (or min k) elements from unordered set
// eg. find min k elements from arr[], stored in res[]
void topk(int arr[], int n, int res[], int k)
{
int i; // copy and k elements to res
for (i=0; i<k; res[i]=arr[i],++i);
// make maxtop heap for res[]
for(i=(k>>1)-1; i>-1; heap_adjust(res,i--,k));
for(i=k; i<n; ++i){
if (res[0] <= arr[i]) continue;
// now arr[i] < heap top
res[0] = arr[i];
heap_adjust(res,0,k);
}
} // Time O(nlogk)
其中arr[]为原始无序数据,res[]为寻找结果。堆调整使用2)中的调整方法。首先任意选择无序数组arr[]中的K个元素,对其进行堆化;然后从K开始遍历无序数组arr[],每次将比堆顶小的放入堆顶,然后堆调整;最后得到堆res[]为TopK结果。其时间复杂度:创建K个元素堆O(KlogK),寻找最小K元素O((N-K)logK),总时间复杂度为O(NlogK),(当N远大于K时)。
对于寻找最大K个元素,则需要构建最小堆,以及最小堆的堆调整,不再赘述。
注:本文涉及的源码:https://git.oschina.net/eudiwffe/codingstudy/blob/master/src/heap/heap.c
[数据结构]——堆(Heap)、堆排序和TopK的更多相关文章
- 数据结构 - 堆(Heap)
数据结构 - 堆(Heap) 1.堆的定义 堆的形式满足完全二叉树的定义: 若 i < ceil(n/2) ,则节点i为分支节点,否则为叶子节点 叶子节点只可能在最大的两层出现,而最大层次上的叶 ...
- 基本数据结构——堆(Heap)的基本概念及其操作
基本数据结构――堆的基本概念及其操作 小广告:福建安溪一中在线评测系统 Online Judge 在我刚听到堆这个名词的时候,我认为它是一堆东西的集合... 但其实吧它是利用完全二叉树的结构来维护一组 ...
- 数据结构&堆&heap&priority_queue&实现
目录 什么是堆? 大根堆 小根堆 堆的操作 STL queue 什么是堆? 堆是一种数据结构,可以用来实现优先队列 大根堆 大根堆,顾名思义就是根节点最大.我们先用小根堆的建堆过程学习堆的思想. 小根 ...
- 基本数据结构 —— 堆以及堆排序(C++实现)
目录 什么是堆 堆的存储 堆的操作 结构体定义 判断是否为空 往堆中插入元素 从堆中删除元素 取出堆中最大的元素 堆排序 测试代码 例题 参考资料 什么是堆 堆(英语:heap)是计算机科学中一类特殊 ...
- 算法与数据结构基础 - 堆(Heap)和优先级队列(Priority queue)
堆基础 堆(Heap)是具有这样性质的数据结构:1/完全二叉树 2/所有节点的值大于等于(或小于等于)子节点的值: 图片来源:这里 堆可以用数组存储,插入.删除会触发节点shift_down.shif ...
- python数据结构之堆(heap)
本篇学习内容为堆的性质.python实现插入与删除操作.堆复杂度表.python内置方法生成堆. 区分堆(heap)与栈(stack):堆与二叉树有关,像一堆金字塔型泥沙:而栈像一个直立垃圾桶,一列下 ...
- 堆heap和栈Stack(百科)
堆heap和栈Stack 在计算机领域,堆栈是一个不容忽视的概念,堆栈是两种数据结构.堆栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除.在单片机应用中,堆栈 ...
- C 数据结构堆
引言 - 数据结构堆 堆结构都很耳熟, 从堆排序到优先级队列, 我们总会看见它的身影. 相关的资料太多了, 堆 - https://zh.wikipedia.org/wiki/%E5%A0%86%E7 ...
- (转)堆heap和栈stack
一 英文名称 堆和栈是C/C++编程中经常遇到的两个基本概念.先看一下它们的英文表示: 堆――heap 栈――stack 二 从数据结构和系统两个层次理解 在具体的C/C++编程框架中,这两个概念并不 ...
- java数据结构----堆
1.堆:堆是一种树,由它实现的优先级队列的插入和删除的时间复杂度都是O(logn),用堆实现的优先级队列虽然和数组实现相比较删除慢了些,但插入的时间快的多了.当速度很重要且有很多插入操作时,可以选择堆 ...
随机推荐
- win7安装时,避免产生100m系统保留分区的办法
在通过光盘或者U盘安装Win7操作系统时,在对新硬盘进行分区时,会自动产生100m的系统保留分区.对于有洁癖的人来说,这个不可见又删不掉的分区是个苦恼.下面介绍通过diskpart消灭保留分区的办法: ...
- Web大前端时代之:HTML5+CSS3入门系列
准备来一波新技术,待续.... Old: 联系源码:https://github.com/dunitian/LoTHTML5 文档下载:https://github.com/dunitian/LoTD ...
- JavaScript 对象属性介绍
本篇主要介绍JS中对象的属性,包括:属性的分类.访问方式.检测属性.遍历属性以及属性特性等内容. 目录 1. 介绍:描述属性的命名方式.查找路径以及分类 2. 属性的访问方式:介绍'.'访问方式.'[ ...
- 在开启DRS的集群中修复VMware虚拟主机启动问题
通过iSCSI方式连接到ESXi主机上的外挂存储意外失联了一段时间,导致部分虚拟主机在集群中呈现出孤立的状态,单独登陆到每台ESXi上可以看到这些虚拟主机都变成了unknow状态.因为有过上一次(VM ...
- C++随笔:从Hello World 探秘CoreCLR的内部(1)
紧接着上次的问题,上次的问题其实很简单,就是HelloWorld.exe运行失败,而本文的目的,就是成功调试HelloWorld这个控制台应用程序. 通过我的寻找,其实是一个名为TryRun的文件出了 ...
- Android Studio —— 创建Menu菜单项
大多数android程序的右上角都会设置一个菜单按钮比如微信的界面右上角的加号. 这个需要在layout同级目录下新建文件夹命名为menu,再右击新建的menu新建xml文件:
- iOS之ProtocolBuffer搭建和示例demo
这次搭建iOS的ProtocolBuffer编译器和把*.proto源文件编译成*.pbobjc.h 和 *.pbobjc.m文件时,碰到不少问题! 搭建pb编译器到时没有什么问题,只是在把*.pro ...
- Ubuntu安装redis并配置远程、密码以及开启php扩展
一.前言 redis是当前流行的nosql数据库,很多网站都用它来做缓存,今天我们来安装并配置下redis 二.安装并配置redis 1.安装redis sudo apt-get install re ...
- Linux网络属性配置
目录 IP地址分类 如何将Linux主机接入到网络中 网络接口的命名方式 ifcfg系列命令 如何配置主机名 如何配置DNS服务器指向 iproute2系列命令 Linux管理网络服务 永久生效配置路 ...
- 换个角度看微信小程序[推荐]
去年参加几次技术沙龙时,我注意到一个有意思的现象:与之前大家统一接受的换名片不同,有些人并不愿意被添加微信好友--"不好意思,不熟的人不加微信". 这个现象之所以有意思,是因为名片 ...