简介
堆(heap)是一种树型数据结构,具有以下特点:
- 堆中某个节点的值总是不大于或不小于其父节点的值
- 堆总是一棵完全二叉树
根节点最大的堆称为大根堆,相反则称之为小根堆。
堆可以用于排序。
例如图中就是一个大根堆: 大根堆
代码
大根堆较小根堆更容易实现,以下,我们用C++来实现大根堆模板。
我们用class
来封装堆的模板类:
1 | const int MAX_SIZE = 100000; |
我们定义了堆的最大空间MAX_SIZE
为100000
,然后用数组ele
来储存元素。tot
用于储存当前元素数量,同时也是最后一个元素的位置。
编号
我们给每个节点编号,可以发现编号为n的节点的左子节点为2n、右子节点为2n+1、父节点为⌊n2⌋,因此,我们用三个私有方法le(int n)
、rt(int n)
、dad(int n)
分别表示n
号元素的左子节点、右子节点和父节点的位置:
1 | int Heap::le(int n) { |
构造函数Heap()
用于初始化:
1 | Heap::Heap() { |
我写的堆模板中有5个公有方法:
push(int e)
:把元素e
压入堆,成功返回true
pop()
:弹出根节点元素,成功返回true
empty()
:若堆为空返回true
top()
:返回根节点元素size()
:返回节点数
我们看这张图,如果要把20压入堆,应如何实现呢? E.G. Step 1 Step 2 Step 3
首先,在尾部添加一个元素20:
然后,我们将它与父节点11比较,发现20>11,不符合大根堆的性质。于是我们将其与父节点交换:
交换后,20仍然大于其父节点,也就是根节点12,因此,我们可以再进行一次交换:
这样就完成了把20压入堆的操作。
由此可见,每次插入操作都需要对堆进行调整,因此,时间复杂度为O(logn)。
代码实现:
1 | bool Heap::push(int e) { |
注意最开始有一个判断tot == MAX_SIZE - 1
,这是为了防止溢出。
如果要删除根节点呢? E.G. Step 1 Step 2
还是刚才那张图,我们可以先把根节点20与最后一个节点11交换,然后删除这个节点:
由于根节点最大的性质,我们必须要让一个最大的数来代替11成为根节点。11的2个子节点中,只有12满足条件,因此要交换11和12:
就完成了删除操作。
同样,删除操作的时间复杂度也是O(logn),代码如下:
1 | bool Heap::pop() { |
剩下3个方法就很好理解了:
1 | bool Heap::empty() { |
这样基本上就能实现堆的所有功能了,代码完整版:
1 | const int MAX_SIZE = 100000; |
另外,我们可以用template
实现真正的模板类:
1 | const int MAX_SIZE = 100000; |
用Heap<type>
的方式调用即可,例如声明一个long long
类型的大根堆hp
:Heap<long long> hp
。
堆排序
利用堆的子节点总比父节点小或大的性质,就可以进行O(nlogn)的排序。
实现过程很简单,只要将待排序数列一个一个压入堆,然后一个一个输出根节点并弹出即可。
大根堆可以从大到小排序,小根堆可以从小到大排序。
用上面的大根堆模板对数组进行从大到小的排序:
1 | template<class type> |
STL
STL提供了一种类似于堆的数据结构:优先队列(priority queue)。priority_queue
包含在头文件queue
中,共有3个参数:
1 | template <class T, class Container = vector<T>, |
T
:元素类型Container
:用于储存元素的容器类型,元素类型必须与T
一致,默认为vector
Compare
:比较方式,默认为less
即小于(实现大根堆),传入greater
可以实现小根堆
例如,我们要定义一个double
类型的小根堆pq
,可以用:
1 | priority_queue<double, vector<double>, greater<double> > pq; |
priority_queue
同样有5个方法:
push()
pop()
empty()
top()
size()
这5个方法作用同上。
v1.5.2