用贪心法解决图的着色问题

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包括图中所有未被着色的结点,着色开始时V1G所有结点集合。NEW表示已确定可以用新颜色着色的结点集合。

V1中找出可用新颜色着色的结点集的程序框架描述为:

   NEW={ };

  for   每个v Î V1 do

   if  vNEW中所有结点间都没有边

    {  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(Gnewclr)

    GRAPH GSET newclr     /类型GRAPHSET有待具体说明/

    {    

        int found;

  (1)    newclr=φ

  (2)     while(G中有来着色的顶点v){

(31)  found=0/found的初值为false/   

(32)  for(newclr中的每个顶点w)

(33)  if(vw相邻)   

(34)  found=1

(35)  if(found==0){    /vnewclr中的任何顶点都不相邻/

(4)                   对v着色;

(5)                   将v放人newclr

                     }

                }

 }

  第二步求精:

  用C中的控制语句对第一步求精的结果作更加精细的描述,具体如下:

    void greedy(Gnewclr)

    GRAPH GSET newclr    /类型-GRAPHSET有待具体说明/

    { 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(Grlewclr) 

 GRAPH G'SET newclr://类型GRAPHSET有待说明/

    {   

       int found

       elementtypevw;//'elementtype可以自定义/

       MAKENULL(newtlr)   

       V=FIRSTG(G)

       while(v!=NULL){

       found0

       w=FIRSTS(newclr)

       while(w!NULL){

       (EDGE(vwG))

       found1

       wNEXTS(newclr)

       }

      (found=0){

       MARK(vG)

       ADDS(vnewclr)

       }

       v=NEXTG(G);

      }

  }   

    按上述函数最后一步工作就是给出类型Elementtype的定义和实现抽象数据型GRAPHSET。此后,上述函数就是可执行的程序了。

5、算法设计的流程图

 

设置初始化的方案

(将图中第一个没有着色的付给v)

G图中没有被着色的点非零

Found0,判断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;        /*  
没证明前proved0 */
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]; /*
用于判断第1234种颜色是否用过  */
  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;    /*  
储存颜色的数组Color0  */
    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、收获及体会

    通过这次作业,我对贪心法有了初步的了解,贪心法是一种依据题意,选取一种度量标准的顺序,以尽可能快地求得满意解的方法。它省去了为查找最优解而去穷尽所有可能而耗费的大量时间。并不是所有的问题都可以用贪心法解决,只有那些选择具有无后效性,即不依赖与以后将要作出的选择的问题才能有贪心法。另外,图的着色也是一个非常重要的问题,将它运用到路口指示等上面,就可以使用最少的不同颜色的灯指挥交通。总之,贪心法是一个非常快捷的算法,能节约大量的时间。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值