假期学习第一步之......学习堆排序
堆:分为小根堆和大根堆,就是说堆根的元素的关键字最小(最大);
堆排序:Heapsort;
时间复杂度:O(nlogn);
优点:当题目描述只是说输出第K大或者是最大的数据的时候比快排有优势。快排的时间复杂度为O(nLogn),但是如果是堆排序的话时间复杂度为O(klogn),如果k<<n那么还是选择堆 排序;
步骤:
1>将待排序size个数据存储在数组内。如果下标2*i<size,那么2*i为i的左子树;如果2*i+1<size,那么2*i+1为i的右子树;
2>建立初始堆。从最后一个非叶子节点开始调整,就是size/2。如果是想建小根堆的,建的初始堆堆根元素最大;如果是想建大根堆,初始堆堆根元素最小。
3>堆排序。对于第i趟堆排序,调整堆根元素和第n-i+1结点交换,保证n-i+1~n-1是有序的。
参考资料:http://www.cnblogs.com/dolphin0520/archive/2011/10/06/2199741.html
堆的应用:
(一)哈夫曼树:POJ 3253
(二)"和并项" POJ 2442 Sequence
连接:http://poj.org/problem?id=2442
题目大意:给出m行数,每行n个数。从每行中选出一个数做和。那么有n^m个值,最后输出n个最小的和。
解题思路:
如果从最暴力的方法思考,枚举每一行的每一个数和接下来的每一行的任意一个数求出最小的n个和,可定会超时的。
但是其实有用的就是最小的n个数,那么依次枚举该行和前面所有行的情况中的最小的n个数的和的话,枚举的数据量会减少很多的。
1>第一组数据存在data1[n]中,给他排序
2>第二组数据存在data2[n]中,给他排序
3>求出data2[0]与data1[]中数据的和,存在数组dataq[]中。给dataq[]从小到大排序。
4>依次枚举data2[1]~data2[n]与data1[]作和。如果比 dataq[n-1]大,break; 否则dataq[n-1]=他们的和,重新排序dataq[];
5>将dataq[]给data1[]
6>重复2>-5>过程
7>输出data1[]就是想要的结果
扩展:STL中有一个heap.
1>make_heap(heap, heap+n, cmp); 默认的情况下是将把“最大”的元素排列到首位,而其他元素看上去并非有序
2>sort_heap(heap, heap+n, cmp); 进行堆排序
3>pop_heap(heap, heap+n, cmp); 并不是将最大(最小元素弹出),而是将frist与last交换,在将frist与last-1做成一个堆
4>push_heap(heap, heap+n, cmp); 将新元素加进去后做成一个堆;//感觉和1>没啥差别
代码如下:
#include<iostream> #include<stdio.h> #include<algorithm> using namespace std; #define N 2005 int data1[N], data2[N], dataq[N]; int main() { int i, j, tt, n, m, T; scanf("%d", &T); while(T--) { scanf("%d%d", &m, &n); memset(data1, 0, sizeof(data1)); memset(data2, 0, sizeof(data2)); memset(dataq, 0, sizeof(dataq)); for(i=0; i<n; i++) scanf("%d", &data1[i]); sort(data1, data1+n); for(tt=0; tt<m-1; tt++) { for(i=0; i<n; i++) scanf("%d", &data2[i]); int flag=0; sort(data2, data2+n); for(i=0; i<n; i++) dataq[i]=data2[0]+data1[i]; sort(dataq, dataq+n); for(i=1; i<n; i++) { for(j=0; j<n; j++) { if(data2[i]+data1[j]>dataq[n-1]) break; dataq[n-1]=data2[i]+data1[j]; sort(dataq, dataq+n); } } for(i=0; i<n; i++) data1[i]=dataq[i]; } for(i=0; i<n-1; i++) printf("%d ", data1[i]); printf("%d\n", data1[n-1); } return 0; }
#include<iostream> #include<stdio.h> #include<algorithm> using namespace std; #define N 2005 int data1[N], data2[N], dataq[N]; int main() { int i, j, tt, n, m, T; scanf("%d", &T); while(T--) { scanf("%d%d", &m, &n); memset(data1, 0, sizeof(data1)); memset(data2, 0, sizeof(data2)); memset(dataq, 0, sizeof(dataq)); for(i=0; i<n; i++) scanf("%d", &data1[i]); for(tt=0; tt<m-1; tt++) { sort(data1, data1+n); for(i=0; i<n; i++) scanf("%d", &data2[i]); sort(data2, data2+n); for(i=0; i<n; i++) dataq[i]=data2[0]+data1[i]; make_heap(dataq, dataq+n); for(i=1; i<n; i++) { for(j=0; j<n; j++) { if(data2[i]+data1[j]>=dataq[0]) break; dataq[0]=data2[i]+data1[j]; make_heap(dataq, dataq+n); } } for(i=0; i<n; i++) data1[i]=dataq[i]; } sort(data1, data1+n); for(i=0; i<n-1; i++) printf("%d ", data1[i]); printf("%d\n", data1[n-1]); } return 0; }
PS:此题最后不进行严格的控制格式也能ac such as:
for(i=0; i<n; i++)
printf("%d ", data1[i]);
printf("\n");
(三)优先队列: POJ 2051 Argus
连接:http://poj.org/problem?id=2051
题目大意:描述好难翻译。就是给出每个id 的num和id出现的间隔时间数pre。也就是说此id要间隔pre才能在出现。输出前k个id出现的次序,如果相同时间now内出现多个id,按照id 的num小的先输出。
解题思路:堆的思想用优先队列实现,有两个优先级别按照now小的优先级别高,如果now的时间相同按照num小的先输出。
扩展:优先队列重载符号,定义一个friend boo Name()函数进行设置优先级;
代码如下:
#include<stdio.h> #include<string.h> #include<iostream> #include<queue> using namespace std; struct ID { friend bool operator < (ID x, ID y) { if(x.now!=y.now) return y.now<x.now; else return y.num<x.num; } int num, per, now; }; int main() { int k, t=0; char ch[50]; ID id[1005]; priority_queue<ID>Q; while(1) { scanf("%s", ch); if(strcmp(ch, "Register")==0) { scanf("%d%d", &id[t].num, &id[t].per); id[t].now=id[t].per; Q.push(id[t]); t++; } if(strcmp(ch, "#")==0) break; } scanf("%d", &k); while(k--) { ID temp; temp=Q.top(); Q.pop(); printf("%d\n", temp.num); temp.now+=temp.per; Q.push(temp); } return 0; }
(四)...更新中....waiting....