算法手记 之 数据结构(堆)(POJ 2051)
一篇读书笔记
书籍简评:《ACM/ICPC 算法训练教程》这本书是余立功主编的,代码来自南京理工大学ACM集训队代码库,所以小编看过之后发现确实很实用,适合集训的时候刷题啊~~,当时是听了集训队final的意见买的,感觉还是不错滴。
相对于其他ACM书籍来说,当然如书名所言,这是一本算法训练书,有着大量的算法实战题目和代码,尽管小编还是发现了些许错误= =,有部分注释的语序习惯也有点不太合我的胃口。实战题目较多是比较水的题,但也正因此才能帮助不少新手入门,个人认为还是一本不错的算法书,当然自学还是需要下不少功夫的。
小编认为这本书主要针对的对象是想要全面了解算法竞赛内容的入门选手,想要单纯研习算法的同学可以啃啃《算法导论》,ACM的大神们加油消灭掉刘汝佳的大黑书吧
刚刚入门的孩纸们要和我一起fighting啊= =
话不多说,进入正题咯, (本节部分概念来源本书P25-2.1.3 堆,其余均为原创)
数据结构中所说的堆(heap)指的就是一个完全二叉树,当然可以用链表来实现,也可以用一维数组(线性)来模拟实现。
依照堆中存放的数据大小,堆分为两大类,一类是最大堆,一类是最小堆。
- 最大堆:任意一个结点的值都大于等于任一子结点的值,所以最大堆的根(root)一定是maximun。
- 最小堆:任意一个结点的值都小于等于任一子结点的值,所以最小堆的根(root)一定是minimun。
我们经常听说,heap是一种高效紧凑的数据结构,那么为什么高效而紧凑呢。用一张图来简单介绍吧。(下图中 上下分别为heap数组(用heap[]表示),heap的图示)
ps:上图中的完全二叉树可以作为heap的图示,上面绿色一栏是对应的数组(array),例如16就是heap[1],14就是heap[2]...依次类推。
我们习惯上把 14 和 10 叫做 16 的子结点,14为16的左儿子,10为16的右儿子。
那么像这样,我们就可以从数组(array)中得到heap[2],heap[3]为heap[1]的子结点,heap[4],heap[5]为heap[2]的子结点....
通过c/c++整形除法规则,得到2/2 = 1 , 3/2 = 1;因此我们把 数据所在位置 满足 s/2 =f 公式的 s 称作 f 的子结点(son), f 称作 s 的父结点(father),这样我们就可以利用数组来紧凑整齐地存储这一系列数据,下一次要调用某一位置 p 的父结点时,直接采用p/2就可以找到 p 的父结点了,同理,子结点就是p*2和p*2+1,这样存储数据是不是很整齐也很便于查找呢。(虽然小编觉得手写这个数据结构的功能时依然会有些繁琐啦= =)
那么下面我们来看一下这个数据结构的一般操作怎样通过代码实现呢?(多代码预警~~)(入门的孩纸们记得多调试就容易理解了~~fighting!)
- 删除优先级最高的元素-DeleteMin() —— 例如删除-最小堆的heap[1]
此操作分为三步:
- 直接删除根(root);
- 用最后一个元素代替root;
- 将heap向下重新调整;————第三步表示为下方被调用的 调整堆函数 - down(
1 /* 删除优先级最高的元素 */ 2 /* 最小堆为例 */ 3 4 /* heap - down_adjustment */ 5 void down(int p) //current_node 6 { 7 int q = p*2; //left_son_node 8 int a = heap[p]; 9 10 while (q < hlength) //hlength指的是heap数组的长,也就是堆中元素总数 11 { 12 if(heap[q] > heap[q+1]) //find_min_son 13 q++; 14 15 if(heap[q] < a) //complete_adjustment 16 break; 17 else 18 { 19 heap[p] = heap[q]; 20 p = q; 21 q = p*2; 22 } 23 } 24 heap[p] = a; 25 return; 26 } 27 28 /* delete_minimun_node */ 29 int DleteMin() 30 { 31 int r = heap[1]; //delete_root 32 heap[1] = heap[hlength--]; 33 down(1); //this is the key point!! 34 return r;
1 /* 删除优先级最高的元素 */ 2 /* 最小堆为例 */ 3 4 /* heap - down_adjustment */ 5 void down(int p) //current_node 6 { 7 int q = p*2; //left_son_node 8 int a = heap[p]; 9 10 while (q <= hlength) //hlength指的是heap数组的长,也就是堆中元素总数 11 { 12 if(q < hlength && heap[q] > heap[q+1]) //find_min_son 13 q++; 14 15 if(heap[q] < a) //complete_adjustment 16 break; 17 else 18 { 19 heap[p] = heap[q]; 20 p = q; 21 q = p*2; 22 } 23 } 24 heap[p] = a; 25 return; 26 } 27 28 /* delete_minimun_node */ 29 int DleteMin() 30 { 31 int r = heap[1]; //delete_root 32 heap[1] = heap[hlength--]; 33 down(1); //this is the key point!! 34 return r; 35 }
- 在堆中插入新元素-Insert(x)——依然以最小堆为例 (不懂的依然记得一步一步地来理解,最好自己调试)
操作步骤为:
- 将待insert的元素x添加到末尾;
- 向上调整;————第二步用被调用的 up() 函数表示;
1 /* 堆中插入新元素 */ 2 /* 最小堆为例 */ 3 4 /* heap - up_adjustment */ 5 void up(int p) //current_node 6 { 7 int q = p/2; //partner_node 8 int a = heap[p]; 9 10 while(q > 1 && a < heap[q]) 11 { 12 heap[p] = heap[q]; 13 p = q; 14 q = p/2; 15 } 16 heap[p] = a; 17 return; 18 } 19 20 /* Insert_new_node */ 21 void Insert(int a) 22 { 23 heap[++hlength] = a; //加长并将a加到末尾 24 up(hlength); 25 }
- 将x位置的优先级提升到 p 值:IncreaseKey(x,p);
1 /* 堆中将x优先级提至p */ 2 void IncreaseKey(int x,int p) 3 { 4 if (heap[x] < p) //若heap[x]本身小于p,那么x的优先级本来就比p高 5 return; 6 heap[x] = p; //否则将heap[x]赋值为 p 7 up(x); //调用上方函数up() 8 }
- 数组模拟建堆:Build()——(⊙o⊙)额,原谅我最后才建堆,其实这也是正常顺序啦= =
1 /* 数组建堆 */ 2 void Build() 3 { 4 for (int i = hlength / 2; i > 0; i++) 5 down(i); //调用第一次的 调整堆函数 6 }
- 向上和向下调整每层都是常数级别,共log n层,因此 调整堆 的时间度O(log n);
- 插入/删除 只调用一次向上或向下调整,因此都是O(log n);
1 //Argus-中文貌似是一个神话人物 阿尔戈斯 号称百眼巨人~~ 2 //吼吼吼= =(此情节与题目无关) 3 4 //循环维持最小堆 5 //Time:32Ms Memory:176K 6 #include<iostream> 7 #include<cstring> 8 #include<cstdio> 9 using namespace std; 10 11 #define MAX 1001 12 13 struct Argument { 14 int name; //编号 15 int now; //当前执行时间 16 int period; //周期 17 friend bool operator < (Argument &a, Argument &b) { 18 return a.now < b.now || (a.now == b.now && a.name < b.name); 19 } 20 }arg[MAX]; 21 22 int len = 1; 23 24 /*从x向下调整*/ 25 void down(int x) 26 { 27 Argument tmp = arg[x]; 28 int next = 2 * x; 29 for (int next = 2 * x; next < len;next *= 2) 30 { 31 //比较子结点优先级 32 if (next + 1 < len && arg[next + 1] < arg[next]) 33 next++; 34 //不可下调 35 if (tmp < arg[next]) break; 36 //下调 37 arg[x] = arg[next]; 38 x = next; 39 } 40 arg[x] = tmp; 41 } 42 43 void createHeap() 44 { 45 for (int i = len / 2; i > 0; i--) 46 down(i); 47 } 48 49 int main() 50 { 51 52 char command[10]; 53 while (scanf("%s", command), strcmp(command, "#")) 54 { 55 scanf("%d%d", &arg[len].name, &arg[len].period); 56 arg[len++].now = arg[len].period; 57 } 58 59 createHeap(); 60 61 int k; 62 scanf("%d", &k); 63 for (int i = 0; i < k; i++) 64 { 65 printf("%d\n", arg[1].name); 66 arg[1].now += arg[1].period; 67 down(1); 68 } 69 70 return 0; 71 }