制定基本的学校时间表

时间:2016-06-16 12:26:49

标签: c++ arrays algorithm

我正在尝试实施一个非常基本的,非常简单的版本,从预先确定的班次列表和预先确定的人员列表中生成学校日程安排。

约束和基本设置

为了举例,让我们假设以下问题数据:

5-人,我们称他们为A,B,C,D,E,他们希望分配他们各自的时间表。

每个人都有一份之前选择的班次清单。

每周有5天,让我们假设每天有3个班次,所以,我们有一个3行,5列的矩阵。表示班次的单元格从上到下从左到右编号,从1开始。

列表:

A = {1,2,3,5,10,11}

B = {6,7,1,3,8,15}

C = {2,6,8,9,12,13}

D = {3,4,5,6,7,8}

E = {6,8,10,11,13,14}

归因于所有轮班后,时间表将是:

A - 5,10,11

B - 1,7,15

C - 2,6,12

D - 3,4,9

E - 8,13,14

我如何将这个概念概括为一个真实的案例,让我们说20个人,40个班次,每个人从8个班次的名单中选择2个。

我的代码如下:

 #include <iostream>
    #include <algorithm>
    #include <vector>
    #include <set>
    #include <cmath>
    using namespace std;

    #define OPT_DEGREE 300

    #define DEBUG 0
    #define vpbvi vector<pair<bool,vector<ULL> > >
    #define ULL unsigned long long

    static string dict[20];

    void showV(vector<ULL> & v)
    {
        for(int i = 0; i < v.size(); i++)
        {
            cout << v[i] << " ";
        }
        cout << endl;
    }

    inline bool do_vectors_intersect(vector<ULL> v1, vector<ULL> v2)
    {
        unsigned long long int target_sz = v1.size()+v2.size();
        set<ULL> s;
        for(int i = 0; i < v1.size(); i++)
            s.insert(v1[i]);
        for(int j = 0; j < v2.size(); j++)
            s.insert(v2[j]);

        return !(static_cast<ULL>(s.size()) == target_sz); //True se ha intersecçao False c.c.
    }

    void generateAllPossibleShifts(vector<vector<ULL> > & auxiliar, vector<ULL> & _shifts, int N, int K)
    {
        string bitmask(K, 1); // K leading 1's
        bitmask.resize(N, 0); // N-K trailing 0's

         // print integers and permute bitmask
         do {
                vector<ULL> aux;
                for (int i = 0; i < N; ++i) // [0..N-1] integers
                {
                    if (bitmask[i])
                    {
                        aux.push_back(_shifts[i]);
                        if(DEBUG){
                            cout << " " << _shifts[i];}
                    }
                }
                if(DEBUG){
                    cout << endl << aux.size() << " Done. Create Pair and add to scheudle." << endl;}
                    pair<bool,vector<ULL> > pbvi = make_pair(true,aux);

                    for(int i = 0; i < aux.size();i++)
                        if(DEBUG){
                        cout << "aux[" <<i<<"] = " << pbvi.second[i] << endl;}
                    auxiliar.push_back(aux);
                   // fullVec.push_back(pbvi);
                    aux.resize(0); //vector is cleared here
                    if(DEBUG){
                    cout << "Clear vec" << endl;
                    cout << pbvi.second.size() << " Done" << endl;
                    cout <<  endl;}
            } while ( prev_permutation(bitmask.begin(), bitmask.end()));
    }

    vector<ULL> SumVecs(vector<ULL> & a, vector<ULL> & b)
    {
        vector<ULL> newVec;
        for(int i = 0; i < a.size(); i++)
            newVec.push_back(a[i]);
        for(int i = 0; i < b.size(); i++)
            newVec.push_back(b[i]);
        return newVec;
    }

    void AppendToFirst(vector<ULL> & fst, vector<ULL> & snd, int ind)
    {
        for(int k = 0; k < snd.size(); k++)
        fst.push_back(snd[k]);
    }

    void OptimizeBeforeNextPassLeft(vector<vector<ULL> > & bsc, vector<vector<ULL> > & arg2)
    {
        int opt = 0;
        for(int i = 0; i < bsc.size(); i++)
        {
            for(int j = 0; j < arg2.size(); j++)
            {
                if(do_vectors_intersect(bsc[i],arg2[j])==true){
                    arg2.erase(remove(arg2.begin(), arg2.end(), arg2[j]), arg2.end());
                    }
            }
        }
    }

    void OptimizeBeforeNextPassRight(vector<vector<ULL> > & bsc, vector<vector<ULL> > & arg2)
    {
        int opt = 0;
        for(int i = bsc.size()-1; i >= 0 ; i--)
        {
            for(int j = 0; j < arg2.size(); j++)
            {
                if(do_vectors_intersect(bsc[i],arg2[j])==true){
                    bsc.erase(bsc.begin()+i);
                    }
            }
        }
    }

    void HardOptimize(vector<vector<ULL> > & bsc, vector<vector<ULL> > & arg2)
    {
        int opt = 0;
        for(int i = bsc.size()-1; i >= 0 ; i--)
        {
            for(int j = 1; j < arg2.size(); j+=2)
            {
                if(do_vectors_intersect(bsc[i],arg2[j])==true){
                    bsc.erase(bsc.begin()+i);
                    }
            }
        }
    }

    //Recursive Function that filters bad attempts and uses "basic" look-ahead technique to narrow search space
    //while building an iterative solution. Can still be optimized.
     void ExpandSearchSpace(vector<vector<vector<ULL> > > & v, vector<vector<ULL> > & buildSol, int guesslvl, vector<vector<ULL> > & placeholder)
    {
       if(guesslvl==4) //Num de pessoas-1
       {
           // cout << "inside ret " << endl << buildSol.size();
            placeholder = buildSol;
           return;
       }
       else
       {
            vector<vector<ULL> > BuildSolCp;
            const int ssz = buildSol.size();
            for(int i = 0; i < ssz;i++)
            {
                vector<ULL> arg1 = buildSol[i];
                const int ssz2 = v[guesslvl+1].size();
               //cout << "arg1.sz() = " << arg1.size() << endl;
                for(int j = 0; j < ssz2;j++)
                {
                    if(do_vectors_intersect(buildSol[i], v[guesslvl+1][j])==false){
                 //   cout << "Iter " << guesslvl << "   " << buildSol[i].size() << " ";
                   vector<ULL> arg2 = v[guesslvl+1][j];
                 // cout << "arg2 " << arg1.size() << " --- ";
                  vector<ULL> auxi = SumVecs(arg1,arg2);
                 // cout << "OLFOFKODSJFDSIHFDSFDS" << endl;
                     BuildSolCp.push_back(auxi);
                   //  cout << "PUSHDED SDUSHFUDSHF"<<endl;

                    }
                }
            }

            guesslvl++;
            if(BuildSolCp.size()> 1000){
            cout << "WE neeed optimize Jon" << endl;
            for(int i = 0; i < BuildSolCp.size();i++)
            showV(BuildSolCp[i]);
            vector<vector<ULL> > s= v[guesslvl+1];
            //vector<vector<ULL> > s3= v[guesslvl+4];
          //  vector<vector<ULL> > s4= v[guesslvl+5];
         //  OptimizeBeforeNextPassLeft(BuildSolCp, s);
         OptimizeBeforeNextPassLeft(buildSol,v[guesslvl+2]);

          //  OptimizeBeforeNextPassRight(BuildSolCp, s);}
           // OptimizeFromBothSidesAtOnce(BuildSolCp, v[guesslvl+1][j]);
           }

            cout << BuildSolCp.size() << " " << guesslvl << endl;

           ExpandSearchSpace(v,BuildSolCp, guesslvl, placeholder);
       }
      // cout << "end" << endl;
    }

    void ShowPrettyScheudle(vector<vector<ULL> > sol)
    {
        vector<int> scheudle(15);
        for(int j = 0; j <= 10; j+=5){
            for(int i = j; i < j+5; i++)
            {
                cout << sol[0][i] << "\t | ";
            }
            cout << endl;}

    }

    int main()
    {
        static vector<vector<ULL> > WorkerVec1,WorkerVec2,WorkerVec3,WorkerVec4, WorkerVec5;
        static vector<vector<ULL> > WorkerVec6,WorkerVec7,WorkerVec8,WorkerVec9, WorkerVec10;
        static vector<vector<ULL> > WorkerVec11,WorkerVec12,WorkerVec13,WorkerVec14, WorkerVec15;
        static vector<vector<ULL> > WorkerVec16,WorkerVec17,WorkerVec18,WorkerVec19, WorkerVec20;
        vector<vector<ULL> > sol;
        static vector<vector<vector<ULL>>> v;

        vector<ULL> v1{5,10,11,3,1,2}, v2{1,7,3,15,6,8} ,v3{2,6,12,8,13,9}, v4{3,4,5,6,7,8},v5{6,8,10,11,13,14};
        generateAllPossibleShifts(WorkerVec1, v1,6,3);
        generateAllPossibleShifts(WorkerVec2, v2,6,3);
        generateAllPossibleShifts(WorkerVec3, v3,6,3);
        generateAllPossibleShifts(WorkerVec4, v4,6,3);
        generateAllPossibleShifts(WorkerVec5, v5,6,3);
        v.insert(v.end(), {WorkerVec1,WorkerVec2, WorkerVec3,WorkerVec4, WorkerVec5} );
        cout << "SIZE OF v[0] in main is " << v[0].size() << endl; //20
        for(int i = 0; i < v[0].size(); i++)
        {
            sol.push_back(v[0][i]);
        }
        cout << sol.size() << endl; //20
        vector<vector<ULL> > plcholder;
        cout << "OMG " << plcholder.size()<<endl;
        ExpandSearchSpace(v,sol,0,plcholder);

        cout << sol.size() << endl;
        for(int i = plcholder.size()-1; i >= 0; i--){
            cout << plcholder[i].size() << endl;
            if(plcholder[i].size()==15){
                 cout << "FUCK YEA ";showV(plcholder[i]);
                 cout << endl << endl;
                vector<vector<ULL> > vect{plcholder[i]};
                ShowPrettyScheudle(vect);
                break;}
                }
        cout << endl;
       // cout << endl << Ans[0].size() << " " << Ans[1].size() << " " << Ans[2].size() << " " << Ans[3].size();
        return 0;
    }

