在学习计算机编程的时候,堆栈
是我们最经常使用到的概念,也是2种最基本的内存管理方式。可是在数据结构算法也有一个堆
,还有叫二叉堆
的,那么堆栈的堆、堆、和二叉堆,到底是什么,它们之间有关系吗,本文就是来解密。
先说结论,数据结构中的堆
和二叉堆
是一个概念,它们和内存管理中的堆
没有半毛钱的关系。为了区别说法,本文我们称前者是二叉堆
,后者叫内存堆
。
内存堆
内存堆通常和栈出现在一起叫内存堆栈:
- 栈:是指函数调用是参数和局部变量的存储方式。在函数调用的时候由系统分配一块连续内存,局部变量都存在在栈中,在函数结束后统一释放。函数间的调用关系也是由函数栈通过先进先出的规则管理。
- 堆:是动态分配的,需要程序主动控制。
二叉堆
设想我们有一个垃圾桶问题。假设:
- 总共有7个固定的放垃圾桶的位置
- 垃圾车隔10分钟来一次,每次把一个最重的垃圾桶拉走
- 居民每隔10分钟会放一个新的垃圾桶,重量不定
要求设计一个方案,使得工作最轻松。
方案一,没有任何设计的情况
- 垃圾车取垃圾时,要每个秤一遍,选取最重的后拖走
- 居民看到空的位置放上去。
这个方案,垃圾车每次都需要:秤7次。
方案二,每次都排序好
既然,垃圾车每次都要秤7次,不如我们先排好序,方案如下:
- 垃圾车每次都在1号位置取
- 居民没次把垃圾桶排好序。
这个方案优化了垃圾车都是一次搬走,但是对于居民每次要排序,怎么排呢?最节省的方式是用二分查找,如下图:
先秤4号位置的垃圾桶,如果更轻,则再比较6号位置的,如果更轻,则最后比较7号位置的,如果还是更轻,则所有的桶需要向前移动一位,然后放到7号位置,如下图:
该方案的最坏情况,每次需要:秤3次,移6次
二叉堆出场
先二叉堆
排序,满足以下要求:
-
全二叉树
(即:一定要先填满上面的) - 父节点比子节点重:如上所示,1比2和3重,2比4和5重,3比6和7重。
注意:这不是二叉搜索树,二叉搜索树是父节点比左子节点重,但比右子节点轻。
-
二叉堆
看着是二叉树,实际上在存储中还是使用数组,由于是全二叉树
,子节点可以通过2*k
和2*k + 1
来定位。比如对于3号位置,其子节点就是3*2=6
和3*2+1=7
号位置。
先看垃圾车的操作,最重在1号位置,所以1次搬走。
再看居民的操作,假设新来一个垃圾桶是6kg:
第一步:首先填上空着的1号位置,如下图。之后,要把1号位置下沉到合适位置。
-
第二步:先和两个子节点比较,即2和3号位置,如果都比它们重则完成,否则和最重的交换位置,本例中,和3号交换位置如下。这一步:秤2次,移动1次
-
第三步:先和两个子节点比较,即6和7号位置,如果都比它们重则完成,否则和最重的交换位置,本例中,和7号位置交换。这一步:秤2次,移动1次。
最坏的情况:秤4次,移2次
比较3种算法
方案 | 操作次数 | 复杂度 |
---|---|---|
方案一 | 秤7次 | O(N) |
方案二 | 秤3次,移6次 | O(N) |
方案三 | 秤4次,移2次 | O(Log(N)) |
显然方案三最优。因为数量较少,所以方案三的优势不明显,当数量逐渐增加时,Log(N)和N的差距就会越来越大。当有10000个垃圾桶时,以下是数据,这个优势就是数量级的:
方案 | 操作次数 | 复杂度 |
---|---|---|
方案一 | 秤10000次 | O(N) |
方案二 | 秤13次,移9999次 | O(N) |
方案三 | 秤14次,移14次 | O(Log(N)) |
总结
- 内存堆栈的堆和数据结构中的堆,没有联系,它们只是都有上面小下面大,有点像米堆,呵呵;
-
二叉堆
和二叉搜索树
的区别是,前者父节点比子节点都大,后者是父节点比左节点大,比右节点小; -
二叉堆
算法被普遍用于调度算法中,其核心操作是获取最大的
和插入
。二叉堆
保证所有操作的复杂度在log(N)。