高级钟点秘书——会议安排

来源:互联网 发布:淘宝开团是不是很难抢 编辑:程序博客网 时间:2024/04/29 00:13
本内容来源于《趣学算法》,在线章节:http://www.epubit.com.cn/book/details/4825

高级钟点秘书——会议安排

所谓“钟点秘书”,是指年轻白领女性利用工余时间为客户提供秘书服务,并按钟点收取酬金。

“钟点秘书”为客户提供有偿服务的方式一般是:采用电话、电传、上网等“遥控”式服务,或亲自到客户公司处理部分业务。其服务对象主要有三类:一是外地前来考察商务经营、项目投资的商人或政要人员,他们由于初来乍到,急需有经验和熟悉本地情况的秘书帮忙;二是前来开展短暂商务活动,或召开小型资讯发布会的国外客商;三是本地一些请不起长期秘书的企、事业单位。这些客户普遍认为:请“钟点秘书”,一则可免去专门租楼请人的大笔开销;二则可根据开展的商务活动请有某方面专长的可用人才;三则由于对方是临时雇用关系,工作效率往往比固定的秘书更高。据调查,在上海“钟点秘书”的行情日趋看好。对此,业内人士认为:为了便于管理,各大城市有必要组建若干家“钟点秘书服务公司”,通过会员制的形式,为众多客户提供规范、优良、全面的服务,这也是建设国际化大都市所必需的。

某跨国公司总裁正分身无术,为一大堆会议时间表焦头烂额,希望高级钟点秘书能做出合理的安排,能在有限的时间内召开更多的会议。

图2-6 高级钟点秘书

2.4.1 问题分析

这是一个典型的会议安排问题,会议安排的目的是能在有限的时间内召开更多的会议(任何两个会议不能同时进行)。在会议安排中,每个会议i都有起始时间bi和结束时间ei,且bi<ei,即一个会议进行的时间为半开区间[biei)。如果[biei)与[bjej)均在“有限的时间内”,且不相交,则称会议i与会议j相容的。也就是说,当bi\geqslantejbj\geqslantei时,会议i与会议j相容。会议安排问题要求在所给的会议集合中选出最大的相容活动子集,即尽可能在有限的时间内召开更多的会议。

在这个问题中,“有限的时间内(这段时间应该是连续的)”是其中的一个限制条件,也应该是有一个起始时间和一个结束时间(简单化,起始时间可以是会议最早开始的时间,结束时间可以是会议最晚结束的时间),任务就是实现召开更多的满足在这个“有限的时间内”等待安排的会议,会议时间表如表2-6所示。

表2-6 会议时间表

会议i12345678910开始时间bi891011131415171816结束时间ei10111514161717182019

会议安排的时间段如图2-7所示。

..\17-0245 图\0209.tif

图2-7 会议安排时间段

从图2-7中可以看出,{会议1,会议4,会议6,会议8,会议9},{会议2,会议4,会议7,会议8,会议9}都是能安排最多的会议集合。

要让会议数最多,我们需要选择最多的不相交时间段。我们可以尝试贪心策略:

(1)每次从剩下未安排的会议中选择会议具有最早开始时间且与已安排的会议相容的会议安排,以增大时间资源的利用率。

(2)每次从剩下未安排的会议中选择持续时间最短且与已安排的会议相容的会议安排,这样可以安排更多一些的会议。

(3)每次从剩下未安排的会议中选择具有最早结束时间且与已安排的会议相容的会议安排,这样可以尽快安排下一个会议。

思考一下,如果选择最早开始时间,则如果会议持续时间很长,例如8点开始,却要持续12个小时,这样一天就只能安排一个会议;如果选择持续时间最短,则可能开始时间很晚,例如19点开始,20点结束,这样也只能安排一个会议,所以我们最好选择那些开始时间要早,而且持续时间短的会议,即最早开始时间+持续时间最短,就是最早结束时间。

因此采用第(3)种贪心策略,每次从剩下的会议中选择具有最早结束时间且与已安排的会议相容的会议安排

2.4.2 算法设计

