图着色问题
1. 问题概述
图着色问题,是最著名的 NP-完全问题之一。道路着色问题是图论中最著名的猜想之一。
- 数学定义:
给定一个无向图 G=(V, E),其中 V 为顶点集合,E 为边集合,图着色问题即为将 V 分为 K 个颜色组,每个组形成一个独立集,即其中没有相邻的顶点。其优化版本是希望获得最小的 K 值。
- 问题描述:
图的 m-着色判定问题——给定无向连通图 G 和 m 种不同的颜色。用这些颜色为图 G 的各顶点着色,每个顶点着一种颜色,是否有一种着色法使 G 中任意相邻的 2 个顶点着不同颜色?
图的 m-着色优化问题——若一个图最少需要 m 种颜色才能使图中任意相邻的 2 个顶点着不同颜色,则称这个数 m 为该图的色数。求一个图的最小色数 m 的问题称为 m-着色优化问题。
如图中所示,把每一个矩形收缩为一个顶点,把相邻的两个矩形用一条边相连接,就可以把一个矩形抽象为一个平面图
例: 图左)所示的矩形图可抽象为图(右) 所表示的平面图。矩形区域用数字表示,颜色用图中的各个颜色表示,则图中表示了不同矩形区域的不同着色问题。
2. 算法思想
- 我们可以遍历图的每一个顶点,并对当前顶点开始着色;
- 着色时需要保证该顶点的颜色与相邻顶点的颜色不相同;
- 如果相同了需要用其他颜色进行着色;
- 当最后一个顶点着色完成时,则已经求出一种着色方案了,输出着色方案即可;
- 如果当前着色的不是最后一个顶点,则继续对下一个顶点进行着色即可;
3. 求解过程
举例:用 黄 , 绿 , 紫 , 红 四种颜色对 图 着色。
第一步:
- 对顶点 1 涂黄色,并判断改颜色与相邻的顶点的颜色是否相同;
- 此时顶点 1 的颜色与相邻顶点的颜色不相同,因此继续对下一个顶点进行着色即可;
第二步:
- 对顶点 2 涂黄色,并判断改颜色与相邻的顶点的颜色是否相同;
- 此时顶点 2 的颜色与相邻顶点的颜色相同,因此继续对顶点 2 着色,如绿色;
- 此时顶点 2 的颜色与相邻顶点的颜色不相同,因此继续对下一个顶点进行着色即可;
第三步:
- 对顶点 3 涂黄色,并判断改颜色与相邻的顶点的颜色是否相同;
- 此时顶点 3 的颜色与相邻顶点的颜色相同,因此继续对顶点 3 着色,如绿色;
- 此时顶点 3 的颜色与相邻顶点的颜色相同,因此继续对顶点 3 着色,如紫色;
- 此时顶点 3 的颜色与相邻顶点的颜色不相同,因此继续对下一个顶点进行着色即可;
第四步:
- 对顶点 4 涂黄色,并判断改颜色与相邻的顶点的颜色是否相同;
- 此时顶点 4 的颜色与相邻顶点的颜色相同,因此继续对顶点 4 着色,如绿色;
- 此时顶点 4 的颜色与相邻顶点的颜色相同,因此继续对顶点 4 着色,如紫色;
- 此时顶点 4 的颜色与相邻顶点的颜色相同,因此继续对顶点 4 着色,如红色;
- 此时顶点 4 的颜色与相邻顶点的颜色不相同,因此继续对下一个顶点进行着色即可;
第五步:
- 对顶点 5 涂黄色,并判断改颜色与相邻的顶点的颜色是否相同;
- 此时顶点 5 的颜色与相邻顶点的颜色不相同;
- 此时对最后一个顶点着完色了,输出着色方案;
最终着色方案为: 顶点 1 和顶点 5 是黄色,顶点 2,顶点 3,顶点 4 分别为绿色,紫色,红色。
4. 算法具体步骤
- 输入顶点数和着色数并存到变量 n,m 中;
- 输入无向图的邻接矩阵,并用二维数组进行存储;
- 开始着色;
- 循环每一个顶点,循环变量为 k;
- 对顶点 k 着一种颜色;
- 判断顶点 k 的颜色与其相邻顶点的颜色是否相同,如果相同则着另一种颜色后继续判断是否与其 1. 相邻顶点的颜色相同;
- 如果当前着色的是最后一个顶点,则表示已经完成了着色,输出着色方案;
- 如果不是最后一个顶点则表示还没对所有的顶点着完色,此时对下一个顶点进行着色即可;
完整代码:
#include <stdio.h>
int color[100];
bool ok(int k,int c[][100]) { //判断顶点k的着色是否发生冲突
int i,j;
for(i = 1; i < k; i++) {
if(c[k][i]==1 && color[i] == color[k]) // 数组中为1,并且颜色与已有的颜色相同
return false;
}
return true;
}
// n:顶点数,m:着色数,c:图的邻接矩阵
void graphcolor(int n,int m, int c[][100]) {
int i, k;
for(i = 1; i <= n; i++)
color[i] = 0; // 颜色数组初始化为0
k = 1; // 循环变量,表示数组的第k行
while(k >= 1) {
color[k] = color[k] + 1; // 顶点k颜色标记 + 1
while(color[k] <= m) // 是否用完了可用的颜色
if(ok(k,c)) //判断顶点k的着色是否发生冲突
break;
else
color[k] = color[k] + 1;//搜索下一个颜色
if(color[k] <= m && k == n) { // 颜色没有用完并且处理完了最后一个顶点
for(i = 1; i <= n; i++) // 开始打印颜色方案
printf("%d",color[i]);
printf("\n");
} else if(color[k] <= m && k < n) // 颜色没有用完并且当前处理的不是最后一个顶点
k = k + 1; //处理下一个顶点
else {
color[k] = 0;
k = k - 1;//回溯
}
}
}
int main() {
int i, j, n, m;
int c[100][100]; //存储n个顶点的无向图的数组
printf("输入顶点数n和着色数m:\n"); // n:顶点数,m:着色数
scanf("%d%d", &n, &m);
printf("输入无向图的邻接矩阵:\n");
for(i = 1; i <= n; i++)
for(j = 1; j <= n; j++)
scanf("%d", &c[i][j]);
printf("着色所有可能的解:\n");
graphcolor(n, m, c);
}
5. 性能分析
时间复杂度: 现分析只求出一种着色方案时的时间复杂度,假如图的顶点数为 n,代码中:判断是否颜色冲突的函数的时间复杂度为 O(n);着色函数中对 n 个顶点进行遍历,因此时间代价为 O(n);因此整个程序需要求出一种着色方案时的时间代价为 O(n2);
空间复杂度: 用一个整型二维数组存储了无向图的邻接矩阵,因此空间代价为 O(n2);
参考资料
[1] 图着色问题,https://baike.baidu.com/item/图着色问题/8928655, 2022 年 4 月