高效率地安排见面会

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

  • 一个学生同一时刻只能参见一个研究小组的见面会,因此一个学生希望参加的所有见面会不能同时开,否则会有冲突。
  • 在满足上面那一点的情况下,多个学生的不重叠的见面会同时开的话,可以减少时间消耗。
    比如A同学希望参加(1,2,3)见面会,B同学希望参加(1,3,4)见面会,如果2和4能同时召开,而其他见面会这时刻不召开来避免冲突,这样即防止冲突又减少一个见面的时间,因此类似这种情况可以用最少着色图来表示其情况:
    这里写图片描述
    作者给对最少着色图求解给了两种思路:
    第一种解法:
    对顶点1分配颜色1,然后对剩下的n-1个顶点枚举其所有的颜色可能,再一一验证是否可以满足我们的着色要求,枚举的复杂度是O( (n1)n ),验证一种颜色配置是否满足要求需要的时间复杂度O( n2 ),所以总共的时间复杂度是O( (n1)n n2 .
    第二种解法:
    我们可以尝试这个图进行K中着色,首先把K设为1,看看有没有合适的方案,在逐渐把K提高。
    从第二种解法思路来分析,我们可以用贪心策略来去实现,就是把当前的第K种颜色尽量标记更多的结点,其它标记不了的结点就用下一种颜色,然后循环重复的这样做,直到所有的结点都标记上了颜色。
    代码实现:
#include <iostream>
#include<stdio.h>
#include<stdlib.h>
#define MAX 20
#define FALSE 0
#define TRUE 1
using namespace std;
//结点
typedef struct node
{
   int value;
   struct node * next;
}edgenode;
//无向图链表的头结点
typedef struct vnode
{
   int value;//无向图链表的头结点索引
   edgenode* firstedgenode;//指向该顶点的第一个相邻结点
}topnode;
//无向图指向链表链的数组
typedef topnode node_list[MAX+1];
//无向图的结构体
typedef struct linklist_graph
{
   int nodes,edges;//无向图的结点数和边数
   node_list linklist;//无向图的指向链表链的数组
}linkgraph;
//保存每个结点的颜色标记
int color[MAX+1]={0};
//无向的结点个数
int nodenumber=0;
//创建无向图
void creat_graph(linkgraph* g)
{
   edgenode* newnode;
   printf("please input node number and edge number:");
   scanf("%d%d",&g->nodes,&g->edges);
   printf("node number = %d, edges = %d\n", g->nodes, g->edges);
   nodenumber=g->nodes;
   for(int i=1;i<=g->nodes;i++)
   {
      g->linklist[i].value=i;
      g->linklist[i].firstedgenode=NULL;
   }
   for(int k=0;k<g->edges;k++)
   {
      int i,j;
      printf("please input new edge: ");
      scanf("%d%d",&i,&j);
      newnode=(edgenode*)malloc(sizeof(edgenode));
      newnode->value=j;
      newnode->next=g->linklist[i].firstedgenode;
      g->linklist[i].firstedgenode=newnode;
      newnode=(edgenode*)malloc(sizeof(edgenode));
      newnode->value=i;
      newnode->next=g->linklist[j].firstedgenode;
      g->linklist[j].firstedgenode=newnode;
   }
}
//判断当前的节点是否能着第colorvalue种颜色
int isColour(topnode node,int colorvalue)
{
   edgenode* nextnode=node.firstedgenode;
   while(nextnode)
   {
      if(colorvalue==color[nextnode->value])
            return FALSE;
      nextnode=nextnode->next;
   }
   return TRUE;
}
int findMinColor(linkgraph *g)
{
   color[1]=1;
   //标记最少种颜色
   int minColorNum=0;
   while(true)
   {
      int i;
      //如果全部节点着色则退出
      for( i=2;i<=nodenumber;i++)
      {
         if(!color[i]) break;
      }
      if(i>nodenumber) break;
       minColorNum++;
       //寻找该颜色能着的结点
      for(i=2;i<=nodenumber;i++)
      {
         //已着颜色跳过
         if(color[i]) continue;
         if(isColour(g->linklist[i],minColorNum))color[i]=minColorNum;
      }
   }
   return minColorNum;
}
int main()
{
    linkgraph* g=(linkgraph*)malloc(sizeof(linkgraph));
    creat_graph(g);
    cout <<findMinColor(g)<< endl;
    return 0;
}

扩展问题:
题目:
某一天,在微软亚洲研究院有N个面试要进行,它们的时间分别为(B[i], E[i])(B[i]为面试开始时间,E[i]为面试结束时间)。假设一个面试者一天只参加一个面试。为了给面试者提供一个安静便于发挥的环境,我们希望将这N个面试安排在若干个面试点。不同的面试在同一个时间不能被安排在同一个面试点。如果你是微软亚洲研究院的HR,现在给定这N个面试的时间之后,你能计算出至少需要多少个面试点吗?请给出一个可行的方案。
作者也给出了两种思路:
第一种解法:
使用贪心策略,首先将B[i]从小到大排序,然后按顺序拿一个时间区间和其他的区间进行比较,如果没有重叠(说明可以着同一种颜色),则这两个合并一个新的区间,该新的区间为B[i],E[j],直到所有的区间都已经被遍历过了,最后合并后的区间个数就是代表其最少个面试地点。
代码实现:

#include <iostream>
#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
#define MAX 50
#define FALSE 0
#define TRUE 1
//面试会结点结构体
struct meeting
{
   int b;
   int e;
}m[MAX];
//存储当前的面试会是否能标记该索引对应的颜色
bool isForbidden[MAX]={false};
//存储面试会结点的颜色
int color[MAX]={0};
using namespace std;
bool compare(meeting a,meeting c )
{
   return a.b<c.b;
}
//判断面试会是否有时间冲突
int Overlap(meeting a,meeting c)
{
   if(a.e>c.b)
      return TRUE;
   else
      return FALSE;
}
int findMinColor(int number)
{
   int nMaxcolors=0;
   int k;
   for(int i=0;i<number;i++)
   {
      for(int j=0;j<nMaxcolors;j++)
         isForbidden[j]=false;
      for(int j=0;j<i;j++)
         if(Overlap(m[j],m[i]))isForbidden[color[j]]=true;//若与该j面试会有时间冲突,则不能标记该颜色
      for(k=0;k<nMaxcolors;k++)
         if(!isForbidden[k])break;//查找该结点所标记的颜色
      if(k<nMaxcolors)
         color[i]=k;
      else
         color[i]=nMaxcolors++;
   }
   return nMaxcolors;
}
int main()
{
   int number;
   //测试
   printf("input the meeting number smaller than 50:");
   scanf("%d",&number);
   for(int i=0;i<number;i++)
   {
      int x,y;
      do
      {
         x=rand()%24+1;
         y=rand()%24+1;
      }while(x==y);//以防产生的随机数一样
      cout<<"x:"<<x<<"\t"<<"y:"<<y<<endl;
      if(x<y)
      {
         m[i].b=x;
         m[i].e=y;
      }
      else
      {
         m[i].b=y;
         m[i].e=x;
      }
   }
   sort(m,m+number,compare);//排序面试会的开始时间
   cout<<findMinColor(number)<<endl;
    return 0;
}

第二种解法思路:
把所有的B[i],E[i]按大小排序,得到一个长度为2*N的有序数组,然后我们遍历这个数组,遇到一个B[i],就把当前已使用的数目加1,遇到对应的E[i]时,就把当前已经使用的颜色数目减1。其实从作者的伪代码可以分析出,其思路相当于寻找一个最多区间结点构成的完全无向图,若碰到区间结点的开始时间会加入该完全无向图,碰到区间结点的结束时间则会从无向图删除该结点,最终就可以得到一个最大的完全无向图,而完全无向图的最少着色即就是它的结点个数,不过在排序要注意的是:当一个区间的开始时间等于另一个区间的结束时间,则让结束时间排在开始时间的前面。
代码实现:

#include <iostream>
#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
#define MAX 50
#define BEGIN 0
#define END 1
#define FALSE 0
#define TRUE 1
using namespace std;
//面试会时间结点结构体
struct meeting
{
   int value;
   int type;
}TimePints[2*MAX];
bool compare(meeting a,meeting c )
{
   //注意当一个区间的开始时间等于另一个区间的结束时间,则让结束时间排在开始时间的前面
   if(a.value==c.value)
   {
      return a.type==BEGIN? 0:1;
   }
   return a.value<c.value;
}
int findMinColor(int number)
{
   int nMaxcolors=0,nColorUsing=0;
   for(int i=0;i<number;i++)
   {
      //如果是开始时间,该结点加入完全无向图,否则从无向图删除该结点
      if(TimePints[i].type==BEGIN)
      {
         nColorUsing++;
         if(nColorUsing>nMaxcolors)
            nMaxcolors=nColorUsing;
      }
      else
         nColorUsing--;
   }
   cout<<endl;
   return nMaxcolors;
}
int main()
{
   int number;
   printf("input the meeting number smaller than 50:");
   scanf("%d",&number);
   number=number*2;
   int old;
   //测试
   for(int i=0;i<number;i++)
   {
      int x;
      if(i%2==0)
        {
              do
            {
               x=rand()%24+1;
            }while(old==x);
            cout<<"being:"<<x<<endl;
            TimePints[i].type=BEGIN;
        }
      else
         {
            do
            {
               x=rand()%24+1;
            }while(x<=old);
             cout<<"end:"<<x<<endl;
            TimePints[i].type=END;
         }
          TimePints[i].value=x;
          old=x;
   }
   sort(TimePints,TimePints+number,compare);//排序面试会的所有时间
   cout<<findMinColor(number)<<endl;
    return 0;
}

参考资料:
http://blog.csdn.net/starcuan/article/details/19367383
http://blog.csdn.net/chdhust/article/details/8333567

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值