1、二叉堆
二叉堆是一颗完全二叉树,并且满足其某个结点的值总是不大于或者是不小于其父节点的值
根元素比较大的叫大根堆,反之则叫小根堆
建堆的时间复杂度为O(n)
在堆中插入删除并调整堆的时间复杂度都是O(log(n))的,因此相比于优先队列,有应用的必要
一般的二叉堆支持的操作有插入任意值并调整堆,删除最小值并调整堆,调整堆之后我们可以O(1)取得最小的结点,这是相对于小根堆而言的
这里补充说明一下优先队列不能完成但是二叉堆可以完成的事情,那就是删除指定位置元素的值
优先队列只允许弹出堆顶元素,但是一般的二叉堆可以删除任意元素
1 inline void remove(int p) 2 { 3 q[p]=q[n--]; 4 up(p),down(p); 5 }
我们这里给出两种堆实现方式
第一种堆实现方式是自己手写(*^▽^*)
首先给出插入和删除元素并调整堆的实现函数
void insert(int x) { a[++n]=x; up(n); } void del() { if(n==0) return; cout<<a[1]<<endl; a[1]=a[n--]; down(1); }
插入堆元素直接把这个元素放在当前堆的最后位置即可
之后要执行调整函数,把这个新插入的元素向上进行调整,执行up(n),调整到一个合适的位置
这里的删除堆元素删除的是堆的根元素,删除的时候先输出一下纪念一下
然后我们把当前堆的最后位置的元素放在这个空出来的根元素的位置,之后执行调整堆的函数down(1),把这个位置的元素向下调整即可
接下来给出把元素向上调整以及把元素向下调整的实现,这里需要仔细去阅读并调试才容易看懂
void up(int ch) { int t=a[ch]; int gch=ch; while((gch>1)&&(a[gch/2]>t)) //需要调整堆时,即新元素所在子树的根元素比该元素大时 { a[gch]=a[gch/2]; //把新元素所在子树的根元素下移到儿子位置,相当于把一条链往下走一格,空出来上面的一个位置给新元素 //这个过程循环进行,只到到了合适的位置为止 gch=gch/2; } a[gch]=t; //替换原有根元素,原有的根元素的值是最后一次移动的元素 }
void down(int root) { //已经把原根元素的子元素放在了根元素的位置 int ch=root*2; int t=a[root]; while(ch<=n) { if(ch<=n-1&&a[ch]>a[ch+1]) //从上往下一层一层找看是否需要调整 ch++; if(t>a[ch]) //如果需要调整,那么就把下面的链往上提一格,同时更新root的值 { a[root]=a[ch]; root=ch; ch=2*root; } else break; //后面都不需要调整了 } a[root]=t; //此时的root为最后的元素的最终位置,同时t为最后的元素,因为之前已经把最后的元素给t存着了 }
接下来我们给出一个建堆并进行4次输出的完整代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cstdlib> 5 using namespace std; 6 int a[100005]; 7 int n; 8 void print() 9 { 10 cout<<"->"; 11 for(int i=1;i<=n;i++) 12 cout<<a[i]<<" "; 13 cout<<endl; 14 system("pause"); 15 } 16 void down(int root) 17 { //已经把原根元素的子元素放在了根元素的位置 18 int ch=root*2; 19 int t=a[root]; 20 while(ch<=n) 21 { 22 if(ch<=n-1&&a[ch]>a[ch+1]) //从上往下一层一层找看是否需要调整 23 ch++; 24 if(t>a[ch]) //如果需要调整,那么就把下面的链往上提一格,同时更新root的值 25 { 26 a[root]=a[ch]; 27 root=ch; 28 ch=2*root; 29 } 30 else break; //后面都不需要调整了 31 } 32 a[root]=t; //此时的root为最后的元素的最终位置,同时t为最后的元素,因为之前已经把最后的元素给t存着了 33 } 34 void up(int ch) 35 { 36 int t=a[ch]; 37 int gch=ch; 38 while((gch>1)&&(a[gch/2]>t)) //需要调整堆时,即新元素所在子树的根元素比该元素大时 39 { 40 a[gch]=a[gch/2]; //把新元素所在子树的根元素下移到儿子位置,相当于把一条链往下走一格,空出来上面的一个位置给新元素 41 //这个过程循环进行,只到到了合适的位置为止 42 gch=gch/2; 43 } 44 a[gch]=t; //替换原有根元素,原有的根元素的值是最后一次移动的元素 45 } 46 void insert(int x) 47 { 48 a[++n]=x; 49 up(n); 50 } 51 void del() 52 { 53 if(n==0) 54 return; 55 cout<<a[1]<<endl; 56 a[1]=a[n--]; 57 down(1); 58 } 59 int main() 60 { 61 int m; 62 cin>>m; 63 int x; 64 for(int i=1;i<=m;i++) 65 { 66 cin>>x; 67 insert(x); 68 } 69 del(); 70 del(); 71 del(); 72 del(); 73 return 0; 74 }
说完了手写堆,我们再说STL堆,STL堆是STL其他一些数据结构的底层实现,它没有专门与之相对应的头文件
STL堆默认是大根堆,如果我们在建堆的时候想建小根堆,只需要这样做就可以了
make_heap(a.begin(),a.end(),greater<int>());
我们的堆数据结构都是基于数组的,STL堆强烈建议使用vector来实现
接下来给出建堆的代码:
for(int i=0;i<n;i++) { int x; cin>>x; a.push_back(x); } print(); make_heap(a.begin(),a.end());
数组的下标是从0开始的一定要记住
我们的入堆操作也是跟刚才一样分成两个步骤,把这个新元素直接放在数组的最后,再调用函数把它加入堆
a.push_back(10); push_heap(a.begin(),a.end()); //将最后一个元素入堆
当我们要删除一个元素的时候,是先将堆顶元素移动到最后位置,然后执行vector操作就可以了
pop_heap(a.begin(), a.end()); //将堆顶元素移动到最后一个位置 print(); a.pop_back(); //删除最后一个元素
当然,执行堆排序也就很容易了
sort_heap(a.begin(),a.end()); //堆排序
接下来给出完整的代码,一定注意要包含所有的头文件不要有遗漏
1 //aininot260 2 //默认大根堆 3 //小根堆 make_heap(a.begin(),a.end(),greater<int>()); 4 #include<iostream> 5 #include<functional> 6 #include<vector> 7 #include<algorithm> 8 using namespace std; 9 vector<int> a; 10 void print() 11 { 12 for(int i=0;i<a.size();i++) 13 cout<<a[i]<<" "; 14 cout<<endl; 15 } 16 int n; 17 int main() 18 { 19 cin>>n; 20 for(int i=0;i<n;i++) 21 { 22 int x; 23 cin>>x; 24 a.push_back(x); 25 } 26 print(); 27 make_heap(a.begin(),a.end()); 28 print(); 29 a.push_back(10); 30 push_heap(a.begin(),a.end()); //将最后一个元素入堆 31 print(); 32 pop_heap(a.begin(), a.end()); //将堆顶元素移动到最后一个位置 33 print(); 34 a.pop_back(); //删除最后一个元素 35 print(); 36 sort_heap(a.begin(),a.end()); //堆排序 37 print(); 38 return 0; 39 }
2、优先队列
首先贴上一个例程:
1 #include<iostream> 2 #include<queue> 3 #include<functional> 4 using namespace std; 5 struct number1 6 { 7 int x; 8 bool operator <(const number1 &a) const 9 { 10 return a.x<x; //最小值优先 11 } 12 }; 13 struct number2 14 { 15 int x; 16 bool operator <(const number2 &a) const 17 { 18 return a.x>x; //最大值优先 19 } 20 }; 21 int main() 22 { 23 priority_queue<int> q1; 24 //默认是最大值优先 25 priority_queue<int,vector<int>,greater<int> >q2; 26 //如果你想使用最小值优先,加个参数就好了 27 priority_queue<number1>q3; 28 priority_queue<number2>q4; 29 //结构体的优先级前面已经提到了 30 for(int i=1;i<=10;i++) 31 q1.push(i); 32 while(!q1.empty()) 33 { 34 cout<<q1.top()<<" "; 35 q1.pop(); 36 } 37 cout<<endl; 38 return 0; 39 }
有一点需要特别注意,就是谁优先的问题
比如
return a.x>x; //最大值优先 return x<a.x; //最大值优先
我们可以看到第二行是默认的,是系统默认的,如果我们在给结构体写重载函数的时候,按照第二行写是默认序
如果是默认序,那么排序的时候就是从小到大那么排的
如果你改动了这个默认序,那么排序的时候就是从大到小排的
默认序对应的默认优先级是大的排在前面,如果你改动了这个默认序,那么优先级就会变成小的在前面了
千万不要搞混