题目:
假设要用很多个教室对一组活动进行调剂。我们希望应用尽可能少的教室来调剂所有的活动。请给出一个有效的贪心算法,来断定哪一个活动应应用哪一个教室。(这个题目也被成为区间图着色(interval-graph coloring)题目。我们可作出一个区间图,其顶点为已知的活动,其边连接着不兼容的活动。为使任两个相邻结点的色彩均不雷同,所需的起码色彩对应于找出调剂给定的所有活动所需的起码教室数。)
思考:
常规算法:针对所有活动,先用16.1中的方法选择一个教室安排活动,然后对剩余的活动再选择一个新的教室,依次这样安排,直到活动全部安排完。
这个算法的时间复杂度是O(n^2),稍换一个角度,就能得到一个O(nlgn)的算法
O(nlgn)算法:
针对一个特定的活动,为它选择一个适合的教室。对所有已经选择过的教室,用一个最大堆的优先队列(存储活动的开始时间)来维护,比较的依据是在这个教室中最早开始的活动的开始时间。
具体步骤是这样的:
step1:对所有活动按结束时间从小到大排序。
step2:从最后一个活动开始,向第一个活动,依次针对每个活动做以下处理
(1)获取堆顶元素的信息
(2)如果堆顶的活动开始时间早于当前活动的结束时间,则申请一个新的教室,把活动的开始时间填入堆中
(3)如果堆顶的活动开始时间晚于当前活动的结束时间,删除堆顶,并就把当前活动填入堆中。//此时即是每个教室活动的最优选择
(4)选择下一个活动,直到所有活动都处理过
代码:O(lgn)
1、堆的相关操作:
- //Heap.h
- #include <iostream>
- #include <stdio.h>
- using namespace std;
- #define PARENT(i) (i)>>1
- #define LEFT(i) (i)<<1
- #define RIGHT(i) ((i)<<1)+1
- int length = 0;//数组中元素的个数
- int heap_size = 0;//属于堆的元素个数,看到HeapSort就会明白
- /*************************以下是堆处理函数****************************************/
- //使以i结点为根结点的子树成为堆,调用条件是确定i的左右子树已经是堆,时间是O(lgn)
- //递归方法
- void Max_Heapify(int *A, int i)
- {
- int l = LEFT(i), r = RIGHT(i), largest;
- //选择i、i的左、i的右三个结点中值最大的结点
- if(l <= heap_size && A[l] > A[i])
- largest = l;
- else largest = i;
- if(r <= heap_size && A[r] > A[largest])
- largest = r;
- //如果根最大,已经满足堆的条件,函数停止
- //否则
- if(largest != i)
- {
- //根与值最大的结点交互
- swap(A[i], A[largest]);
- //交换可能破坏子树的堆,重新调整子树
- Max_Heapify(A, largest);
- }
- }
- /**********************以下是优先队列处理函数****************************************/
- //将元素i的关键字增加到key,要求key>=A[i]
- void Heap_Increase_Key(int *A, int i, int key)
- {
- if(key < A[i])
- {
- cout<<"new key is smaller than current key"<<endl;
- exit(0);
- }
- A[i] = key;
- //跟父比较,若A[PARENT(i)]<A[i],则交换
- //若运行到某个结点时A[PARENT(i)]>A[i],就跳出循环
- while(A[PARENT(i)] > 0 && A[PARENT(i)] < A[i])
- {
- swap(A[PARENT(i)], A[i]);
- i = PARENT(i);
- }
- }
- //把key插入到集合A中
- void Max_Heap_Insert(int *A, int key)
- {
- if(heap_size == 99)
- {
- cout<<"heap is full"<<endl;
- exit(0);
- }
- heap_size++;length++;
- A[heap_size] = -0x7fffffff;
- Heap_Increase_Key(A, heap_size, key);
- }
- //返回A中最大关键字,时间O(1)
- int Heap_Maximum(int *A)
- {
- return A[1];
- }
- //去掉并返回A中最大关键字,时间O(lgn)
- int Heap_Extract_Max(int *A)
- {
- if(heap_size < 1)
- {
- cout<<"heap underflow"<<endl;
- exit(0);
- }
- //取出最大值
- int max = A[1];
- //将最后一个元素补到最大值的位置
- A[1] = A[heap_size];
- heap_size--;
- //重新调整根结点,维护堆的性质
- Max_Heapify(A, 1);
- //返回最大值
- return max;
- }
- //删除堆中第i个元素
- void Heap_Delete(int *A, int i)
- {
- if(i > heap_size)
- {
- cout<<"there's no node i"<<endl;
- exit(0);
- }
- //把最后一个元素补到第i个元素的位置
- int key = A[heap_size];
- heap_size--;
- //如果新值比原A[i]大,则向上调整
- if(key > A[i])
- Heap_Increase_Key(A, i, key);
- else//否则,向下调整
- {
- A[i] = key;
- Max_Heapify(A, i);
- }
- }
- //main.cpp
- #include <iostream>
- #include "Heap.h"
- using namespace std;
- #define N 11
- //用于存储每个活动的信息
- struct node
- {
- int id;//记录它是第几个活动
- int start;//开始时间
- int finish;//结束时间
- }A[N+1];
- //用于排序
- bool cmp(node a, node b)
- {
- return a.finish < b.finish;
- }
- //最大堆
- int H[N+1];
- //O(lgn)贪心算法
- void Greedy()
- {
- //对所有活动的结束时间从小到大排序
- sort(A+1, A+N+1, cmp);
- int i, ret = 0;
- //从最后一个活动开始,到第一个活动,依次针对每个活动做以下处理
- for(i = N; i >= 1; i--)
- {
- //1)获取堆顶元素的信息(4)更新堆(5)选择下一个活动,直到所有活动都处理过
- int temp = Heap_Maximum(H);
- //(2)如果堆顶的活动开始时间早于当前活动的结束时间,则:
- if(temp < A[i].finish)
- {
- //申请一个新的教室
- ret++;
- //把活动的开始时间填入其中
- Max_Heap_Insert(H, A[i].start);
- }
- //(3)如果堆顶的活动开始时间晚于当前活动的结束时间,则:
- else
- {
- //删除堆顶,并插入新元素
- Heap_Extract_Max(H);
- Max_Heap_Insert(H, A[i].start);
- }
- //选择下一个活动,直到所有活动都处理过
- }
- cout<<ret<<endl;
- }
- /*
- 1 4
- 3 5
- 0 6
- 5 7
- 3 8
- 5 9
- 6 10
- 8 11
- 8 12
- 2 13
- 12 14
- */
- int main()
- {
- int i;
- //输入测试数据
- for(i = 1; i <= N; i++)
- {
- A[i].id = i;
- cin>>A[i].start>>A[i].finish;
- }
- //贪心算法
- Greedy();
- return 0;
- }
转自: http://www.lai18.com/content/632357.html。