1、 设计题目
用贪心法解决图的着色问题
2 、问题描述
如图所示的交叉路口,有5条通路A,B,C,D,E,其中路C和E是单行路,因而共有13个“拐弯”。有些“拐弯”如AB(从A到B)和EC,可以同时通行,而在AD和EB上行驶的车辆就不能同时通过交叉路口.因此,在分组时,AB和EC应该在同一组。但AD和EB不在同一组。
一个具有多条通路的交叉路口,当允许某些通路上的车辆在交叉路口“拐弯”时,必须对其他一些通路上的车辆加以限制,不许同时在交叉路口“拐弯”,以免发生碰撞。所有这些可能的“拐弯”组成一个集合。现在的问题是要把这个集合分成数量尽可能少的组,使得每组中所有的“拐弯”都能同时进行而不会发生车辆碰撞。这样,每个组对应二个指挥灯,因而实现了用尽可能少的指挥灯完成交叉路口的安全管理。
3、算法设计的思想
1、解决图的着色的一般方法
解决着色问题可以有三种不同的方法。第一种方法是穷举法。按这种方法找出的颜色数目一定是最少的,但是,当图中顶点的数目量很大时,这种方法要消耗大量的计算机时间,因而它不是一个有效的方法。第二种方法是利用提供的与问题有关的附加信息(因而图应该具有某种特殊性质),寻找最有可能成功的颜色数目,只需试探几次就可以解决问题。第三种方法是将问题的提法削弱一些,找到一有效的但不是最优的方法,用这种方法得到的颜色数目不一定是最少的。这是在实际问题求解中经常使用的一种方法。
“贪心”算法就是这样一种算法。“贪心”算法的思想是首先 用第一种颜色对图中尽可能多的顶点着色(“尽可能多”表现出“贪心”);然后用第二种颜色对余下的顶点中尽可能多的顶点着色;如此等等,直到把所有的顶点都着完色。当用一种新颜色对余下的顶点着色时,我们采取下列步骤:
(1)选取某个未着色的顶点,并且用新颜色对它着色。
(2)扫描所有未着色的顶点集,对其中的每个顶点,确定它是否与已着新颜色的任何顶点相邻。若不相邻,则用新颜色对它着色。
2、贪心算法的基本思想
从某一个给定的集合中选出一个子集,满足题目所给的要求,这个子集就是一个可行解。可行解不一定是唯一解。贪心法把可行的工作分阶段来完成。在各个阶段,选择在某些意义下世局部最优解的方案,希望从各个阶段的局部最优选择,能够得到整体最优。但是,贪心法不是每次都能产生整体最优解。贪心法在每个阶段都保持局部最优,而各个阶段结果合起来,总的结果可能不是最优.
贪心算法常用来求“最优解”这类问题的。最优解问题可描述为:有n个输入,它的解是由这n 个输入的某个子集组成,并且这个子集必须满足事先给定的条件。这个条件称为约束条件。而把满足约束条件的子集称为该问题的可行解。这些可行解可能有多个。为了衡量可行解的优劣,事先给了一个关于可行解的函数,称为目标函数。目标函数最大(或最小)的可行解,称为最优解。
a) 求“最优解”最原始的方法为搜索枚举方案法(一般为回溯法)。
b) 贪心算法则不同,它不是建立在枚举方案的基础上的。它从前向后,根据当前情况,“贪心地”决定出下一步,从而一步一步直接走下去,最终得到解。
c) 贪心算法是一种比动态规划更高效的算法。只是要保证得到最优解是贪心算法的关键。
4、算法设计分析
贪心算法:该算法给出的是近似解。
while 有结点未着色;
{ 选择一种新颜色;
在未着色的结点中,给尽可能多的彼此结点之间没有边点着色;
}
把上面方法应用于图1.2,得到下面的分组:
绿色:AB, AC, AD, BA, DC, ED
蓝色:BC, BD, EA
红色:DA, DB
黄色:EB, EC
假设需要着色的图是G,集合V1包括图中所有未被着色的结点,着色开始时V1是G所有结点集合。NEW表示已确定可以用新颜色着色的结点集合。
从V1中找出可用新颜色着色的结点集的程序框架描述为:
NEW={ };
for 每个v Î V1 do
if v与NEW中所有结点间都没有边
{ 从V1中去掉v ;
NEW=NEWÈ{v} ;
}
用C语言描述的“贪心”算法如下:
void greedy(G,newelr)(动画)
GRAPH G;
SET newelr;//类型GRAPH和SET有待具体说明//
//本程序把G中可以着同一色的顶点放入newclr*//
{ (1) newelr=φ;//φ表示空集//
(2) while(G中有未着色的顶点v)
(3) if(v不与newelr中的任何顶点相邻){
(4) 对v着色;
(5) 将v放人newclr;
}
其中,G是被着色的图,newelr的初值为空,算法执行的结果形成可以着相同颜色的顶点的集合newclr。只要重复调用greedy算法,直到图中的所有顶点都被着色为止,即可求出问题的解。
第一步求精:
现对上述greedy算法中的语句(3)(条件语句)进一步求精:我们使用一个布尔量found,初值为false:对newclr中的每个顶点w,测v与w是否相邻,若相邻,则令found为true。因而得到下列结果:
void greedy(G,newclr)
GRAPH GSET newclr //类型GRAPH和SET有待具体说明//
{
int found;
(1) newclr=φ
(2) while(G中有来着色的顶点v){
(3.1) found=0//found的初值为false//
(3.2) for(newclr中的每个顶点w)
(3.3) if(v与w相邻)
(3.4) found=1;
(3.5) if(found==0){ //v与newclr中的任何顶点都不相邻//
(4) 对v着色;
(5) 将v放人newclr
}
}
}
第二步求精:
用C中的控制语句对第一步求精的结果作更加精细的描述,具体如下:
void greedy(G,newclr)
GRAPH G;SET newclr; //类型-GRAPH和SET有待具体说明//
{ int found;
newclr=φ;
v=G中第一个未着色的顶点;
while(v!=0){ //G中还有未着色的顶点v//
found=0;
w=newclr中的第一个顶点;
while(w!=0){//newclr中的顶点还没取尽。/
证(v与w相邻)
found=1
w=newclr中的下个顶点
}
if(found==0){
对v着色,
将v放人newclr;
v=G中下一个未着色的顶点;
}
第三步求精:
由上一步求精的结果可见,算法中的大部分操作都归结为对图和集合的操作。设G和S分别是抽象数据型GRAPH和SET的实例,我们在G上规定如下操作:
(1)FIRSTG(G)返回G中的第一个未加标记(未着色)的元素’;若G中没有这样的元素存在,则返回NULL。
(2)EDGE(V,w,G)若V和w在G中相邻,则返回true,否则返回false。
(3)MARK(v,G)标记G中的元素v。
(4)ADDG(v,G)将元素v放人G中。
(5)NEXTG(G)返回G中下一个未标记的元素,若G中没有这样的元素存在,则返回NULL。
在S上规定如下操作:
(1)MAKENULL(S)将集合S置空。
(2)FIRSTS(S)返回S中的第一个元素;若S为空集,则返回NULL。
(3)NEXTS(S)返回S中的下一个元素,若S中没有下一个元素,则返回NUlL。
(4)ADDS(v,S)将v放人S中。
利用这些操作代替上述函数中的文字描述,即可得到如下更加形式化的函数: vold greedy(G,rlewclr)
GRAPH G'SET newclr://类型GRAPH和SET有待说明//
{
int found,
elementtypev,w;//'elementtype可以自定义//
MAKENULL(newtlr);
V=FIRSTG(G);
while(v!=NULL){
found=0;
w=FIRSTS(newclr);
while(w!=NULL){
订(EDGE(v,w,G))
found;1;
w=NEXTS(newclr);
}
订(found==0){
MARK(v,G):
ADDS(v,newclr),
}
v=NEXTG(G);
}
}
按上述函数,最后一步工作就是给出类型Elementtype的定义和实现抽象数据型GRAPHSET。此后,上述函数就是可执行的程序了。
5、算法设计的流程图
是 设置初始化的方案 (将图中第一个没有着色的付给v) 当G图中没有被着色的点非零 令Found=0,判断newelr中的每一个点是否与v相邻 图中所有的点都已被着色,结束程序 对v点着色,然后将v放入newelr中 否 否
6、源代码
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <graphics.h>
#define S 50
int proved;
int menu(void);
int input(int Matrix[S][S]);
FILE *LoadSaveMenu(char *wr) ;
void save(int Matrix[S][S],int s);
int load(int Matrix[S][S]);
int prove(int Matrix[S][S],int Dis[],int s);
void print(int Dis[],int s);
void Exit();
/*创建二维数组Matrix[][]表示关联矩阵,一维数组Dis[]表示国家颜色,以switch调用各个函数进行证明和操作*/
void main()
{
int Matrix[S][S]; /* 存放关联矩阵 */
int Dis[S]; /* 存放每个国家对应的颜色 */
int s; /* 区域数目 */
int c;
FILE *fp;
int gdriver=DETECT,gmode;
initgraph(&gdriver,&gmode," ");
clrscr();cleardevice();
while(1)
{
clrscr();cleardevice();
c=menu();
clrscr();cleardevice();
switch(c)
{
case 1: s=input(Matrix);
break;
case 2: save(Matrix,s);
break;
case 3: s=load(Matrix);
break;
case 4: proved=prove(Matrix,Dis,s);
break;
case 5: print(Dis,s);
break;
case 6: Exit();
break;
default: break;
}
printf("/n/tPress any key to main menu.....");
getch();
}
}
/*图形主菜单,按上下键选择功能,返回所选项给main */
int menu(void)
{
int gdriver=DETECT,gmode;
char *name[]={"Input relatived districts","Save the record","Load the record","Try to prove","Print the provement","Exit to DOS"};
int i,c=' ',a=0;
clrscr();cleardevice();
initgraph(&gdriver,&gmode," ");
setbkcolor(0);
settextstyle(1,0,5);
setcolor(14);
outtextxy(200,60,"Menu");
settextstyle(3,0,3);
while(1)
{
for(i=0;i<6;i++)
{
if(i==a)
{ setcolor(5);
outtextxy(150,30*(i+4),name[i]);
i++;
setcolor(15);
}
if(i>5) break;
outtextxy(150,30*(i+4),name[i]);
}
c=getch();
switch(c)
{
case 72: a--; break;
case 80: a++; break;
default: break;
}
if(a<0) a++;
if(a>5) a--;
if(a==0&&c==13)break;
if(a==1&&c==13)break;
if(a==2&&c==13)break;
if(a==3&&c==13)break;
if(a==4&&c==13)break;
if(a==5&&c==13)break;
}
return(a+1);
}
/*输入函数, 要求定义区域的数目,输入相关联的每两个区域,返回区域数目*/
int input(int Matrix[S][S])
{
int i,j,s;
printf("/n/tPlease input how much district you want: ");
/* 定义区域的数目 */
scanf("%d",&s);
s++;
for(i=1;i<s;i++)
for(j=1;j<s;j++)
{
if(i!=j) Matrix[i][j]=0;
if(i==j) Matrix[i][j]=1;
}
do{ clrscr();cleardevice();
printf("/n/tdistrict: "); /* 输入相关联的两个区域 */
scanf("%d",&i);
printf("/tand district : ");
scanf("%d",&j);
Matrix[j][i]=Matrix[i][j]=1;
printf("/tEnter 'q' to out ,others to continue....../n");
}while(getch()!='q');
proved=0; /* 没证明前proved为0 */
return(s);
}
/*按 save 或 load 传来的 wb 或rb 对所选文件进行读或写操作,返回文件指针fp */
FILE *LoadSaveMenu(char *wr)
{
FILE * fp;
char c,solas[25];
printf("/n/tPlease input the recordlist you wantto select:");
/* 选择读取或存入的文件 */
printf("/n/t[1]China map [2]World map [3]save/load as :");
c=getch(); printf("%c/n",c);
switch(c)
{
case '1' : fp=fopen("China.txt",wr);
break;
case '2' : fp=fopen("World.txt",wr);
break;
case '3' : printf("/n/tPlease input your file name: ");
scanf("%s",solas);
fp=fopen(solas,wr);
break;
default : break;
}
return fp;
}
/*把现在的Matrix矩阵存入到Loadsavemenu返回的fp指向的文件中*/
void save(int Matrix[S][S],int s)
{
int i,j;
FILE * fp;
fp=LoadSaveMenu("wb");
fputc(s,fp); /* 存入区域个数 */
for(i=1;i<s;i++)
for(j=1;j<s;j++)
fwrite(&Matrix[i][j],sizeof(int),1,fp); /* 存入矩阵 */
fclose(fp);
}
/*在Loadsavemenu 返回的fp中读出已记录的矩阵*/
int load(int Matrix[S][S])
{
int i,j,s;
FILE * fp;
fp=LoadSaveMenu("rb");
clrscr();cleardevice();
printf("/n/tThe matrix is as follow:/n");
s=fgetc(fp);
for(i=1;i<s;i++)
{
if(i%17==0) {printf("/n/n/tPress any key to continue.."); getch(); clrscr();cleardevice();}
printf("/n/t/t");
for(j=1;j<s;j++)
{
fread(&Matrix[i][j],sizeof(int),1,fp);
printf("%d",Matrix[i][j]);
}
}
fclose(fp);
proved=0;
return(s);
}
int prove(int Matrix[S][S],int Dis[],int s)
{
int i,j,k;
int Color[5]; /* 用于判断第1,2,3,4种颜色是否用过 */
Dis[1]=1; /* 第一个区域即Dis[1]填上1号颜色 */
i=2; /* i表示区域序号,i=2即第二个区域 */
while(i<s)
{
k=1; /* k表示颜色序号,k=1为第一号颜色 */
while(1)
{
for(j=1;j<=4;j++) Color[j]=0; /* 储存颜色的数组Color清0 */
j=1;
while(j<=i-1)
{ if(Matrix[i][j]==1) Color[Dis[j]]=-1; /*判断Matrix[i][j]是否关联,关联则登记这号颜色用过 */
j++;}
while(k<=4)
{ if(Color[k]==0) goto loop;
k++;} /* 判断k号颜色是否用过 */
i=i-1; /* 若颜色已用尽,则退回到前一个区域改变颜色 */
k=Dis[i]+1; /* 退到前一个区域,使其当前颜色号加1 */
}
loop:Dis[i]=k; /* 把第k种颜色给第i个区域 */
i=i++;
}
printf("/n/tThe matrix have proved successfully !!!");
proved=1;
printf("%d",proved);
}
/*由全局变量proved决定是否已经通过证明,当且仅当证明后才可打印出来*/
void print(int Dis[],int s)
{
int i,j;
if(proved==1)
{
printf("/n/tThe color schemes is:/n");
for(i=1;i<s;i++)
{if(i%17==0) {getch();clrscr();cleardevice();}
printf("/n/tdestrict %-2d draw ",i);
switch(Dis[i])
{
case 1: printf("orange"); /* 设1号颜色对应橙色,类推 */
break;
case 2: printf("pink");
break;
case 3: printf("blue");
break;
case 4: printf("yellow");
break;
}
}
}
else
{
printf("/n/tThis matrixhaven'tproved, Gotoprove frist !!!");
return;
}
}
/*安全退出本程序*/
void Exit()
{
char c;
printf("/n/tDo you really want to return to DOS?(y/n)");
c=getch();
if(c=='y'||c=='Y') exit(0);
else return;
}
7 、运行结果与分析
8、收获及体会
通过这次作业,我对贪心法有了初步的了解,贪心法是一种依据题意,选取一种度量标准的顺序,以尽可能快地求得满意解的方法。它省去了为查找最优解而去穷尽所有可能而耗费的大量时间。并不是所有的问题都可以用贪心法解决,只有那些选择具有无后效性,即不依赖与以后将要作出的选择的问题才能有贪心法。另外,图的着色也是一个非常重要的问题,将它运用到路口指示等上面,就可以使用最少的不同颜色的灯指挥交通。总之,贪心法是一个非常快捷的算法,能节约大量的时间。