问题描述:
假设要在足够多的会场里安排一批活动,并希望使用尽可能少的会场。设计一个有效的贪心算法进行安排(这个问题实际上是著名的图着色问题。若将每一个活动作为图的一个顶点,不相容活动间用边相连。使相邻顶点着有不同颜色的最小着色数,相应于要找的最小会场数)。
问题解答:
<1>、用贪心选择策略解会场安排问题。
贪心算法重要的两个性质:贪心选择性质和最优子结构性质。
1、 问题的贪心选择性质
证明:首先将会场安排问题数学化,设有n个活动的集合 e= { 1 ,2 ,…,n },每个活动 i 都有一个要求使用该会场的起始时问si 和一个结束时问fi 。即k是所需最少会场的个数。设活动已排序,( a1 , a2 , … ,ak )是所需要的k个已安排了活动的会场。①当k = 1时,也就是所有的活动在一个会场里相容,a1 是满足贪心选择性质的最优解;②当k>= 2时,取b1=a1,bk=ak (即bk是安排了m个活动的一个会场,(n-m)个活动都安排在b1 到bk-1个会场里)。就是(n-m)个活动安排需要(k -1)个会场。则(b1,b2 ,…,bk-1 )是(n - m)个活动可行解。另一方面,由{( a1 ,a2,…,ak)-ak}=(b1,b2,…,bk-1)知,(b1,b2,…,bk-1)也是满足贪心选择性质的最优解,所以,会场安排问题具有贪心选择性质。
2、 问题的最优子结构性质
证明:( a1,a2, …,ak )是n个活动的集合e= {1,2 ,…,n }所需会场的最优解。设a1中安排了m个相容的活动,那么也就是说(n-m)个活动完全安排需要k-1个会场。假设(n - m)个活动安排只需要k-2个会场或则更少的会场。也就是说n个活动安排只需要k-1个会场或者更少的会场就可以安排完,则前后出现矛盾。一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。
<2>、算法实现思路:
1)、首先定义一个时间类Node,有两个data,flag属性,其中data是时间值,flag是判断活动开始还是结束。
2)、对所有的时间对象按.data排序到一个数组中,其中选择归并排序方法进行排序。
3)、该算法的贪心选择的意义是使剩余的可安排时间段极大化,以便安排尽可能多的相容活动。依次取出该数组的数据,判断其属性flag,若是一个开始时间,则Count++,若是结束时间Count--,其中最大的Count值就是要求的最少的会场数。
<3>、算法程序:
#include<iostream> #include<fstream> using namespace std; //活动的时间类 class Node { public: int data;//时间值 bool flag;//判断是开始还是结束,0表示开始,1表示结束 bool operator<(Node &secondRational);//按时间值比较两个对象的大小 }; //比较类中时间值的大小 bool Node::operator<(Node &secondRational) { if((this->data-secondRational.data)<0) return true; else return false; } //复制数组函数 template<typename T> void arraycopy(T source[],int sourceStartIndex,T target[],int targetStartIndex,int length); //合并数组函数 template<typename T> void merge(T list1[],int list1Size,T list2[],int list2Size,T temp[]); //归并排序函数 template<typename T> void mergeSort(T list[],int arraySize) { if(arraySize>1) { //复制排序前半部分数组 T *firstHalf=new T[arraySize/2]; arraycopy(list,0,firstHalf,0,arraySize/2); mergeSort(firstHalf,arraySize/2); //复制排序后半部分数组 int secondHalfLength=arraySize-arraySize/2; T *secondHalf=new T[secondHalfLength]; arraycopy(list,arraySize/2,secondHalf,0,secondHalfLength); mergeSort(secondHalf,secondHalfLength); //合并复制两部分数组 T *temp=new T[arraySize]; merge(firstHalf,arraySize/2,secondHalf,secondHalfLength,temp); arraycopy(temp,0,list,0,arraySize); delete []temp; delete []firstHalf; delete []secondHalf; } } //将两个数组按大小顺序合并入一个数组中 template<typename T> void merge(T list1[],int list1Size,T list2[],int list2Size,T temp[]) { int current1=0; int current2=0; int current3=0; while(current1<list1Size&¤t2<list2Size) { if(list1[current1]<list2[current2]) temp[current3++]=list1[current1++]; else temp[current3++]=list2[current2++]; } while(current1<list1Size) temp[current3++]=list1[current1++]; while(current2<list2Size) temp[current3++]=list2[current2++]; } //将一个数组复制到另一个数组中 template<typename T> void arraycopy(T source[],int sourceStartIndex,T target[],int targetStartIndex,int length) { for(int i=0;i<length;i++) { target[i+targetStartIndex]=source[i+sourceStartIndex]; } } //最少会场数函数 int Greedyplan(Node f[],int n) { int Count=0; int maxCount=0; /* 遍历活动时间,若此时间为开始时间,则Count+1,为结束时间,则Count-1 统计得出最大的Count值,并将Count值赋给maxCount, 此maxCount值就为最少的会场数 */ for(int i=0;i<n;i++) { if(f[i].flag==0)//若此时间为开始时间Count+1 { Count++; if(Count>maxCount)//记录次循环中最大的Count值 { maxCount=Count; } } else//若为结束时间Count-1 { Count--; } } return maxCount; } int main() { //读出输入文件中的数据 fstream fin; fin.open("input.txt",ios::in); if(fin.fail()) { cout<<"File does not exist!"<<endl; cout<<"Exit program"<<endl; return 0; } int n; fin>>n; //建立两个Node类型的数组,用于存放开始时间和结束时间 Node *a=new Node[n]; Node *b=new Node[n]; for(int i=0;i<n;i++) { fin>>a[i].data; fin>>b[i].data; } //将开始时间表示为0 for(int j=0;j<n;j++) { a[j].flag=0; } //将结束时间表示为1 for(int k=0;k<n;k++) { b[k].flag=1; } //再建立一个Node类型的数组,将前两个数组中的数据复制到此数组中 Node *c=new Node[2*n]; arraycopy(a,0,c,0,n); arraycopy(b,0,c,n,n); //将数组c按时间值大小排序,此排序为稳定的归并排序 mergeSort(c,2*n); //调用最少会场数函数 int mm=Greedyplan(c,2*n); //控制台输出 cout<<"最少会场数为:"<<mm<<endl; //将结果数据写入到输出文件 fstream fout; fout.open("output.txt",ios::out); fout<<mm; fin.close(); fout.close(); system("pause"); return 0; }