堆是一棵完全二叉树,如果其父亲结点的值均比孩子结点大,称大顶堆,反之称为小顶堆,学过数据结构知道用数组表示完全二叉树最为简便,自然而然,我们这里也就用数组来表示堆。
如果数组是从序号1开始,对一棵完全二叉树,下列点如果存在,则对结点i/2是父亲结点,2i和2i+1是左右孩子结点。
1.堆
以建大顶堆为例:
建堆过程可以通过比赛规则来模拟,两两对决决出一个强者,强者再对决…最后在最顶上的就是最强者,这样形成的完全二叉树可以保证每一个父亲结点比其下面的孩子结点要强,只要我们保证所有父亲结点满足上述条件就可以了,而在保证每一个父亲结点是不是最大时,自然要和他下面的孩子结点比,这样由上相下比就是一个向下调整的过程,这个过程会重复多次,不妨先建立一个向下调整的函数
void downadjust(int low,int n){ //向下调整操作 ,low为父亲结点,共n个元素 int p=2*low; while(p<=n){ int temp=p; if(p<n&&heap[p]<heap[p+1])temp=p+1; //找出孩子较大个 if(heap[p/2]<heap[temp]) //没有它大,调下去 { swap(heap[p/2],heap[temp]); p=temp*2; } else { break;} } }
现在考虑建堆,完全二叉树的父亲结点共n/2个,只要对这些结点向下调整即可,唯一值得思考是从第一个开始还是从最后一个开始,实际上面的那个向下调整函数是默认下面是大根堆的(思考一下为什么),所以自然是从最后一个父亲结点开始。
void createheap(int n) { //建立大根堆 for(int i=n/2;i>=1;i--) downadjust(i,n); }
建堆完成后考虑一下删除元素和加入元素,删除最大元素的话,只要把最后一个元素发在堆顶,视堆共n-1,个元素进行一次向下调整即可。
那如何加入元素呢?这里我们直接把元素放在最后一位,对比向下调整,我们来一次向上调整即可。
void upadjust(int n) { //向上调整操作 int temp=n/2; while(temp>=1){ if(heap[n]>heap[temp]){ swap(heap[n],heap[temp]); n=temp; temp=n/2; } else{break;} } }
2.堆排序
堆排序实际是一种选择排序,正是利用了堆,选择效率比简单选择排序快了许多,我们可以利用大根堆每次把最大元素选出来,放在数列末尾,有点类似于堆删除元素,重复n-1次:
void heapsort(int n){ createheap(n); for(int i=n;i>1;i--) { swap(heap[1],heap[i]); downadjust(1,i-1); } }
最后附完整的编程代码:
#include <iostream> #include<algorithm> using namespace std; const int maxn=101; int heap[maxn]; void downadjust(int low,int n){ //向下调整操作 int p=2*low; while(p<=n){ int temp=p; if(p<n&&heap[p]<heap[p+1])temp=p+1; //大根 if(heap[p/2]<heap[temp]) { swap(heap[p/2],heap[temp]); p=temp*2; } else { break;} } } void upadjust(int n) { int temp=n/2; while(temp>=1){ if(heap[n]>heap[temp]){ swap(heap[n],heap[temp]); n=temp; temp=n/2; } else{break;} } } void createheap(int n) { //建立大根堆 for(int i=n/2;i>=1;i--) downadjust(i,n); } void heapsort(int n){ createheap(n); for(int i=n;i>1;i--) { swap(heap[1],heap[i]); downadjust(1,i-1); } } int main(){ heap[1]=6; heap[2]=9; heap[3]=1; heap[4]=5; heap[5]=9; heapsort(5); for(int i=1;i<=5;i++) cout<<heap[i]<<" "; return 0; }