堆是用来表示元素集合的一种数据结构。与“堆内存”不同。堆的性质,第一:顺序性:任何结点的值都小于或者等于其子结点的值,这意味着最小元素位于根结点。
最大顶堆跟这个相反。第二个性质是形状:一种二叉树,最底层的叶子结点尽可能靠左分布,如果有n个结点,那么所有结点到根的距离不会超过logn。
下面用vector来实现堆:
我们规范的从下标1开始,函数定义如下:
root=1;
value(i)=x[i];
leftchild(i)=2*i;
rightchild(i)=2*i+1;
parent(i)=i/2;两个关键函数:siftup和siftdown。
#include <iostream> #include <string> #include <cstdio> #include <cmath> #include <vector> #include <algorithm> #include <sstream> #include <cstdlib> #include <fstream> #include <queue> #include <ctime> #include <set> using namespace std; int tmp[12]={12,20,15,29,23,17,22,35,40,26,51,19}; vector<int> x; void siftup(int n) { int i,p; for(i=n;i>1 && x[p=i/2]>x[i];i=p ){ int t=x[p]; x[p]=x[i]; x[i]=t; } } void siftdown(int n) { int i,c; for(i=1;(c=2*i)<=n;i=c) { if(c+1<=n && x[c+1]<x[c])c++; if(x[i]<=x[c])break; int t=x[c]; x[c]=x[i]; x[i]=t; } } int main() { x.push_back(0); for(int i=0;i<12;i++)x.push_back(tmp[i]); x.push_back(13); siftup(x.size()-1); for(int i=1;i!=x.size();i++)cout<<x[i]<<" "; cout<<endl; x[1]=18; siftdown(x.size()-1); for(int i=1;i!=x.size();i++)cout<<x[i]<<" "; cout<<endl; return 0; }
这两个函数的运行时间是O(logn)。
下面我们将一般抽象接口,实现一个优先队列,一次插入,一次extracmin,运行时间都是O(logn)都比较高效,查找并删除堆中的最小元素,然后重新
组织数组使其保持堆性质。
#include <iostream> #include <string> #include <cstdio> #include <cmath> #include <vector> #include <algorithm> #include <sstream> #include <cstdlib> #include <fstream> #include <queue> #include <ctime> #include <set> using namespace std; int tmp[12]={12,20,15,29,23,17,22,35,40,26,51,19}; vector<int> x; void siftup(int n) { int i,p; for(i=n;i>1 && x[p=i/2]>x[i];i=p ){ int t=x[p]; x[p]=x[i]; x[i]=t; } } void siftdown(int n) { int i,c; for(i=1;(c=2*i)<=n;i=c) { if(c+1<=n && x[c+1]<x[c])c++; if(x[i]<=x[c])break; int t=x[c]; x[c]=x[i]; x[i]=t; } } void insert(int t) { x.push_back(t); siftup(x.size()-1); } int extracmin() { int t=x[1]; x[1]=x[x.size()-1]; x.resize(x.size()-1); siftdown(x.size()-1); return t; } int main() { x.push_back(0); for(int i=0;i<12;i++)x.push_back(tmp[i]); insert(13); for(int i=1;i!=x.size();i++)cout<<x[i]<<" "; cout<<endl; while(x.size()>1) { cout<<extracmin()<<endl; } return 0; }
当insert和extracmin都运用到n个元素的堆时候,都需要O(logn)的时间。
下面我们考虑一种排序算法,堆排序算法:
该算法在最坏情况的运行时间也是O(nlogn),保证最坏的性能,而由于快速排序的最坏时间O(n^2);由于树是平衡的,所以函数的运行效率都很高;
该排序算法通过在同一个实现数组中包含两种抽象结构来避免使用额外的空间;
#include <iostream> #include <string> #include <cstdio> #include <cmath> #include <vector> #include <algorithm> #include <sstream> #include <cstdlib> #include <fstream> #include <queue> #include <ctime> #include <set> using namespace std; vector<int> x; void siftup(int n) { int i,p; for(i=n;i>1 && x[p=i/2]>x[i];i=p ){ int t=x[p]; x[p]=x[i]; x[i]=t; } } void siftdown(int n) { int i,c; for(i=1;(c=2*i)<=n;i=c) { if(c+1<=n && x[c+1]<x[c])c++; if(x[i]<=x[c])break; int t=x[c]; x[c]=x[i]; x[i]=t; } } int main() { x.push_back(0); int tmp[12]={12,69,17,20,51,26,23,29,35,40,21,15}; for(int i=0;i<12;i++)x.push_back(tmp[i]); for(int i=2;i!=x.size();i++)siftup(i); for(int i=x.size()-1;i>=2;i--) { int t=x[1]; x[1]=x[i]; x[i]=t; siftdown(i-1); } for(int i=1;i!=x.size();i++)cout<<x[i]<<" "; cout<<endl; }
由于是最小堆,所以排序后是降序排序。