问题描述:
假设要在足够多的会场里安排一批活动,并希望使用尽可能少的会场。设计一个有效的贪心算法进行安排(这个问题实际上是著名的图着色问题。若将每一个活动作为图的一个顶点,不相容活动间用边相连。使相邻顶点着有不同颜色的最小着色数,相应于要找的最小会场数)。
问题解答:
<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;
}