(1)初始化:将n个会议的开始时间、结束时间存放在结构体数组中(想一想,为什么不用两个一维数组分别存储?),如果需要知道选中了哪些会议,还需要在结构体中增加会议编号,然后按结束时间从小到大排序(非递减),结束时间相等时,按开始时间从大到小排序(非递增);

(2)根据贪心策略就是选择第一个具有最早结束时间的会议,用last记录刚选中会议的结束时间;

(3)选择第一个会议之后,依次从剩下未安排的会议中选择,如果会议i开始时间大于等于最后一个选中的会议的结束时间last,那么会议i与已选中的会议相容,可以安排,更新last为刚选中会议的结束时间;否则,舍弃会议i,检查下一个会议是否可以安排。

2.4.3 完美图解

1.原始的会议时间表(见表2-7):

表2-7 原始会议时间表

会议num12345678910开始时间beg31525386812结束时间end64759811101214

2.排序后的会议时间表(见表2-8):

表2-8 排序后的会议时间表

会议num24136587910开始时间beg12353568812结束时间end45678910111214

3.贪心选择过程

(1)首先选择排序后的第一个会议即最早结束的会议(编号为2),用last记录最后一个被选中会议的结束时间,last=4。

(2)检查余下的会议,找到第一个开始时间大于等于 lastlast=4)的会议,子问题转化为从该会议开始,余下的所有会议。如表2-9所示。

表2-9 会议时间表

C:\Users\LL\Desktop\45957\45957-小田-制作489质检587 40.jpg

从子问题中,选择第一个会议即最早结束的会议(编号为3),更新last为刚选中会议的结束时间last=7。

(3)检查余下的会议,找到第一个开始时间大于等于lastlast=7)的会议,子问题转化为从该会议开始,余下的所有会议。如表2-10所示。

表2-10 会议时间表

C:\Users\LL\Desktop\45957\45957-小田-制作489质检587 40-1.jpg

从子问题中,选择第一个会议即最早结束的会议(编号为 7),更新 last 为刚选中会议的结束时间last=11。

(4)检查余下的会议,找到第一个开始时间大于等于lastlast=11)的会议,子问题转化为从该会议开始,余下的所有会议。如表2-11所示。

表2-11 会议时间表

C:\Users\LL\Desktop\45957\45957-小田-制作489质检587 41.jpg

从子问题中,选择第一个会议即最早结束的会议(编号为10),更新last为刚选中会议的结束时间last=14;所有会议检查完毕,算法结束。如表2-12所示。

4.构造最优解

从贪心选择的结果,可以看出,被选中的会议编号为{2,3,7,10},可以安排的会议数量最多为4,如表2-12所示。

表2-12 会议时间表

C:\Users\LL\Desktop\45957\45957-小田-制作489质检587 41-1.jpg

2.4.4 伪代码详解

(1)数据结构定义

以下C++程序代码中,结构体meet中定义了beg表示会议的开始时间,end表示会议的结束时间,会议meet的数据结构:

