3着色问题
问题描述
考虑3着色问题:
给出一个无向图G=(V,E),需要用三种颜色之一为V中的每个顶点着色,三种颜色分别为1,2和3,使得没有两个邻接顶点有相同的颜色。把这样的着色成为合法的;否则,如果两个邻接的顶点有同一种颜色就是非法的。
一种着色可以用n元组(c1,c2,…,cn)来表示,使ci∈{1,2,3},1≤i≤n。例如,(1,2,2,3,1)表示一个有5个顶点的图的着色。
一个n个顶点的图共有3^n种可能的着色(包括合法的和非法的),所有可能的着色的集合可以用一棵完全的三叉树来表示,称为搜索树。在这棵树中,从根到叶节点的每一条路径代表一种着色指派。
👇对于一个有三个顶点的图的所有可能3着色的搜索树
如果没有两个邻接的着色顶点有同样的颜色,图的一个不完全着色称为部分解。回溯法通过每次一个节点地生成基底树来工作。如果从根到当前节点的路径对应一个合法着色,过程就终止(除非想要找到不止一种着色方式)。
- 如果这条路径的长度小于n,并且相应的着色是部分的,那么就生成现节点的一个子节点,并将它标记为现节点。
- 如果对应的路径不是部分的,那么现节点标识为死节点并生成对应于另一种颜色的新节点。
- 如果三种颜色都已经试过并且没有成功,搜索就回溯到父节点,它的颜色被该改变,依次类推。
例子
考虑下图a👇,考虑用颜色{1,2,3}对其顶点着色,图b👇是在搜索一个合法的着色的过程中生成的搜索数的一部分。
- 首先,在生成第三个节点之后,发现着色(1,1)不是部分的,因此该节点被标记为死节点,在图中用×表示。
- 然后b被指派为颜色2,着色(1,2)是部分的,因此,生成一个对应于顶点c的一个新的子节点,赋予初始颜色值1
- 重复上述过程,忽略死节点和扩展那些相应的部分着色,最终得到合法的着色(1,2,2,1,3)。
注意:
- 节点是用深度优先搜索的方法生成的
- 不需要存储整棵搜索树,只需要存储根到当前活动节点的路径
递归算法
伪代码
算法 3-COLORREC
输入:无向图G=(V,E)。
输出:G的顶点的3着色c[1...n],其中每个c[j]为1,2或3。
1. for k ← 1 to n
2. c[k] ← 0
3. end for
4. flag ← false
5. graphcolor(1)
6. if flag then output c
7. else output "no solution"
过程 graphcolor(k)
1. for color = 1 to 3
2. c[k] ← color
3. if c为合法着色 then set flag ← true and exit
4. else if c是部分的 then graphcolor(k+1)
5. end for
C++代码实现
#include <iostream>
using namespace std;
//无向图G为上图a
int G[5][5] = { {0,1,1,0,0},
{1,0,0,1,1},
{1,0,0,1,1},
{0,1,1,0,1},
{0,1,1,1,0} };
bool iscolor(int k, int *c) {//判断是否为合法颜色
for (int i = 0;i < 5 && i != k;i++) {
if (G[k][i] == 1 && c[k] == c[i])
return false;
}
return true;
}
void graphcolor(int k, int *c) {
for (int i = 1;i <= 3;i++) {
c[k] = i;//着色1,2,3
if (iscolor(k, c)) {
if (k == 4)//合法解
output(c, 5);
else//部分合法解
graphcolor(k + 1, c);
}
}
}
void output(int *c, int n) {//输出所有合法解
for (int i = 0;i < n;i++)
cout << c[i] << " ";
cout << endl;
}
int main() {
//G的顶点的3着色
int color[5];
for (int k = 0;k < 5;k++)
color[k] = 0;
graphcolor(0, color);
return 0;
}
输出结果
迭代算法
伪代码
算法 3-COLORITER
输入:无向图G=(V,E)。
输出:G的顶点的3着色c[1...n],其中每个c[j]为1,2或3。
1. for k ← 1 to n
2. c[k] ← 0
3. end for
4. flag ← flag
5. k ← 1
6. while k≥1
7. while c[k]≤2
8. c[k]=c[k]+1
9. if c为合法着色 then set flag ← true 且从两个while循环退出
10. else if c为部分解 then k ← k+1
11. end while
12. c[k] ← 0
13. k ← k-1
14. end while
15. if flag then output c
16. else output "no solution"
C++代码实现
#include <iostream>
using namespace std;
//无向图G为上图a
int G[5][5] = { {0,1,1,0,0},
{1,0,0,1,1},
{1,0,0,1,1},
{0,1,1,0,1},
{0,1,1,1,0} };
bool iscolor(int k, int *c) {//判断是否为合法颜色
for (int i = 0;i < 5 && i != k;i++) {
if (G[k][i] == 1 && c[k] == c[i])
return false;
}
return true;
}
void output(int *c, int n) {//输出合法解
for (int i = 0;i < n;i++)
cout << c[i] << " ";
cout << endl;
}
int main() {
//G的顶点的3着色
int color[5];
for (int k = 0;k < 5;k++)
color[k] = 0;
bool flag = false;
int k = 0;
while (k >= 0) {
while (color[k] <= 2) {
color[k]++;
if (iscolor(k, color)) {
if (k == 4) {//合法解
flag = true;
break;
}
else//部分合法解
k++;
}
}
if (flag)
break;
//回溯
color[k] = 0;
k = k - 1;
}
if (flag)
output(color, k+1);
else
cout << "no solution!" << endl;
return 0;
}
输出结果
时间复杂度
以上递归算法和迭代算法中,最坏情况下生成了O(3n)个节点。对于每个生成的节点,如果当前的着色是合法的、部分的,或者两者都不是,这需要O(n)的工作来检查。因此,在最坏情况下,全部的运行时间为O(3n)。