堆是一种数据结构,堆排序是用这种数据结构解决排序问题,它的一个典型应用就是优先队列。
普通队列:先进先出,后进后出,由时间顺序决定出队的顺序,
优先队列:出队顺序和入队顺序无关;和优先级有关
在操作系统中动态管理调度任务就是使用优先队列,优先队列适合于处理动态的情况。
优先队列的主要操作:入队、出队(取出优先级最高的元素)
优先队列的实现:
可以用数组实现优先队列,而用堆这种数据结构,效率比数组要高,对于总共N个请求:使用普通数组或者顺序数组,最差的情况,时间复杂度是O(n^2),而使用堆,则是O(nlgn)。
堆的实现
堆是树形结构,一种经典的堆的实现是二叉堆,它是一颗二叉树,它的每一个子节点的值,都不大于其父节点的值,另一个性质是,必须是一颗完全二叉树,满足这两个性质的数据结构就是堆,特别的,下图展示的是一个最大堆,因为树顶位置总是最大的元素,而相应的每个子节点不小于其父节点的值构成的是最小堆。
这里以最大堆做分析。
用组数存储二叉堆,由于堆是一个完全二叉树,因此可以用数组来存储二叉堆,按照层序从上到下,每一层从左到右,以1作为起始号标上序号,如下图,可以看出一个规律,每个节点的左节点的序号是该节点序号的2倍,右节点的序号是自身序号的2倍加1。
这样我们就可以把所有这些数据存在数组中,而相应的我们标记的序号对应数组中的索引,上面的堆可以用下面的数组存储。
在数组中我们可以用下面的公式来找到数组中每个索引的元素相应的左孩子、右孩子和父节点。
parent(i) = i/2
left child (i) = 2i
right child (i) = 2i + 1
Heapify实现由数组构成堆
给定一个数组,我们可以按照上面的规则构造一颗完全二叉树,此时的完全二叉树还不是最大堆。如下图中所示,而对于一颗完全二叉树,所有的叶子节点本身就是一个最大堆,图中有5个(蓝色的节点)只含有1个元素的最大堆。
有一个规律是,第一个非叶子节点的索引,是这颗完全二叉树的节点个数除以2得到的结果,图中是5,(从索引1开始计数得出的规则)。
接下来从后向前依次考察每一个不是叶子节点的节点,上图示例中我们最先看的是22这个节点,以这个元素为根构成的这颗子树不满足最大堆的性质,因此将22和它的子节点做一次比较,发现22小于62,于是两者交换一下位置,此时,以索引5对应元素为根构成的这个子树满足了最大堆的性质,如下图所示
接下来看13这个元素,将13和它的左右子节点作比较,13比30和41都小,将30和41中的较大者与13交换位置,这样一来,以索引4对应元素为根构成的这个子树也满足了最大堆的性质。
然后,我们再看下一个非叶子节点19(索引3所在的元素),将它和左右叶子节点比较,然后与28交换位置,使得以索引3对应元素为根构成的这个子树也满足了最大堆的性质。
然后是看17这个节点,类似的与子节点比较大小,与62交换一次位置,此时由于17还有子节点,所以还要继续和它的子节点22比较大小,并交换位置,此时以索引2对应元素为根构成的子树满足了最大堆的性质,如下图所示。
最后看树的根15,同样的依次和它的左右子节点比较大小,并和其中较大者交换位置,依次和42交换位置,和41交换位置,和30交换位置,得到如下图的结果。
此时完成了将一开始的数组整理成为最大堆的过程。
将n个元素逐个插入到一个空堆中,实现一个堆,算法复杂度是O(nlogn),而Heapify过程使得一个数组变成一个堆,算法复杂度是O(n)。
代码实现(C++):
#include <iostream>
#include <algorithm>
#include <string>
#include <ctime>
#include <cmath>
#include <cassert>
using namespace std;
template<typename Item>
class MaxHeap{
private:
Item *data;
int count; //堆中元素的数量
}
void shiftDown(int k){
while( 2*k <= count ){
int j = 2*k; // 在此轮循环中,data[k]和data[j]交换位置
if( j+1 <= count && data[j+1] > data[j] )
j ++;
// data[j] 是 data[2*k]和data[2*k+1]中的最大值
if( data[k] >= data[j] ) break;
swap( data[k] , data[j] );
k = j;
}
}
public:
//构造函数
MaxHeap(Item arr[],int n){
data = new Item[n+1]; //开辟空间
capacity = n; //容量
for( int i = 0; i < n ; i++ )
data[i+1] = arr[i]; //遍历数组把值复制给data
count = n; //堆中元素的个数
for( int i = count/2 ; i >= 1 ; i-- )
shiftDown(i); //Heapify的过程
}
//析构函数,释放空间
~MaxHeap(){
delete[] data;
}
//成员函数,返回堆中元素个数
int size(){
return count;
}
//成员函数,堆中元素是否为0
bool isEmpty(){
return count == 0;
}
Item extractMax(){
assert( count > 0 );
Item ret = data[1];
swap( data[1] , data[count] );
count --;
shiftDown(1);
return ret;
}
template<typename T>
void heapSort(T arr[], int n){
MaxHeap<T> maxheap = MaxHeap<T>(arr,n);//将数组变成堆
for( int i = n-1 ; i >= 0 ; i-- )
arr[i] = maxheap.extractMax();
}