• 《编程之美》1.9:高效率的安排见面会的一个解法


    原题是这样的:

    在校园招聘的季节里,为了能让学生们更好地了解微软亚洲研究院各研究组的情况,HR部门计划为每一个研究组举办一次见面会,让各个研究组的员工能跟学生相互了解和交流(如图1-4所示)。已知有n位学生,他们分别对m个研究组中的若干个感兴趣。为了满足所有学生的要求,HR希望每个学生都能参加自己感兴趣的所有见面会。如果每个见面会的时间为t,那么,如何安排才能够使得所有见面会的总时间最短? 最简单的办法,就是把m个研究组的见面会时间依次排开,那我们就要用m * t的总时间,我们有10多个研究小组,时间会拖得很长,能否进一步提高效率?

    此题的官方解法是将问题转化为一个已知的图的问题:即图的最少着色问题。 但有两点感觉不太好:

    • 一是这个问题的转换感觉角度有些大,不够平滑。 如果此前没听过图的最少着色问题,那么是怎么也不可能想到这儿的。
    • 二是关于此题的分析与解法讲解非常笼统,尤其是解法,寥寥数语就完了 - 我觉得是没有把问题讲清楚的 - 虽然读者自己可以阅读“ 图的最少着色问题”来获得更多的了解,但既然此题作为单独的一题存在,我觉得还是讲清楚点好。

    另外,自己思考了一下,觉的此题不转化成图的最少着色问题,通过简单的递归,应该也是可以实现的。


    思路分析

    已知有n位学生,m个见面会,且每个学生可以选择参加任意多个见面会。我们的目的是在没有冲突的情况下把某几个见面会的时间重叠起来,同时开。何为冲突,比如学生甲参加了见面会A与B,那么A与B就是冲突的,因为如果同时开的话,学生甲必然要放弃一个。

    那么,我们可以按以下方式,逐个考虑见面会:

    • 对于见面会A,因为其前面没有见面会,略过;
    • 对于见面会B,考虑它和其前面的见面会A是否冲突,如果不冲突,就将B和A合并,继续考虑C;而另外还有一个分支是不管是否冲突,此时不做合并,直接考虑C;
    • 对于见面会C,考虑它和其前面的见面会A,B是否冲突,第一个分支是如果与A不冲突, 就将C和A合并,继续考虑D;第二个分支是如果与B不冲突, 就将C和B合并,继续考虑D;而第三格分支还是不做任何合并,直接考虑D
    • 按此规则继续对下一个见面会做考察
    • 当对最后一个招聘会做完考察后,记下其时间,程序然后递归回溯,会由其他分支继续考察最后一个招聘会,比较并记录最短的那个,这样,当所有的分支都考察过后,记录的那个最短时间就是全局最短的时间了。 

    代码

    • 输入数据可以用一个二维数组input[m][n]来表示,行表示见面会,列表示学生,数组元素表示某学生是否参加该见面会。 
    • 递归过程用当前考虑的见面会控制,当最后一个见面会考虑完之后,就得到一个候选解,程序然后回溯,从另一分支再次进入,考虑最后一个见面会,与之前的候选解比较,保存较优的那个:候选解包括见面会时间与当前的具体安排
    View Code
    1 #include <iostream>
    2 #include <list>
    3 #include <vector>
    4
    5  using namespace std;
    6
    7  // If 2 recruiting meetings are conflicting
    8  bool IsConflict(const vector<bool>& v1, const vector<bool>& v2)
    9 {
    10 for(int i = 0; i < v1.size(); ++i)
    11 {
    12 if(v1[i] && v2[i]) return true;
    13 }
    14 return false;
    15 }
    16
    17  // merge 2 recruiting meetings: v2 will be held at the same time of v1
    18  void Merge(vector<bool>& v1, const vector<bool>& v2)
    19 {
    20 for(int i = 0; i < v1.size(); ++i)
    21 {
    22 v1[i] = v1[i] || v2[i];
    23 }
    24 }
    25
    26  // input: input[m][n], 2d array to represents students' selection of meetings. row stands for meetings, column stands for students,
    27 // and array value stands for if a student select a meeting
    28 // next: The meeting to check
    29 // curTime: Time required so far
    30 // curArrangement: record the information of which meeting is merged with another meeting
    31 // best[output]:The best time
    32 // bestArrangement[output] : The best arragement
    33 void ArrangeRecruitings(vector<vector<bool> >& input, int next, int& curTime, vector<list<int> >& curArrangement, int& bestTime, vector<list<int> >& bestArrangement)
    34 {
    35 int m = input.size();
    36 // base cases
    37 if(next >= m)
    38 {
    39 // Save the best one
    40 if(curTime < bestTime)
    41 {
    42 bestTime = curTime;
    43 bestArrangement = curArrangement;
    44 }
    45
    46 return;
    47 }
    48 else
    49 {
    50 // recursive cases
    51 for(int i = 0; i <= next; ++i)
    52 {
    53
    54 if(curArrangement[i].empty()) continue; // if already merged with other ones, just skip it
    55
    56 if (!IsConflict(input[i], input[next]))
    57 {
    58
    59 // update the status
    60 vector<bool> bkI = input[i];
    61 Merge(input[i], input[next]);
    62
    63 curArrangement[next].pop_back();
    64 curArrangement[i].push_back(next);
    65
    66 // Consider next one after merge
    67 ArrangeRecruitings(input, next+1, curTime, curArrangement, bestTime, bestArrangement);
    68
    69 // restore the status
    70 curArrangement[i].pop_back();
    71 curArrangement[next].push_back(next);
    72
    73 input[i] = bkI;
    74 }
    75 }
    76
    77 // Consider next one without any merge
    78 ArrangeRecruitings(input, next+1, ++curTime, curArrangement, bestTime, bestArrangement);
    79
    80 }
    81 }
    82
    83
    84 int main()
    85 {
    86 // 1. Initializing
    87 int m = 3;
    88 int n = 4;
    89
    90 vector<vector<bool> > input(m, vector<bool>(n, false));
    91
    92 input[0][0] = true;
    93 input[0][1] = true;
    94
    95 input[1][1] = true;
    96 input[1][2] = true;
    97
    98 input[2][2] = true;
    99 input[2][3] = true;
    100
    101 //input[0][2] = true;
    102
    103 int curTime = 1;
    104 int bestTime = m+1;
    105 vector<list<int> > curArrangement(m);
    106 vector<list<int> > bestArrangement(m);
    107 for(int i = 0; i < m; ++i) curArrangement[i].push_back(i);
    108
    109 // 2. Solve
    110 ArrangeRecruitings(input, 1, curTime, curArrangement, bestTime, bestArrangement);
    111
    112 // 3. Output the result
    113 cout << "Totoal Time: " << bestTime << endl;
    114 for(int i = 0; i < m; ++i)
    115 {
    116 cout << i << "): ";
    117 if(bestArrangement[i].empty())
    118 {
    119 cout << "none" << endl;
    120 continue;
    121 }
    122 for(list<int>::const_iterator it = bestArrangement[i].begin(); it != bestArrangement[i].end(); ++it)
    123 cout << *it << "-";
    124
    125 cout << endl;
    126 }
    127 }


    复杂度分析

    可以注意到,我们需要逐个考虑见面会,一共是m个;而在考虑第i个见面会时,我们最多可能会产生出i个分支,不难看出,总问题的规模为m!;而产生每个分支时需要做的计算是O(n)的冲突检测与合并,于是,整个的算法复杂度为:O(m! * n)

  • 相关阅读:
    mysql密码重置
    利用python生成定制二维码
    totnado前后端分离跨域设置
    supervisor详解
    redis持久化常识和配置
    redis数据的备份与恢复
    supervisor下更换源文件报错
    EF5框架封装
    IEnumerable和IEnumerator 详解
    心宽,路自宽
  • 原文地址:https://www.cnblogs.com/baiyanhuang/p/1988782.html
Copyright © 2020-2023  润新知