struct Meet{     int beg;   //会议的开始时间     int end;   //会议的结束时间} meet[1000];

(2)对会议按照结束时间非递减排序

我们采用C++中自带的sort函数,自定义比较函数的办法,实现会议排序,按结束时间从小到大排序(非递减),结束时间相等时,按开始时间从大到小排序(非递增):

bool cmp(Meet x,Meet y){     if(x.end==y.end)  //结束时间相等时         return x.beg>y.beg; //按开始时间从大到小排序     return x.end<y.end; //按结束时间从小到大排序}sort(meet,meet+n,cmp);

(3)会议安排问题的贪心算法求解

在会议按结束时间非递减排序的基础上,首先选中第一个会议,用last变量记录刚刚被选中会议的结束时间。下一个会议的开始时间与last比较,如果大于等于last,则选中。每次选中一个会议,更新last为最后一个被选中会议的结束时间,被选中的会议数ans加1;如果会议的开始时间不大于等于last,继续考查下一个会议,直到所有会议考查完毕。

int ans=1;     //用来记录可以安排会议的个数,初始时选中了第一个会议int last = meet[0].end;  //last记录第一个会议的结束时间for( i = 1;i < n; i++)   //依次检查每个会议{    if(meet[i].beg > =last)     {     //如果会议i开始时间大于等于最后一个选中的会议的结束时间       ans++;       last = meet[i].end; //更新last为最后一个选中会议的结束时间    }}return ans; //返回可以安排的会议最大数

上面介绍的程序中,只是返回了可以安排的会议最大数,而不知道安排了哪些会议,这显然是不满足需要的。我们可以改进一下,在会议结构体meet中添加会议编号num变量,选中会议时,显示选中了第几个会议。

2.4.5 实战演练

//program 2-3#include <iostream>#include <algorithm>#include <cstring>using namespace std;struct Meet{     int beg;   //会议的开始时间     int end;   //会议的结束时间     int num;   //记录会议的编号}meet[1000];    //会议的最大个数为1000class setMeet{  public:    void init();    void solve();  private:    int n,ans; // n:会议总数 ans: 最大的安排会议总数};//读入数据void setMeet::init(){     int s,e;     cout <<"输入会议总数:"<<endl;     cin >> n;     int i;     cout <<"输入会议的开始时间和结束时间,以空格分开:"<<endl;     for(i=0;i<n;++i)     {         cin>>s>>e;         meet[i].beg=s;         meet[i].end=e;         meet[i].num=i+1;     }}bool cmp(Meet x,Meet y){     if (x.end == y.end)           return x.beg > y.beg;     return x.end < y.end;}void setMeet::solve(){     sort(meet,meet+n,cmp);    //对会议按结束时间排序     cout <<"排完序的会议时间如下:"<<endl;     int i;     cout <<"会议编号"<<"  开始时间 "<<" 结束时间"<<endl;     for(i=0; i<n;i++)     {       cout<< "   " << meet[i].num<<"\t\t"<<meet[i].beg <<"\t"<< meet[i].end << endl;     }     cout <<"-------------------------------------------------"<<endl;     cout << "选择的会议的过程:" <<endl;     cout <<"  选择第"<< meet[0].num<<"个会议" << endl;//选中了第一个会议     ans=1;     int last = meet[0].end;  //记录刚刚被选中会议的结束时间     for( i = 1;i < n;++i)     {          if(meet[i].beg>=last)          {            //如果会议i开始时间大于等于最后一个选中的会议的结束时间             ans++;             last = meet[i].end;             cout <<"  选择第"<<meet[i].num<<"个会议"<<endl;          }     }     cout <<"最多可以安排" <<ans << "个会议"<<endl;}int main(){  setMeet sm;  sm.init();//读入数据  sm.solve();//贪心算法求解  return 0;}

算法实现和测试

(1)运行环境

Code::Blocks

(2)输入

输入会议总数:10输入会议的开始时间和结束时间,以空格分开:3 61 45 72 55 93 88 116 108 1212 14

(3)输出

排完序的会议时间如下:会议编号    开始时间    结束时间   2           1          4   4           2          5   1           3          6   3           5          7   6           3          8   5           5          9   8           6          10   7           8          11   9           8          12   10          12         14-------------------------------------------------选择的会议的过程:  选择第2个会议  选择第3个会议  选择第7个会议  选择第10个会议最多可以安排4个会议

使用上面贪心算法可得,选择的会议是第2、3、7、10个会议,输出最优值是4。

2.4.6 算法解析及优化拓展

1.算法复杂度分析

(1)时间复杂度:在该算法中,问题的规模就是会议总个数n。显然,执行次数随问题规模的增大而变化。首先在成员函数setMeet::init()中,输入n个结构体数据。输入作为基本语句,显然,共执行n次。而后在调用成员函数setMeet::solve()中进行排序,易知sort排序函数的平均时间复杂度为O(nlogn)。随后进行选择会议,贡献最大的为if(meet[i].beg>=last)语句,时间复杂度为O(n),总时间复杂度为O(n +nlogn)= O(nlogn)。

(2)空间复杂度:在该算法中,meet[]结构体数组为输入数据,不计算在空间复杂度内。辅助空间有inans等变量,则该程序空间复杂度为常数阶,即O(1)。

2.算法优化拓展

想一想,你有没有更好的办法来处理此问题,比如有更小的算法时间复杂度?