1.堆
1.1 堆的定义与基本操作
堆是一颗完全二叉树,数中每个结点的值都不小于(或不大于)其左右孩子的结点的值。其中,如果父亲结点的值大于等于孩子结点的值,那么称这样的堆为大顶堆,这时每个结点的值都是以它为根结点的子数的最大值;如果父亲结点的值小于等于孩子结点的值,那么称这样的堆为小顶堆,这时每个结点的值都是以它为根结点的子数的最小值。堆一般用于优先队列的实现,而优先队列默认使用大顶堆,因此我以大顶堆为例来讲述,以下所指的堆都是指大顶堆。
好,那怎么具体实现呢?对于完全二叉树来说,比较简单的是实现方法是用数组来存储完全二叉树。这样结点就按层序存储于数组中,其中第一个结点将存储于数组中的1号位,,并且数组i号位表示的结点的左孩子就是2i号位,而右孩子是(2i+1)号位。于是可以像下面这样定义数组来表示堆:
const int maxn = 10;
int heap[maxn] , n = 10;
而建堆过程事实上是向下调整:,调整方法如下:
总是将当前结点V与它们的左右孩子比较(如果有的话),假如孩子中存在权值比结点V的权值大的,就将其中权值最大的那个孩子结点与结点V交换;交换完毕后继续让结点和其左孩子比较,直到结点V的的孩子的权值都比结点的权值小或是结点V不存在孩子结点。
可以写出向下调整的代码:
//对heap数组在[low,high]范围内进行向下调整
//其中low为欲调整结点的数组下标 high一般为堆的最后一个元素数组的下标
void DownAdjust(int low,int high){
int i = low , j = i*2 ;//i为欲调整结点 j为其左孩子
while(j<=high){//存在左孩子
//如果右孩子存在且右孩子的值大于左孩子的值
if(j+1<=high && heap[j+1]>heap[j]){
j = j + 1;//让j存储右孩子下标
}
//如果孩子中最大的权值比欲调整的结点i大
if(heap[j]>heap[i]){
swap(head[j],heap[i]);//交换最大权值的孩子与欲调整结点i
i = j;//保持i为欲调整结点、j为i的左孩子
j = i*2;
}else{
break;//孩子的权值均比欲调整结点i小 调整结束
}
}
}
建堆的代码如下:
//建堆 O(n)
void CreateHeap(){
for(int i = n / 2 ; i >= 1 ; i--){
downAdjust(i,n);
}
}
另外,如果要删除堆中的最大元素(也就是堆顶元素),并让其保持堆的结构,那么只需要最后一个元素覆盖堆顶元素,然后对根结点进行调整即可。
//删除堆顶元素 O(logn)
void DeleteTop(){
heap[1] = heap[n--];//用最后一个元素覆盖堆顶元素 并让元素个数减1
downAdjust(1,n);//向下调整堆顶元素
}
那么,如果要往堆里添加一个元素,应当怎么办呢?可以把想要添加的元素放在数组最后(也就是完全二叉树的最后一个结点后面),然后进行向上调整操作。向上调整就是把欲调整结点与父亲结点比较,如果权值比父亲结点大,那么就交换其与父亲结点,这样反复比较,直到达到堆顶或是父亲结点的权值较大为止。
//往堆里添加一个元素
//对heap数组在[low,high]范围进行向上调整
//和向下调整思路差不多 就是j的赋值有所改变
//其中low一般设置为1 high表示欲调整结点的数组下标
void UpAdjust(){
int i = high , j = i / 2;//i为欲调整结点 j为其父亲结点
while(j >= low){//父亲在[low,high]范围内
//如果父亲权值小于欲调整结点的值
if(heap[j]<heap[i]){
swap(heap[j],heap[i]);
i = j;//保持i为欲调整结点、j为i的父亲
j = i / 2;
}else{
break;
}//父亲权值比欲调整结点i的权值大 调整结束
}
}
2.堆排序
堆排序是指使用堆结构对一个序列进行排序。此处讨论递增排序的情况。考虑对一个堆来说,堆顶元素是最大的,因此在堆建立完毕后,堆排序的直观思路就是取出堆顶元素,然后将堆的最后一个元素替换至堆顶,再进行依次针对堆顶元素的向下调整-----如此重复,直到堆中只有一个元素为止。
具体实现时,为了节省空间,可以倒着遍历数组,假设当前访问到i号位,那么将堆顶元素与i号位的元素交换,接着在[1,i-1]范围内对堆顶元素进行一次向下调整即可。
//堆排序
void HeapSort(){
createHeap();//建堆
for(int i = n;i>1;i--){//倒着枚举 直到堆中只有一个元素
swap(heap[i],heap[1]);//交换heap
DownAdjust(1,i-1);//调整堆顶
}
}
参考资料:算法笔记-p335-堆