【实验目的】
应用回溯法求解图的着色问题
【实验要求】
设下图G=(V,E)是一连通无向图,有3种颜色,用这些颜色为G的各顶点着色,每个顶点着一种颜色,且相邻顶点颜色不同。试用回溯法设计一个算法,找出所有可能满足上述条件的着色法,如果这个图不能用3种颜色着色满足相邻顶点颜色互异的要求就给出否定的回答。
【算法思想及处理过程】
回溯法是一种通过递归尝试所有可能的解决方案以找到满足问题条件的所有解的算法。
回溯法的基本思想可以总结如下:
选择候选:尝试在当前位置选择一个候选解。
合法性检查:检查当前选择是否满足问题的约束条件。
递归前进:如果选择合法,继续尝试下一个位置的候选解。
回溯:如果在某一步无法继续或所有候选均不合法,则撤销之前的选择(即回溯),尝试其他候选解。
具体步骤如下:
初始化:定义一个表示当前状态的数据结构,初始化为初始状态。
递归函数:设计一个递归函数,尝试在当前状态的基础上构造解。
边界条件:定义递归终止条件(例如已经构造出一个完整解)。
候选选择和合法性检查:在递归函数中,尝试所有可能的候选解,并检查其合法性。
回溯:如果当前选择不合法或递归未找到解,则撤销当前选择,继续尝试其他候选解。
在本题中:
输入和初始化:
输入图的顶点数量 num_vertices。
输入图的邻接矩阵 graph。
初始化颜色数组 color_assignment,初始值为 -1,表示未着色。
递归函数 colorGraphUtil:
递归地尝试为每个顶点着色。
如果所有顶点都已着色,打印当前着色方案,并增加解的计数。
合法性检查 isSafe:
检查当前顶点是否可以被指定颜色着色,即检查与当前顶点相邻的顶点是否有相同颜色。
回溯处理:
对于每个顶点,尝试三种颜色。
如果某种颜色可以合法地着色当前顶点,则递归处理下一个顶点。
如果递归成功(找到一个解),则继续。
否则,撤销当前顶点的着色(回溯),尝试其他颜色。
输出结果:
在递归结束后,打印所有找到的着色方案和总数。
在colorGraphUtil函数中
首先判断当前递归是否已完成所有顶点的着色。如果所有顶点都已着色,打印有效的着色方案并增加方案计数,然后结束递归。如果不是,则继续尝试每种颜色。对每个颜色进行尝试。如果当前颜色合法,将颜色赋给当前顶点,并递归处理下一个顶点。递归返回后,撤销当前顶点的颜色赋值(回溯),尝试下一种颜色。如果所有颜色均不合法,撤销当前选择并返回上一级递归。
在isSafe函数中:
使用 for 循环遍历图中所有顶点,范围是 0 到 num_vertices - 1。
对于每个顶点 i,首先检查 graph[vertex][i],它表示顶点 vertex 和顶点 i 之间是否存在边(即是否相邻)。
如果 graph[vertex][i] 为 1(或 true),表示顶点 vertex 和顶点 i 相邻。
接着检查 color_assignment[i] == c,即顶点 i 是否已经被着色为颜色 c。
如果相邻顶点 i 已经被着色为颜色 c,则返回 false,表示当前顶点 vertex 不能被着色为颜色 c,因为这会导致相邻顶点颜色相同。
如果循环结束后没有发现任何相邻顶点与颜色 c 冲突,返回 true,表示当前顶点 vertex 可以安全地着色为颜色 c。
【程序代码】
#include <stdio.h>
#include <stdbool.h>
#define MAX_VERTICES 100
int graph[MAX_VERTICES][MAX_VERTICES]; // 邻接矩阵表示图
int num_vertices; // 图中顶点的数量
int num_colors; // 可用的颜色数量
int color_assignment[MAX_VERTICES]; // 存储顶点的颜色编号,-1表示未着色
int solution_count = 0; // 记录有效的着色方案数量
// 检查给定顶点是否可以着色为指定颜色
bool isSafe(int vertex, int c) {
for (int i = 0; i < num_vertices; i++) {
if (graph[vertex][i] && color_assignment[i] == c) {
return false; // 与相邻顶点颜色相同,不安全
}
}
return true;
}
// 回溯函数,尝试为顶点进行着色
void colorGraphUtil(int vertex) {
if (vertex == num_vertices) {
// 所有顶点都已着色完成,打印结果
printf("找到一个有效的着色方案:\n");
for (int i = 0; i < num_vertices; i++) {
printf("顶点 %d: %d\n", i, color_assignment[i] + 1);
}
printf("\n");
solution_count++; // 记录找到一个有效方案
return;
}
// 尝试为当前顶点着色
for (int c = 0; c < num_colors; c++) { // 遍历每种颜色
if (isSafe(vertex, c)) {
color_assignment[vertex] = c; // 将颜色赋给顶点
colorGraphUtil(vertex + 1); // 递归处理下一个顶点
color_assignment[vertex] = -1; // 回溯,尝试下一种颜色
}
}
}
// 对图进行顶点着色
void colorGraph() {
// 从第一个顶点开始进行着色
colorGraphUtil(0);
// 输出总共找到的有效着色方案数量
printf("总共找到 %d 种有效的着色方案。\n", solution_count);
}
int main() {
// 读取图的顶点数量和邻接矩阵
printf("请输入图中顶点的数量:");
scanf("%d", &num_vertices);
printf("请输入可用的颜色数量:");
scanf("%d", &num_colors);
printf("请输入图的邻接矩阵(%d * %d):\n", num_vertices, num_vertices);
for (int i = 0; i < num_vertices; i++) {
for (int j = 0; j < num_vertices; j++) {
scanf("%d", &graph[i][j]);
}
}
// 初始化颜色赋值数组
for (int i = 0; i < num_vertices; i++) {
color_assignment[i] = -1; // 初始未着色
}
// 调用顶点着色函数
colorGraph();
return 0;
}
【运行结果】
【算法分析】
isSafe函数的时间复杂度为O(V),其中是顶点数量 um_vertices。这是因为它需要检查与当前顶点相邻的所有顶点。
colorGraphUtil 函数是递归调用的核心部分,每个顶点尝试 num_colors种颜色,每次尝试都调用 isSafe 检查当前颜色是否可行。
在最坏情况下,递归函数 colorGraphUtil的时间复杂度可以表示为T(V)=num_colors^V×V
每个顶点尝试num_colors种颜色:num_colors^V。
每次尝试颜色时,调用 isSafe 函数需要 (O(V)) 的时间。
所以,最坏情况下,总的时间复杂度为 (O(num_colors^V times V)),其中 (V) 是顶点的数量,num_colors是颜色的数量。