1.问题描述
1.1设计内容
已知中国地图,对各省进行着色,要求相邻省所使用的颜色不同,并保证使用的颜色总数最少。
1.2设计要求
(1)设计该问题的核心算法;
(2)设计可视化的界面,界面中能显示和区分中国地图中各省、市、自治区;
(3)程序能正确对地图着色。
2.方法及基本思路
本题抽象出来其实就是着色问题:已知一个图,要求给图上每个点上色,并保证该点的颜色与它的邻接点的颜色都不相同。
回溯法的基本思想是:在解空间中,按深度优先策略,从根结点出发搜索,搜索至任一节点时,先判断该节点是否包含问题的解。如果不包含,则跳过以该节点为根的子节点,逐层向其父节点回溯。否则,进入该子树,继续按照深度优先策略搜索。
假设地图邻接关系如右所示,那么它的状态解空间树如左图所示:
3.算法描述
算法名称:回溯法
输入:34个省份
输出:每个省份的颜色
省份着色(Graph) :
1. 初始化变量:
着色颜色数 colorCount = 0
最小着色颜色数 minColorCount = ∞
2. 对于图中的每个省份节点 v,设置其颜色 color[v] = 0
3. 对于图中的每个省份节点 v,将其未使用的颜色 mark[v][c] = true
4. 递归着色(0) // 从第一个省份节点开始着色
5. 输出结果最小着色颜色数 minColorCount
递归着色(v) :
1. 如果当前省份节点 v 是最后一个节点:
更新最小着色颜色数 minColorCount = colorCount
返回
2. 对于每一种颜色 c 从 1 到 maxColors:
If:省份节点 v 可以使用颜色 c:
设置 color[v] = c
对于省份节点 v 的每一个相邻节点 u,更新 mark[u][c] = false
颜色数 colorCount++
递归着色(v + 1)
颜色数 colorCount --
对于省份节点 v 的每一个相邻节点 u,更新 mark[u][c] = true
If: colorCount > minColorCount:
说明当前方案无效,回溯到上一个节点
4.时间空间复杂度分析
基本运算:比较
递归公式:T(N, k) = k * T(N-1, k-1)
时间复杂度:O(k^N)
在最坏情况下,算法需要遍历所有可能的颜色方案,所以时间复杂度为 O(k^N),其中 k 是可用的颜色数,N 是省份节点的数量。
空间复杂度:O(N)
递归调用栈的深度最大为省份节点的数量 N,所以空间复杂度为 O(N)。
5.算法实例
输入数据:输入34个省份以及各省邻接关系
输出数据:如下图,由于C语言不支持可视化,改为用省份颜色表示着色,一共得到四种颜色,且相邻省份颜色不同。
6.源码
参考链接
这位博主的代码可在Dev C++下运行;以下是我稍作修改,可以在VS2022中运行的代码,并为此添加了注释
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include "string.h"
#include<time.h>
// 定义一个结构体ArcNode,表示省份之间的连接关系
typedef struct ArcNode {
int apn; // 相邻省的序号,即连接的省份
struct ArcNode* nextarc; // 指向下一个ArcNode的指针,表示下一个连接的省份
} ArcNode;
// 定义一个结构体VNode,表示一个省份的信息
typedef struct VNode {
char name[6];
int color;
int number;
ArcNode* firstarc; // 指向该省份的第一个连接关系的指针
} VNode;
// 定义一个结构体DNode,表示双链表的一个节点
typedef struct DLinkNode {
struct VNode* data; // 指向VNode的指针,作为双链表节点的数据
struct DLinkNode* prior; // 指向前一个DLinkNode的指针
struct DLinkNode* next; // 指向下一个DLinkNode的指针
} DLinkNode;
// 定义一个数组,存储ArcNode的指针,最多可以存储145个省份的连接关系
ArcNode* node[145], * p;
// 定义一个数组,存储VNode的实例,最多可以存储35个省份的信息
VNode province[35];
// 定义一个二维数组,存储每个省份的颜色,最多可以存储35个省份的5种颜色(红、绿、蓝、黄、紫)
int color[35][5];
void Intput(VNode* province, ArcNode* node[]); // 输入中国地图信息的函数,参数是省份和连接关系的数组
void Initialization(VNode* province); // 初始化各省颜色及颜色数组
void Painting(VNode* province, ArcNode* p, int i); // 对省份进行上色的函数,参数是省份和指向某个连接关系的指针和省份的序号
void Setcolor(int i); // 设置输出颜色的函数,参数是颜色编号
void Output(VNode* province, ArcNode* p); 输出信息的函数,参数是省份和指向某个连接关系的指针
void Find(DLinkNode*& L, VNode* province, ArcNode* p); // // 求最少着色颜色的函数,参数是双链表的头节点、省份数组和省份的数量
void CreateListR(DLinkNode*& L, VNode* province, int n); // // 使用尾插法创建双链表的函数,参数是双链表的头节点、省份数组和省份的数量
int main() {
DLinkNode* DuLinkLis; // 双链表的头节点指针
Intput(province, node); // 输入中国地图信息
CreateListR(DuLinkLis, province, 34); // 使用尾插法创建双链表
Find(DuLinkLis, province, p); // 求最少着色颜色数
return 0;
}
// 输入中国地图信息的函数实现
void Intput(VNode* province, ArcNode* node[]) {
for (int i = 1; i < 145; i++) // 对145个连接关系进行初始化
node[i] = (ArcNode*)malloc(sizeof(ArcNode)); // 为每个连接关系分配内存空间并初始化为0
for (int i = 1; i < 145; i++) // 对每个连接关系设置相邻省的序号和指向下一个连接关系的指针
node[i]->nextarc = node[i + 1]; // node[i]->nextarc指向node[i+1],即每个连接关系的下一个连接关系是node[i+1]
strcpy_s(province[1].name, 20,"新疆");
province[1].firstarc = node[1];
node[1]->apn = 2;
node[2]->apn = 3;
node[3]->apn = 4;
node[3]->nextarc = NULL;
strcpy_s(province[2].name, 20,"西藏");
province[2].firstarc = node[4];
node[4]->apn = 1;
node[5]->apn = 3;
node[6]->apn = 7;
node[7]->apn = 8;
node[7]->nextarc = NULL;
strcpy_s(province[3].name, 20,"青海");
province[3].firstarc = node[8];
node[8]->apn = 1;
node[9]->apn = 2;
node[10]->apn = 4;
node[11]->apn = 7;
node[11]->nextarc = NULL;
strcpy_s(province[4].name,20, "甘肃");
province[4].firstarc = node[12];
node[12]->apn = 1;
node[13]->apn = 3;
node[14]->apn = 5;
node[15]->apn = 6;
node[16]->apn = 7;
node[17]->apn = 9;
node[17]->nextarc = NULL;
strcpy_s(province[5].name,20, "内蒙古");
province[5].firstarc = node[18];
node[18]->apn = 4;
node[19]->apn = 6;
node[20]->apn = 9;
node[21]->apn = 13;
node[22]->apn = 21;
node[23]->apn = 32;
node[24]->apn = 33;
node[25]->apn = 34;
node[25]->nextarc = NULL;
strcpy_s(province[6].name, 20,"宁夏");
province[6].firstarc = node[26];
node[26]->apn = 4;
node[27]->apn = 5;
node[28]->apn = 9;
node[28]->nextarc = NULL;
strcpy_s(province[7].name,20, "四川");
province[7].firstarc = node[29];
node[29]->apn = 2;
node[30]->apn = 3;
node[31]->apn = 4;
node[32]->apn = 8;
node[33]->apn = 9;
node[34]->apn = 10;
node[35]->apn = 11;
node[35]->nextarc = NULL;
strcpy_s(province[8].name, 20,"云南");
province[8].firstarc = node[36];
node[36]->apn = 2;
node[37]->apn = 7;
node[38]->apn = 11;
node[39]->apn = 12;
node[39]->nextarc = NULL;
strcpy_s(province[9].name,20, "陕西");
province[9].firstarc = node[40];
node[40]->apn = 4;
node[41]->apn = 5;
node[42]->apn = 6;
node[43]->apn = 7;
node[44]->apn = 10;
node[45]->apn = 13;
node[46]->apn = 14;
node[47]->apn = 15;
node[47]->nextarc = NULL;
strcpy_s(province[10].name, 20,"重庆");
province[10].firstarc = node[48];
node[48]->apn = 7;
node[49]->apn = 9;
node[50]->apn = 11;
node[51]->apn = 15;
node[52]->apn = 16;
node[52]->nextarc = NULL;
strcpy_s(province[11].name,20, "贵州");
province[11].firstarc = node[53];
node[53]->apn = 7;
node[54]->apn = 8;
node[55]->apn = 10;
node[56]->apn = 12;
node[57]->apn = 16;
node[57]->nextarc = NULL;
strcpy_s(province[12].name, 20,"广西");
province[12].firstarc = node[58];
node[58]->apn = 8;
node[59]->apn = 11;
node[60]->apn = 16;
node[61]->apn = 17;
node[61]->nextarc = NULL;
strcpy_s(province[13].name, 20,"山西");
province[13].firstarc = node[62];
node[62]->apn = 5;
node[63]->apn = 9;
node[64]->apn = 14;
node[65]->apn = 21;
node[65]->nextarc = NULL;
strcpy_s(province[14].name,20, "河南");
province[14].firstarc = node[66];
node[66]->apn = 9;
node[67]->apn = 13;
node[68]->apn = 15;
node[69]->apn = 21;
node[70]->apn = 24;
node[71]->apn = 25;
node[71]->nextarc = NULL;
strcpy_s(province[15].name, 20,"湖北");
province[15].firstarc = node[73];
node[73]->apn = 9;
node[74]->apn = 10;
node[75]->apn = 14;
node[76]->apn = 16;
node[77]->apn = 25;
node[78]->apn = 26;
node[78]->nextarc = NULL;
strcpy_s(province[16].name,20,"湖南");
province[16].firstarc = node[79];
node[79]->apn = 10;
node[80]->apn = 11;
node[81]->apn = 12;
node[82]->apn = 15;
node[83]->apn = 17;
node[84]->apn = 26;
node[84]->nextarc = NULL;
strcpy_s(province[17].name,20, "广东");
province[17].firstarc = node[85];
node[85]->apn = 12;
node[86]->apn = 16;
node[87]->apn = 19;
node[88]->apn = 20;
node[89]->apn = 26;
node[90]->apn = 30;
node[90]->nextarc = NULL;
strcpy_s(province[18].name, 20,"海南");
province[18].firstarc = NULL;
strcpy_s(province[19].name,20, "澳门");
province[19].firstarc = node[91];
node[91]->apn = 17;
node[91]->nextarc = NULL;
strcpy_s(province[20].name,20, "香港");
province[20].firstarc = node[92];
node[92]->apn = 17;
node[92]->nextarc = NULL;
strcpy_s(province[21].name,20, "河北");
province[21].firstarc = node[93];
node[93]->apn = 5;
node[94]->apn = 13;
node[95]->apn = 14;
node[96]->apn = 22;
node[97]->apn = 23;
node[98]->apn = 24;
node[99]->apn = 34;
node[99]->nextarc = NULL;
strcpy_s(province[22].name, 20,"北京");
province[22].firstarc = node[100];
node[100]->apn = 21;
node[101]->apn = 23;
node[101]->nextarc = NULL;
strcpy_s(province[23].name,20, "天津");
province[23].firstarc = node[102];
node[102]->apn = 21;
node[103]->apn = 22;
node[103]->nextarc = NULL;
strcpy_s(province[24].name,20, "山东");
province[24].firstarc = node[104];
node[104]->apn = 14;
node[105]->apn = 21;
node[106]->apn = 25;
node[107]->apn = 27;
node[107]->nextarc = NULL;
strcpy_s(province[25].name, 20,"安徽");
province[25].firstarc = node[108];
node[108]->apn = 14;
node[109]->apn = 15;
node[110]->apn = 24;
node[111]->apn = 26;
node[112]->apn = 27;
node[113]->apn = 29;
node[113]->nextarc = NULL;
strcpy_s(province[26].name, 20,"江西");
province[26].firstarc = node[115];
node[115]->apn = 15;
node[116]->apn = 16;
node[117]->apn = 17;
node[118]->apn = 25;
node[119]->apn = 29;
node[120]->apn = 30;
node[120]->nextarc = NULL;
strcpy_s(province[27].name,20, "江苏");
province[27].firstarc = node[122];
node[122]->apn = 24;
node[123]->apn = 25;
node[124]->apn = 28;
node[125]->apn = 29;
node[125]->nextarc = NULL;
strcpy_s(province[28].name,20, "上海");
province[28].firstarc = node[126];
node[126]->apn = 27;
node[127]->apn = 29;
node[127]->nextarc = NULL;
strcpy_s(province[29].name,20, "浙江");
province[29].firstarc = node[128];
node[128]->apn = 17;
node[129]->apn = 25;
node[130]->apn = 26;
node[131]->apn = 27;
node[132]->apn = 28;
node[132]->nextarc = NULL;
strcpy_s(province[30].name, 20,"福建");
province[30].firstarc = node[133];
node[133]->apn = 17;
node[134]->apn = 26;
node[135]->apn = 29;
node[135]->nextarc = NULL;
strcpy_s(province[31].name,20, "台湾");
province[31].firstarc = NULL;
strcpy_s(province[32].name, 20,"黑龙江");
province[32].firstarc = node[136];
node[136]->apn = 5;
node[137]->apn = 33;
node[137]->nextarc = NULL;
strcpy_s(province[33].name,20, "吉林");
province[33].firstarc = node[138];
node[138]->apn = 5;
node[139]->apn = 32;
node[140]->apn = 34;
node[140]->nextarc = NULL;
strcpy_s(province[34].name, 20,"辽宁");
province[34].firstarc = node[141];
node[141]->apn = 5;
node[142]->apn = 21;
node[143]->apn = 33;
node[143]->nextarc = NULL;
}
// 初始化函数,对省份数组进行初始化
void Initialization(VNode* province) {
// 循环遍历34个省份
for (int i = 1; i <= 34; i++)
{
// 将省份的颜色初始化为0,表示未被使用
province[i].color = 0;
// 设置省份的序号为i
province[i].number = i;
// 对每个省份,为其颜色属性赋初值,从1到5
for (int j = 0; j < 5; j++)
color[i][j] = j + 1;
}
}
// 创建双链表的函数
void CreateListR(DLinkNode*& L, VNode* province, int n) {
// 定义创建双链表需要的指针s和r,L为双链表的头结点
DLinkNode* s, * r;
// 为头结点分配内存空间
L = (DLinkNode*)malloc(sizeof(DLinkNode)); //创建头结点
// 设置头结点的prior和next指针为空
L->prior = L->next = NULL;
// 设置头结点的数据为省份数组的第一个省份的信息
L->data = &province[1];
// r指针始终指向双链表的终端结点,开始时指向头结点
r = L; //r始终指向终端结点,开始时指向头结点
// 循环创建双链表的结点,从第二个省份开始,直到第n个省份
for (int i = 2; i <= n; i++)
{
// 为新结点分配内存空间
s = (DLinkNode*)malloc(sizeof(DLinkNode));//创建新结点
// 设置新结点的数据为当前省份的信息
s->data = &province[i];
// 将新结点插入到r指针指向的结点之后
r->next = s; s->prior = r; //将结点s插入结点r之后
// r指针指向新插入的结点
r = s;
}
// 将双链表的尾结点的next指针指向头结点,形成环状结构
r->next = L; //尾结点next域置为头节点
// 将头结点的prior指针指向尾结点,形成环状结构
L->prior = r;
}
void Painting(VNode* province, ArcNode* p, int i) {
// 将p指向province数组中第i个省份的第一个邻接省份
p = province[i].firstarc;
// 遍历该省份的所有邻接省份
while (p != NULL)
{
// 根据邻接省份的颜色值进行判断和操作
switch (province[p->apn].color)
{
// 如果邻接省份的颜色值为1,则将当前省份的第0列颜色值设为0 (第一种颜色为0已经使用,不再可用)
case 1:color[i][0] = 0; break;
// 如果邻接省份的颜色值为2,则将当前省份的第1列颜色值设为0
case 2:color[i][1] = 0; break;
// 如果邻接省份的颜色值为3,则将当前省份的第2列颜色值设为0
case 3:color[i][2] = 0; break;
// 如果邻接省份的颜色值为4,则将当前省份的第3列颜色值设为0
case 4:color[i][3] = 0; break;
// 如果邻接省份的颜色值为5,则将当前省份的第4列颜色值设为0
case 5:color[i][4] = 0; break;
// 如果邻接省份的颜色值不在1-5之间,不进行任何操作
default:break;
}
// 移动到下一个邻接省份
p = p->nextarc;
}
// 在当前省份可能被分配的5种颜色中,找出第一个非零颜色值并分配给当前省份
for (int j = 0; j < 5; j++)
{
// 如果第j列的颜色值不为0
if (color[i][j] != 0)
{
// 将第j列的颜色值赋给当前省份,并重置第j列的颜色值为0
province[i].color = color[i][j];
color[i][j] = 0;
// 跳出循环,因为已经找到了一个有效的颜色值
break;
}
}
}
void Setcolor(int i) // 定义一个函数Setcolor,接收一个整数参数i
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);// 获取标准输出句柄是一个Windows API函数,用于获取标准输出的句柄。通过这个句柄,你可以对标准输出进行各种操作,比如改变文本颜色、设置文本位置等。
switch (i)
{
case 1:SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_GREEN); break;//绿色
case 2:SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN); break;//黄色
case 3:SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED); break;//红色
case 4:SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_BLUE); break;//蓝色
case 5:SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_BLUE); break;//紫色
case 6:SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_BLUE); break;//浅蓝
case 7:SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); break;//白色
case 8:SetConsoleTextAttribute(handle, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); break;//默认低亮白色
}
}
void Output(VNode* province, ArcNode* p) { // 定义一个函数Output,接收两个指针参数:省份节点和弧节点
printf("\n中国34个省份颜色如下:\n"); // 打印提示信息,表示接下来将展示34个省份的颜色
for (int i = 1; i <= 34; i++) // 遍历每个省份
{
Setcolor(province[i].color); // 设置当前省份的颜色
printf("%d %s\t", i, province[i].name); // 打印省份的序号和名称
if (i % 12 == 0) // 如果省份序号是12的倍数
printf("\n"); // 则换行
}
Setcolor(8); // 将颜色设置回默认颜色(8)
printf("\n\n"); // 打印两个换行符
printf("\n中国34个省份颜色及其各邻接省份颜色如下请查阅:\n"); // 打印提示信息,表示接下来将展示每个省份及其邻接省份的颜色
for (int j = 1; j <= 7; j++) // 遍历每个颜色(这里假设有7种颜色)
{
for (int i = 1; i <= 34; i++) // 遍历每个省份
if (province[i].color == j) // 如果该省份的颜色与当前颜色j相同
{
Setcolor(province[i].color); // 设置当前省份的颜色
printf("%d\t%s\t", i, province[i].name); // 打印省份的序号、名称和颜色
Setcolor(8); // 将颜色设置回默认颜色(8)
printf("邻接省份有:"); // 打印提示信息,表示接下来将展示该省份的邻接省份
p = province[i].firstarc; // 从当前省份开始获取其邻接省份的弧节点
while (p != NULL) // 遍历所有邻接省份的弧节点
{
Setcolor(province[p->apn].color); // 设置邻接省份的颜色
printf("%s ", province[p->apn].name); // 打印邻接省份的名称和颜色
p = p->nextarc; // 移动到下一个邻接省份的弧节点
}
printf("\n"); // 换行
}
printf("\n"); // 在遍历完一个颜色后换行,使得每个颜色的省份都在新的一行上显示
}
Setcolor(8); // 将颜色设置回默认颜色(8)
}
void Find(DLinkNode*& L, VNode* province, ArcNode* p) { // 函数声明,接收三个指针参数:双向链表L的引用、省份数组province和弧节点数组ArcNode。
DLinkNode* r = L, * s; // 定义两个双向链表节点指针r和s,并初始化r为L的起始节点。
int Maxcolor = 2, Mincolor = 5; // 定义两个整数变量Maxcolor和Mincolor,分别初始化为2和5。
do // 开始一个do-while循环。
{
s = r; // 将s指向r。
Maxcolor = 2; // 将Maxcolor重置为2。
Initialization(province); // 调用Initialization函数来初始化省份数组province。
while (s != s->prior) // 当s不等于其前驱节点时,执行循环。
{
Painting(province, p, s->data->number); // 调用Painting函数,传入省份数组、弧节点数组和当前节点的编号,对地图进行着色。
if (s->data->color > Maxcolor) // 如果当前节点的颜色大于Maxcolor。
if (s->data->number != r->data->number) // 如果当前节点的编号不等于r节点的编号。
{
s->data->color = 0; // 将当前节点的颜色设置为0。
for (int j = 0; j < 5; j++) // 循环初始化颜色数组。
color[s->data->number][j] = j + 1;
s = s->prior; // 将s指向其前驱节点。
goto a; // 跳转到标签a。
}
else // 如果当前节点的编号等于r节点的编号。
{
printf("从第%d个省%s开始着色用%d种颜色着色失败\n", r->data->number, r->data->name, Maxcolor); // 打印失败信息。
Maxcolor++; // 将Maxcolor增加1。
Initialization(province); // 重新初始化省份数组。
s = r; // 将s指向r。
goto a; // 跳转到标签a。
}
if (s->data->number == r->prior->data->number) // 如果当前节点的编号等于r的前驱节点的编号。
{
printf("从第%d个省%s开始着色用%d种颜色着色成功", r->data->number, r->data->name, Maxcolor); // 打印成功信息。
if (Maxcolor < Mincolor)Mincolor = Maxcolor; // 如果Maxcolor小于Mincolor,则更新Mincolor为Maxcolor的值。
Output(province, p); // 调用Output函数,输出省份和邻接省份的信息。
goto b; // 跳转到标签b。
}
s = s->next; // 将s指向其下一个节点。
a:; // 标签a,表示跳转点。
}
b: r = r->next; // 标签b,将r指向其下一个节点。继续下一次循环。
} while (r != L); // 当r不等于L时,继续循环。循环结束条件是r等于L。
Setcolor(4); // 设置文本颜色为4。
printf("\n对中国地图最少着色颜色数为%d种!!\n", Mincolor); // 打印最少需要的颜色数。
Setcolor(8); // 设置文本颜色为8,恢复默认颜色。
return;
}