我知道代码很乱,但其本质很简单:

我基本上做了一个蛮力,在每次通过时,我“积累”3个可能的班次的块,并将它们与下一组可能的班次进行比较,直到我到达结尾,只选择了可能的班次。

我尝试用一​​个简单的DP配方来思考,甚至​​用图表来思考,但是,我完全陷入困境......也许考虑个人转变而不是“转变块”更好,但是,现在,我很茫然。 我已经在这件事上待了2天了,真诚地让我感到紧张

2 个答案:

答案 0 :(得分:3)

根据您的情况很简单,您可以尝试以下算法,

  1. 首先将轮班与人联系起来
  2. 循环所有班次
    1. 选择最不相关人员的班次
    2. 将班次分配给已经分配最少轮班的人员。
  3. 该算法在O(N * M)中工作,其中N是移位,M是人。它也始终找到解决方案,但必然是正确的解决方案。请参阅j_random_hacker的答案。

    下面是一个不检查输入数据是否有效的实现。我将shift 15更改为0以映射矢量索引。

    #include <list>
    #include <vector>
    #include <iostream>
    #include <algorithm>
    
    class VectorComp{
    public:
      bool operator()(const std::vector<int>& v1,const std::vector<int>& v2){
        if (v1.size()==0) return false;
        if (v2.size()==0) return true;
        return v1.size()<v2.size();
      }
    };
    
    int main(){
      std::vector<std::vector<int>> personToShift{{1,2,3,5,10,11},
                                                  {6,7,1,3,8,0},
                                                  {2,6,8,9,12,13},
                                                  {3,4,5,6,7,8},
                                                  {6,8,10,11,13,14}};
    
      //Map shifts to persons
      std::vector<std::vector<int>> shiftToPerson(15);
      for (size_t i=0;i<personToShift.size();++i){
        for (auto s:personToShift[i]){
          shiftToPerson[s].push_back(i);
        }
      }
    
      //Result vector
      std::vector<std::vector<int>> res(personToShift.size());
    
      for (size_t i=0;i<shiftToPerson.size();++i){
        auto minPersonsForShift = std::min_element(shiftToPerson.begin(),
                                                  shiftToPerson.end(),
                                                  VectorComp());//Find shift with minimum persons
        size_t shift=minPersonsForShift-shiftToPerson.begin();
        size_t minShifts=shiftToPerson.size();
        size_t minPerson=0;
        for (auto person:*minPersonsForShift){//Find person in shift with   minimum shifts so far
          if (res[person].size()<minShifts){
            minPerson=person;
            minShifts=res[person].size();
          }
        }
        res[minPerson].push_back(shift);//Update the result
        shiftToPerson[shift].clear();//Mark the shift assigned by clearing the vector
      }
    
      for (size_t i=0;i<res.size();++i){//Print the result
        std::cout << char(('A'+i)) << " - ";
        for (auto t:res[i]){
          std::cout << t << " ";
        }
        std::cout << std::endl;
      }    
    }       
    

    输出:

    A - 1 2 11 
    B - 0 7 3 
    C - 9 12 13 
    D - 4 5 6 
    E - 14 10 8 
    

