1、问题描述
给定无向连通图G=(V,E)和m种不同的颜色。用这些颜色为图G的各顶点着色,每个顶点着一种颜色。
是否有一种着色法使G中每条边的2个顶点着不同颜色。这个问题是图的m可着色判定问题。
输入:图的顶点的个数,颜色种类树m。输出顶点a与顶点b,表示a与b之间有一条边。
输出:打印所有方案,输出顶点Ai(1<=i<=n)的颜色种类编号,可着色的方案总数。
运行结果:
2、算法设计
本节讨论给定连通图G=(V,E)和m种颜色,如果该图不是m可着色,给出否定回答;若m可着色,找出所有不同着色方法。
约束条件:顶点k与已着色的相邻结点颜色不重复
下面根据回溯法的递归描述框架Backtrack设计图的m着色算法,用图的邻接矩阵a表示无向连通图G= (V,E)。若(i,j)属于图G=(V,E)的边集E,则a[i][j] = 1,否则a[i][j]=0,整数 1,2,....m用来表示m种不同颜色。顶点i所着的颜色用x[i]表示。数组x[1:n]是问题的解向量。问题的解空间可表示为一颗高度为n+1的完全m叉树,解空间树的第i(1<=i<=n)层中每一结点都有m个儿子,每个儿子相应于x[i]的m个可能的着色之一。第n+1层结点均为叶结点。
在下面的解图的m可着色问题的回溯法中。Backtrack(i)搜索解空间中第i层子树。类Color的数据成员记录解空间中结点信息,以减少传给Backtrack的参数。sum记录当前已找到的可m着色方案数。
在算法Backtrack中,当i>n时,算法搜索至叶结点,得到新的m着色方案,当前找到的可m着色方案数sum增1.
当i<=n时,当前扩展结点Z是解空间中的内部结点,该结点有x[i] = 1,2,....m共m个儿子结点。对当前扩展结点Z的每一个儿子结点,由函数OK检查其可行性,并以深度优先的方式递归的对可行子树进行搜索,或剪去不可行子树。
class Color
{
friend int mColoring(int, int, int **);
private:
void BackTrace(int t);
bool Ok(int k);
int n, //图的顶点数
m, //可用颜色数
**a, //a表示图的邻接矩阵,a[k][i]表示第k个顶点和第i个顶点有边相连
*x, //x[k]表示连通图中第k个结点的颜色,x[i]表示连通图中第i个顶点的颜色
sum; //当前已找到的可m着色方案数量
};
//判断顶点k的着色是否与前面已着色的k-1个顶点发生冲突
bool Color::Ok(int k)
{
int i;
for(i = 1; i < k; i++)
if(a[k][i] == 1 && x[i] == x[k]) //顶点相连 顶点同色
return false;
return true;
}
//对解空间树回溯搜索,求得可行着色方案数量
void Color::BackTrace(int t)
{
int i;
if(t > n) //算法搜索到叶节点,得到一个新的每条边的两个顶点着不同颜色的m着色方案
{
sum++; //当前已找到的着色方案数sum+1
for(i = 1; i <= n; i++) //输出找到的当前着色方案
printf("%d ", x[i]);
printf("\n");
return;
}
for(i = 1; i <= m; i++) //该结点有x[i]=1,2..m个儿子结点,表示第k个顶点可着m种颜色,
{ //搜索当前扩展结点的每一个儿子结点
x[t] = i;
if(Ok(t)) //检查可行性
BackTrace(t+1); //满足约束函数,已深度优先的方式对子树搜索,不满足就剪去以该结点为根的子树
x[t] = 0;
}
}
//负责对变量初始化,调用递归函数,BackTrack(1)实现回溯搜索并返回可行m着色方案数量
int mColoring(int n, int m, int **a)
{
Color X;
int *p, i;
p = new int[n+1];
for(i = 1; i <= n; i++)
p[i] = 0;
X.n = n;
X.m = m;
X.a = a;
X.x = p;
X.sum = 0;
X.BackTrace(1); //求得可行m着色方案数量
delete []p;
return X.sum; //返回可行m着色方案数量
}
3、算法效率
图m可着色问题的回溯算法的计算时间上界可以通过计算解空间树中内结点个数来估计。图m可着色问题的解空间树中内结点个数是。对于每一个内结点,在最坏情况下,用OK检查当前扩展结点的每一个儿子所相应的颜色的可用性需耗时O(mn)。因此,回溯法总的时间耗费是