题目:
假设要用很多个教室对一组活动进行调剂。我们希望应用尽可能少的教室来调剂所有的活动。请给出一个有效的贪心算法,来断定哪一个活动应应用哪一个教室。
(这个题目也被成为区间图着色(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);
}
}
2、贪心算法:
//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://blog.csdn.net/mishifangxiangdefeng/article/details/7747779。