图的m着色问题也是经典的回溯问题,我们也可以使用DFS来解决。图的着色还有一个作用,那就判断二分图,将一个群体分成两拨,则这两拨应该着不同颜色。
问题描述
给定无向图G和m种不同的颜色,用这些颜色为图G的各顶点着色,每个顶点着一种颜色。是否有一种着色法,使G中每条边的2个顶点着有不同的颜色?这个问题是图的m可着色判定问题。若一个图最少需要m种颜色才能使图中每条边连接的2个顶点着不同颜色,则称这个数m为图的色数。求一个图的色数m的问题称为图的m可着色优化问题。
如果一个图的所有顶点和边都能用某种方式画在平面上且没有任何两边相交,则称这个图是可平面图。著名的平面图的四色猜想是图的m可着色性判定问题的特殊情形。
算法设计
我们需要写出的图的邻接矩阵,某个点的相邻节点我们都要着异色。如果不是m可着色的,我们就给出否定答案,否则找出所有不同的着色方法。
整数1,2…,m表示m种不同的颜色,顶点i所着颜色用x[i]表示。我们的解空间是一个n+1层m叉的树,树中每个内节点都有m个儿子,这些点都可以着不同的颜色,所以我们的解空间应该是一个排列树。一共有n个点,每个点都有m个着色可能,所以总的着色种类有m*m...*m=m^n
下图为n=3,m=3时问题的解空间树
Backtrack(i)用来搜索第i层的子树,类Color的数据成员记录解空间中结点信息,以减少传给Backtrack的参数,sum记录当前已经找到的可m着色方案。
当i>n时,说明我们已经到达了叶子结点处,得到新的m着色方案,当前找到的可m着色方案数sum+1。
当i<=n时,如果当前着色点和它的相邻点着了同色,那么我们就应该换一个颜色对其着色。我们采用深度优先搜索的方式递归地对可行子树搜索,或剪去不可行子树。
代码
1.递归法
class Color{
friend int mColoring(int,int,int**);
private:
bool ok(int i);
void Backtrack(int t);
int n,//图中的顶点数
m,//颜色种类
**a,//图的邻接矩阵
*x,//当前着色结果
long sum;//方案数
bool Color::ok(int i)
{
for(int j = 1;j <= n;j++)
{
if((a[i][j]==1) && (x[i]==x[j]))
return false;
}
return true;
}
void Color::Backtrack(int t)
{
if(t > n)
{
sum++;
for(int i = 1;i <= n;i++)
cout<<x[i]<<" ";
}
else
{
for(int i = 1;i <= m;i++)
{
x[t] = i;
if(ok(t))
{
Backtrack(t+1);
}
x[t] = 0;
}
}
}
int mColoring(int n,int m,int **a)
{
Color X;
X.n = n;
X.m = m;
X.a = a;
X.sum = 0;
int *p = new int[n+1];
for(int i = 0;i <= n;i++)
p[i] = 0;
X.x = p;
X.Backtrack(1);
delete []p;
return X.sum;
}
};
2.迭代法
class Color{
friend int mColoring(int,int,int**);
private:
bool ok(int i);
void GraphColor(int n,int m,int color[]);
int n,//图中的顶点数
m,//颜色种类
**a,//图的邻接矩阵
*x,//当前着色结果
long sum;//方案数
bool Color::ok(int i)
{
for(int j = 1;j <= n;j++)
{
if((a[i][j]==1) && (x[i]==x[j]))
return false;
}
return true;
}
void Color::GraphColor(int n,int m,int color[])
{
int i,k;
//初始化color
for(i = 0;i < n;i++)
color[i] = 0;
k = 0;
while(k >= 0)
{
color[k] = color[k] + 1;
while((color[k] <= m) && (!ok(color,k,c,n)))
//当前不满足要求则可以换一种颜色填入
color[k] = color[k] + 1;
if(color[k] <= m)
{
//向下递归,逐层向下涂色
if(k == n - 1)
break;
else
{
k += 1;
}
}
else
{
//不满足要求的话就回溯,将这个颜色撤出
color[k] = 0;
k -= 1;
}
}
}
int mColoring(int n,int m,int **a)
{
Color X;
X.n = n;
X.m = m;
X.a = a;
X.sum = 0;
int *p = new int[n+1];
for(int i = 0;i <= n;i++)
p[i] = 0;
X.x = p;
X.Backtrack(1);
delete []p;
return X.sum;
}
};
总结
我们发现,图中的内结点数为对于每个内结点,在最坏情况下,用ok()函数检查当前扩展结点的每个儿子所对应的颜色的可行性需耗时O(mn)。因此,回溯法总的时间耗费为:
共m^(n-1)个内结点(最后一层是叶子结点),每个结点需要耗时O(mn)来检查。