-
堆的基础知识
- 堆是一种有序的二叉树,分为大堆和小堆。其中大堆的父结点的值大于或等于子结点的值,而小堆父结点的值则小于或等于孩子结点的值。堆的特点就是根结点要么最大,要么最小,所以经常用堆的特性来求最值。
- 堆可以用数组存储。若用a[0:n]来存储堆的元素,若已知a[i]是父结点(2*i+2<=n),则a[2*i+1]和a[2*i+2]分别为左右孩子结点。或者已知a[i]为孩子结点(0<i<=n),则父结点为a[ (i-1)/2]。
- 堆结点的插入。堆把要插入的元素放在堆最后的叶子结点,然后再进行Up操作完成插入。若用len表示堆结点的个数,用a[n]来存储堆,堆插入num可以用一句代码实现。
-
a[len++] = num;
- 堆的Up。以大堆为例,堆的Up参考代码如图2-1所示。类似,小堆只需对 "t > a[j]" 的 ">" 换成 "<",再修改函数名称即可。
-
void MaxHeapUp(int a[], int i) {//a[0 : n]中第i个元素进行up操作 int t = a[i]; int j = ( i -1) / 2; while( i > 0 && t > a[j]) { a[i] = a[j]; i = j; j = ( i - 1) / 2; } a[i] = t; }
图2-1 大堆Up
- 堆结点的删除。删除堆结点通常是删除根结点,即把堆的最后一个叶子结点覆盖根结点,然后再对新的根结点进行Down操作。参考代码如图2-2所示。
-
void MaxHeapDown(int a[], int i, int n) {//对a[0:n]中的第i个元素进行down操作 int t = a[i]; int j = 2 * i + 1; while(j+1 <= n) { j = a[j] > a[j+1] ? j : j+1; if( t >= a[j])break; a[i] = a[j]; i = j; j = 2 * i + 1; } a[i] = t; }
-
堆的编程练习
- 题目:TOJ 2196 Nuanran's Idol
- 大意:Nuanran喜欢收集照片,他根据不同的喜欢程度,给每张照片评定一个分数,分数越高,代表越喜欢。而他送给朋友的照片都是分数最低的。
- 算法:小堆。构建一个小堆,每次买进(Buy)新照片,插入到小堆最后一个结点,然后进行Up操作;每次送给(Give)朋友的照片分数即根结点的值,用最后叶子结点覆盖根结点,并对新的根结点进行Down操作。我的代码如图2-3所示。
-
#include <iostream> using namespace std; const int maxn = 100000;//a[i]进行up操作 void MinHeapUp(int a[], int i); void MinHeapDown(int a[], int i, int Length); int main() { int n, score, heap[maxn], Length; char c; while(cin>>n && n) { Length = 0; while(n --) { cin>>c; if(c == 'B') { cin>>score; //insert score and update Length heap[Length++]= score; //a[Length-1] up MinHeapUp(heap, Length-1); } else if(c == 'G') { //output what the given score is cout<<heap[0]<<endl; //delete root, swap[a[0], a[i]] and down heap[0] = heap[Length-1]; MinHeapDown(heap, 0, Length); } else cout<<"Invalid input!"<<endl; } } return 0; } void MinHeapUp(int a[], int i) { int t = a[i]; int j = ( i - 1) / 2; while( i > 0 ) { if(t >a[j]) break; a[i] = a[j]; i = j; j = ( i - 1) / 2; } a[i] = t; }void MinHeapDown(int a[], int i, int Length) { int t = a[i]; int j = 2 * i + 1; while( j + 1 <= Length) { j = a[j] > a[j+1] ? j+1: j ; //取左右孩子较小者 if( t < a[j]) break; a[i] = a[j]; i = j; j = 2 * i +1; } a[i] = t; }
图2-3 TOJ 2096的参考代码
- 题目:TOJ 3488 Stone
- 大意:把N堆石头搬到某地成为一堆。每次把两堆石头合并成一堆,需要消耗的能量值是石堆中石头的数量之和。给定N堆石头以及每堆石头的石头数量,计算最终搬石头消耗能量的最小值。
- 算法:把每堆石头的数量值作为结点的值建立小堆。搬走一堆石头块数最少的石堆,取一次根结点的值,并删除根结点,调整,重新进行一遍。把搬走的两堆石头放在一起,插入,并重复之前的过程。直到石头的堆数只剩下一堆结束。我的代码如图2-4所示。
-
#include <iostream> using namespace std; void Up(int a[], int i) {//a[0 : n] int t = a[i]; int j = ( i -1) / 2; while( i > 0 && t < a[j]) { a[i] = a[j]; i = j; j = ( i - 1) / 2; } a[i] = t; } void Down(int a[], int i, int n) { int t = a[i]; int j = 2 * i + 1; while(j+1 <= n) { j = a[j] < a[j+1] ? j : j+1; if( t <= a[j])break; a[i] = a[j]; i = j; j = 2 * i + 1; } a[i] = t; } int main() { int T, piles, i, a[100000], len, sum, cost; cin>>T; while(T --) { cin>>piles; len = 0; for(i=0; i<piles; i++) {//建立小堆 cin>>a[i]; len++; Up(a, i); } //计算搬石头的最少能量消耗 sum = 0; while(len > 1) { cost = 0; //先搬走数量最少的石堆 cost += a[0]; //重新调整 a[0] = a[--len]; Down(a, 0, len); //再次搬走数量第二少的石堆 cost += a[0]; a[0] = a[--len]; Down(a, 0, len); //把两次搬走的石堆放在一起和剩下的石堆进行调整 a[len++] = cost; Up(a, len-1); sum += cost; } cout<<sum<<endl; } return 0; }
图2-4 TOJ 3488 stone的参考代码
- 题目:TOJ 3515 Middle Number
- 大意:先给T次测试次数,给定N个数,M次操作(加入新的数或者输出中位数)。其中1<=N<=100000, 1<=N<=100000。
- Sample Input
-
1 6 1 2 13 14 15 16 5 add 5 add 3 mid add 20 mid
- Sample Output
-
5 13
- 算法:分别建立一个大堆和小堆,让这两个堆的结点数目之差为0或1。假设len1和len2(|len1-len2|<2)分别为大堆heap1[]和小堆heap2[]的结点数, 判断中位数如图2-5所示。
-
IF len1 == len2 OR len1 - len2 == 1 mid = heap1[0]ELSE mid = heap2[0]
图2-5 判断中位数
- 我的代码如图2-6所示。
-
#include <iostream> #include <string> using namespace std; void MinHeapUp(int a[], int i) {//a[0 : n]中第i个元素进行up操作 int t = a[i]; int j = ( i -1) / 2; while( i > 0 && t < a[j]) { a[i] = a[j]; i = j; j = ( i - 1) / 2; } a[i] = t; } void MaxHeapUp(int a[], int i) {//a[0 : n]中第i个元素进行up操作 int t = a[i]; int j = ( i -1) / 2; while( i > 0 && t > a[j]) { a[i] = a[j]; i = j; j = ( i - 1) / 2; } a[i] = t; } void MinHeapDown(int a[], int i, int n) {//对a[0:n]中的第i个元素进行down操作 int t = a[i]; int j = 2 * i + 1; while(j+1 <= n) { j = a[j] < a[j+1] ? j : j+1; if( t <= a[j])break; a[i] = a[j]; i = j; j = 2 * i + 1; } a[i] = t; } void MaxHeapDown(int a[], int i, int n) {//对a[0:n]中的第i个元素进行down操作 int t = a[i]; int j = 2 * i + 1; while(j+1 <= n) { j = a[j] > a[j+1] ? j : j+1; if( t >= a[j])break; a[i] = a[j]; i = j; j = 2 * i + 1; } a[i] = t; } int main() { int T, N, M, i, a[100000], mid, num, len1, len2, heap1[100000], heap2[100000]; string op; cin>>T; while(T--) { //输入N个正整数 cin>>N; for(i=0; i<N; i++) cin>>a[i]; //建立大堆heap1和小堆heap2 len1=1, len2=0, heap1[0] = a[0], mid = heap1[0]; for(i=1; i<N; i++) { if(a[i] <= mid) { heap1[len1++] = a[i]; MaxHeapUp(heap1, len1-1); } else { heap2[len2++] = a[i]; MinHeapUp(heap2, len2-1); } //平衡堆的整数数量,找出中位数 while( len1 - len2 > 1) {// if (len1 - len2) > 1 then 大堆顶元素插入到小堆并删除大堆顶元素 heap2[len2++] = heap1[0]; MinHeapUp(heap2, len2-1); heap1[0] = heap1[len1-1]; MaxHeapDown(heap1, 0, --len1); } while( len2 - len1 >1) {// if (len2 - len 1) > 1 then 小堆顶元素插入到大堆并删除小堆顶元素 heap1[len1++] = heap2[0]; MaxHeapUp(heap1, len1-1); heap2[0] = heap2[len2-1]; MinHeapDown(heap2, 0, --len2); } if(len1 == len2 || len1 - len2 == 1) mid = heap1[0]; else mid = heap2[0]; } //输入M个操作, 并更新中位数, op=add x , op = mid cin>>M; for(i=0; i<M; i++) { cin>>op; if(op == "add") {//num <= mid, 插入大堆heap1,否则插入小堆heap2 cin>>num; if(num <= mid) { heap1[len1++] = num; MaxHeapUp(heap1, len1-1); } else { heap2[len2++] = num; MinHeapUp(heap2, len2-1); } while( len1 - len2 > 1) {// if (len1 - len2) > 1 then 大堆顶元素插入到小堆并删除大堆顶元素 heap2[len2++] = heap1[0]; MinHeapUp(heap2, len2-1); heap1[0] = heap1[len1-1]; MaxHeapDown(heap1, 0, --len1); } while( len2 - len1 >1) {// if (len2 - len 1) > 1 then 小堆顶元素插入到大堆并删除小堆顶元素 heap1[len1++] = heap2[0]; MaxHeapUp(heap1, len1-1); heap2[0] = heap2[len2-1]; MinHeapDown(heap2, 0, --len2); } if(len1 == len2 || len1 - len2 == 1) mid = heap1[0]; else mid = heap2[0]; } else if(op == "mid") { cout<<mid<<endl; } else cout<<"Invalid!\n"; } } return 0; }
来源:https://www.cnblogs.com/vsky/p/5007353.html