图的m着色问题
一:问题描述
给定无向连通图G=(V,E)和m种不同的颜色。用这些颜色为图G的各顶点着色,每个顶点着一种颜色。如果有一种着色法使G中有边相连的两个顶点着不同的颜色,则称这个图是m可着色的。图的m着色问题是对于给定图G和m中颜色,找出所有不同的着色方法。
二:问题分析
该问题中每个顶点所着的颜色均有m中选择,n个顶点所着颜色的一个组合是一个可能的解。根据回溯法的算法框架,定义问题的解空间及其组织结构是很容易的。从给定的已知条件看,无向连通图G中假设有n个顶点,它肯定至少有n-1条边,有边相连的两个顶点所着颜色不相同,n个顶点所着颜色的所有组合中必然存在不是问题着色方案的组合,因此需要设置约束条件;而针对所有可行解(组合),不存在可行解的优劣问题。所以不需要设置限界条件。
三:算法设计
I.定义数据结构
定义问题的解空间为数组x[]
‚ 定义无向连通图的存储空间和形式,为二维数组a[][]
ƒ定义数组b[3],b[0]存储无向连通图的节点个数,b[1]存储无向连通图的边数,b[2]存放着色方案的个数。
II.确定解空间的组织结构
问题的解空间组织结构是一颗满m叉树,树的深度为n(n指连通图结点数)
III.搜索解空间
IV.设置约束条件
题中条件为,当前顶点要与前面已经确定颜色且有边相连的顶点的颜色不相同。假设当前扩展结点所在的层次为t,则下一步扩展就是要判断第t个顶点着什么颜色,第t个顶点所着的颜色要与已经确定的第1-(t-1)个顶点中与其有边相连的颜色不相同。
所以,约束函数可描述为:
int ok(int a[M][M],int x[M],int t) { for(int j=1;j<t;j++) if(a[t][j]) { if(x[j]==x[t]) return 0; } return 1; }
V.无需设置限界条件
VI.搜索过程。拓展结点沿着某个分支拓展时需要判断约束条件,如果满足,则进入深一层次继续搜索;如果不满足,则拓展生成的结点被剪掉。搜索到叶子结点时,找到一种着色方案。搜索过程直到所有活结点变成死结点为止。
四:实例构造
如下图所示的无向连通图和m=3.
搜索过程:
从根结点A开始,结点A是当前的活结点,也是当前的拓展节点,太代表的状态是给定的无向连通图中任何一个顶点都还没有着色。沿着x[1]=1分支拓展,满足约束条件,生成的结点B将成为活结点,并且成为当前的拓展结点。拓展结点B沿着x[2]=1拓展,不满足约束条件,生成的结点被剪掉,然后沿着x[2]=2,分支拓展,满足约束条件,生成的结点C成为活结点,并且成为当前的拓展结点.....等等,其他搜索依次进行,由下述程序可得最终结果,有6中着色方案,即(1 2 3 1 3)、(1 3 2 1 2)、(2 1 3 2 3)、(2 3 1 2 1)、(3 1 2 3 2)、(3 2 1 3 1)。
C/C++程序如下:
1 #include<stdio.h> 2 #define M 30 3 //图的m着色问题 4 void print(int a[M][M],int b[3]) 5 { 6 int q=0,p=0,i=0,j=0,c=0,d=0; 7 //输入 8 printf("请输入无向连通图的节点数:\n"); 9 scanf("%d",&q); 10 b[0]=q; 11 printf("请输入无向连通图的边数:\n"); 12 scanf("%d",&p); 13 b[1]=p; 14 for(i=0;i<p;i++) 15 { 16 printf("第%d条边的起点 终点:",i+1); 17 scanf("%d%d",&c,&d); 18 a[c][d]=1; 19 a[d][c]=1; 20 } 21 //输出 22 printf("无向连通图矩阵形式:\n"); 23 for(i=0;i<=q;i++) 24 { 25 for(j=0;j<=q;j++) 26 { 27 printf("%4d",a[i][j]); 28 } 29 printf("\n"); 30 } 31 } 32 33 int ok(int a[M][M],int x[M],int t) 34 { 35 for(int j=1;j<t;j++) 36 if(a[t][j]) 37 { if(x[j]==x[t]) 38 39 return 0; 40 } 41 return 1; 42 } 43 44 void Backtrack(int t,int x[],int m,int b[3],int a[M][M]) //m种颜色 45 { 46 47 if(t>b[0]) 48 { 49 b[2]++; 50 printf("第%d种着色方案:\n",b[2]); 51 for(int i=1;i<=b[0];i++) 52 { 53 printf("%4d",x[i]); 54 } 55 printf("\n"); 56 } 57 else 58 { 59 for(int i=1;i<=m;i++) 60 { 61 x[t]=i; 62 if(ok(a,x,t)!=0) 63 Backtrack(t+1,x,m,b,a); 64 } 65 } 66 } 67 68 void main() 69 { 70 int a[M][M]={0}; 71 int x[M]; //解空间 72 int b[3]={0};//b[0]存放节点总数 b[1]存放边的总数 73 int m=3; //m种颜色 74 print(a,b); 75 Backtrack(1,x,m,b,a); 76 77 }
//************************运行结果如下**********************//
/*
请输入无向连通图的节点数:
5
请输入无向连通图的边数:
7
第1条边的起点 终点:1 2
第2条边的起点 终点:1 3
第3条边的起点 终点:2 3
第4条边的起点 终点:2 4
第5条边的起点 终点:2 5
第6条边的起点 终点:3 4
第7条边的起点 终点:4 5
无向连通图矩阵形式:
0 0 0 0 0 0
0 0 1 1 0 0
0 1 0 1 1 1
0 1 1 0 1 0
0 0 1 1 0 1
0 0 1 0 1 0
第1种着色方案:
1 2 3 1 3
第2种着色方案:
1 3 2 1 2
第3种着色方案:
2 1 3 2 3
第4种着色方案:
2 3 1 2 1
第5种着色方案:
3 1 2 3 2
第6种着色方案:
3 2 1 3 1
Press any key to continue*/
五:算法描述
在算法描述中,数组x[]记录着色方案,b[]数组中元素b[2]记录着色方案的种数,初始值为0;m为给定的颜色数;该算法的关键是判断当前的结点可以着那种颜色。图的m着色问题的算法描述如下:
void Backtrack(int t,int x[],int m,int b[3],int a[M][M]) //m种颜色 { if(t>b[0]) { b[2]++; printf("第%d种着色方案:\n",b[2]); for(int i=1;i<=b[0];i++) { printf("%4d",x[i]); } printf("\n"); } else { for(int i=1;i<=m;i++) { x[t]=i; if(ok(a,x,t)!=0) Backtrack(t+1,x,m,b,a); } } }
从根结点开始搜索着色方案,即Backtrack(1,x,m,b,a)。
六:算法分析
计算限界函数需要O(n)时间,需要判断限界函数的结点在最坏的情况下有1+m+m^2+m^3+...+m^(n-1)=(m^n-1)/(m-1)个,故耗时O(n*m^n);在叶子结点处输出着色方案需要耗时O(n),在最坏的情况下会搜索到每一个叶子节点,叶子节点有m^n个,故耗时为O(n*m^n)。图的m的着色问题的回溯算法所需的时间为O(n*m^n)+O(n*m^n)=O(n*m^n)。
七:小结
1.当所给的问题的n个元素中每一个元素均有m中选择,要求确定期中的一种选择,使得对这n个元素的选择结果向量满足某种性质,这类问题的解空间称为满m叉树。均可以用上述算法进行求解。
2.上述C/C++程序中也可改进为将m的值由用户输入,然后存入b[4]数组中的元素b[3],以方便求m为不同值时所需的着色情况。