答案 1 :(得分:3)

假设有n个人,m个班次可用,并且您想要为每个人分配s(必须s <= m / n)班次。可以将此问题建模为在二分图中找到maximum matching的问题。匹配是一组边,使得在多个边中不使用顶点;最大匹配是最大可能大小的匹配。构建图表:

  • 在A部分,为每个人创建s顶点v_ {i,1},...,v_ {i,s}。
  • 在B部分中,为每个班次创建一个顶点。
  • 每当我可以使用shift j时,插入s边(v_ {i,1},j),(v_ {i,2},j),...,(v_ {i,s},j)

结果图是二分的,因为A中的2个顶点之间或B中的2个顶点之间没有边。您可以使用Hopcroft找到O(sqrt(| V |)| E |)时间内的最大匹配-Karp算法在第一个链接中;如果存在,这将为您提供最佳解决方案(每个人都被分配了轮班)。这里| V | = sn + m,| E | = O(snm),因为在最坏的情况下,每个人都可能列出每个班次的可能性,因此整体时间复杂度将为O(sqrt(sn + m)snm)。

Ari解决方案的反例

Ari Hietanen's solution是一个很好的启发式方法,但即使存在一个解决方案也无法找到解决方案,如下面的示例问题实例所示。

假设我们有8个人A,B,......,H和8个班次1,2,...,8,我们希望为每个人分配一个班次,可能的班次矩阵如下所示:

  12345678
A XXXXX...
B XXXX....
C XXXX....
D XXXX....
E XXXX....
F ....XXXX
G .....XXX
H .....XXX

其中X表示该行中的人可以在该列中进行移位。

Ari的算法将首先选择shift(列)5,因为只有2个人(A和F),这个班次可以被比所有其他班次更少的人使用(所有人都可以使用至少3人)。由于此时A和F都没有指定任何轮班,所以未决定是否会选择A或F来指定轮班5,所以它可能选择F - 当然,如果它通过选择那个人来打破联系它有可用的最少轮班,它会这样做(因为F有4个可能的轮班,A有5个)。但是一旦做出这个选择,它就无法解决问题,因为这意味着4个班次1,2,3和4需要以某种方式分成5个人A,B,C,D和E ,这是不可能的。为了确定解决方案确实存在,假设我们将shift 5分配给A:现在我们只需要在 4 人B,C,D中分配4个班次1,2,3和4 E,以及3人F,G和H的3班6,7和8,这很容易完成。