编程之美有一道是关于图的最少着色的题目,题目如下:
在校园招聘的季节里,为了能让学生们更好地了解微软亚洲研究院各研究组的情况,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( (n−1)n ),验证一种颜色配置是否满足要求需要的时间复杂度O( n2 ),所以总共的时间复杂度是O( (n−1)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