一、目的
-
正确应用回溯算法求解图着色问题。
-
对边界值测试结果。
-
正确分析算法时间复杂度。
二、实验内容与设计思想
-
设计思路
(1) 输入图的点数,边数,色数。输入图的每条边。生成图。
(2) 通过回溯算法求可行的图方案,求得后输出。 -
主要数据结构
Graph typedef struct Graph {
vector<set<int>>G;
int color[MAXN] = { 0 };
int e, v, ColorNum;}
G是图中每个点邻接点的储存容器
color储存图中每个点的着色方案
e是图顶点个数,v是图边个数
ColorNum是图的色数
- 主要代码结构
三、实验使用环境
软件:Visual Studio 2022/1/4
平台:win10
四、实验步骤和调试过程
4.1 输入图的基本属性
流程图
输入图的点数,边数和色数。 输入图的边生成模式: 如果模式=0,则手动输入。 如果模式=1,则自动输入。 然后输出图结构。
数据测试与结果:
图的点数 | 图的边数 | 图的色数 | 图结构 |
6 | 18 | 3 | 第 1条边:(5, 4) 第 2条边:(4, 2) 第 3条边:(3, 5) 第 4条边:(3, 1) 第 5条边:(4, 3) 第 6条边:(2, 2) 第 7条边:(3, 1) 第 8条边:(3, 0) 第 9条边:(0, 5) 第10条边:(1, 3) 第11条边:(2, 0) 第12条边:(3, 3) 第13条边:(5, 5) 第14条边:(0, 5) 第15条边:(2, 1) 第16条边:(4, 1) 第17条边:(5, 4) 第18条边:(4, 1) 第19条边:(1, 0) 第20条边:(1, 4) 第21条边:(1, 3) 第22条边:(4, 2) 第23条边:(0, 3) |
6 | 21 | 5 | 第 1条边:(5, 4) 第 2条边:(4, 2) 第 3条边:(3, 5) 第 4条边:(3, 1) 第 5条边:(4, 3) 第 6条边:(2, 2) 第 7条边:(3, 1) 第 8条边:(3, 0) 第 9条边:(0, 5) 第10条边:(1, 3) 第11条边:(2, 0) 第12条边:(3, 3) 第13条边:(5, 5) 第14条边:(0, 5) 第15条边:(2, 1) 第16条边:(4, 1) 第17条边:(5, 4) 第18条边:(4, 1) 第19条边:(1, 0) 第20条边:(1, 4) 第21条边:(1, 3) |
7 | 30 | 6 | 第 1条边:(6, 6) 第 2条边:(2, 5) 第 3条边:(5, 6) 第 4条边:(0, 4) 第 5条边:(3, 4) 第 6条边:(2, 6) 第 7条边:(3, 2) 第 8条边:(6, 4) 第 9条边:(2, 5) 第10条边:(6, 4) 第11条边:(2, 1) 第12条边:(1, 4) 第13条边:(2, 4) 第14条边:(4, 1) 第15条边:(3, 3) 第16条边:(4, 2) 第17条边:(3, 0) 第18条边:(3, 6) 第19条边:(5, 5) 第20条边:(1, 6) 第21条边:(2, 5) 第22条边:(3, 1) 第23条边:(0, 2) 第24条边:(4, 1) 第25条边:(2, 0) 第26条边:(5, 1) 第27条边:(1, 3) 第28条边:(3, 5) 第29条边:(2, 1) 第30条边:(4, 1) |
7 | 26 | 4 | 第 1条边:(6, 6) 第 2条边:(2, 5) 第 3条边:(5, 6) 第 4条边:(0, 4) 第 5条边:(3, 4) 第 6条边:(2, 6) 第 7条边:(3, 2) 第 8条边:(6, 4) 第 9条边:(2, 5) 第10条边:(6, 4) 第11条边:(2, 1) 第12条边:(1, 4) 第13条边:(2, 4) 第14条边:(4, 1) 第15条边:(3, 3) 第16条边:(4, 2) 第17条边:(3, 0) 第18条边:(3, 6) 第19条边:(5, 5) 第20条边:(1, 6) 第21条边:(2, 5) 第22条边:(3, 1) 第23条边:(0, 2) 第24条边:(4, 1) 第25条边:(2, 0) 第26条边:(5, 1) |
8 | 36 | 5 | 第 1条边:(5, 6) 第 2条边:(6, 2) 第 3条边:(7, 3) 第 4条边:(5, 7) 第 5条边:(4, 5) 第 6条边:(6, 6) 第 7条边:(7, 1) 第 8条边:(3, 4) 第 9条边:(4, 5) 第10条边:(1, 5) 第11条边:(0, 6) 第12条边:(1, 5) 第13条边:(3, 3) 第14条边:(6, 3) 第15条边:(4, 1) 第16条边:(0, 7) 第17条边:(3, 0) 第18条边:(0, 5) 第19条边:(5, 6) 第20条边:(5, 2) 第21条边:(3, 3) 第22条边:(6, 6) 第23条边:(4, 5) 第24条边:(1, 3) 第25条边:(5, 5) 第26条边:(4, 6) 第27条边:(6, 1) 第28条边:(2, 3) 第29条边:(7, 3) 第30条边:(0, 5) 第31条边:(2, 2) 第32条边:(0, 6) 第33条边:(2, 7) 第34条边:(5, 4) 第35条边:(5, 3) 第36条边:(1, 6) |
8 | 40 | 5 | 第 1条边:(5, 6) 第 2条边:(6, 2) 第 3条边:(7, 3) 第 4条边:(5, 7) 第 5条边:(4, 5) 第 6条边:(6, 6) 第 7条边:(7, 1) 第 8条边:(3, 4) 第 9条边:(4, 5) 第10条边:(1, 5) 第11条边:(0, 6) 第12条边:(1, 5) 第13条边:(3, 3) 第14条边:(6, 3) 第15条边:(4, 1) 第16条边:(0, 7) 第17条边:(3, 0) 第18条边:(0, 5) 第19条边:(5, 6) 第20条边:(5, 2) 第21条边:(3, 3) 第22条边:(6, 6) 第23条边:(4, 5) 第24条边:(1, 3) 第25条边:(5, 5) 第26条边:(4, 6) 第27条边:(6, 1) 第28条边:(2, 3) 第29条边:(7, 3) 第30条边:(0, 5) 第31条边:(2, 2) 第32条边:(0, 6) 第33条边:(2, 7) 第34条边:(5, 4) 第35条边:(5, 3) 第36条边:(1, 6) 第37条边:(6, 7) 第38条边:(4, 5) 第39条边:(6, 5) 第40条边:(6, 6) |
…… | …… | …… | …… |
4.2 回溯算法
流程图
搜索过程、回溯算法过程:
①初始化颜色总数为点数e。
②每次从点集中选择一个顶点并从第一种颜色开始尝试对其进行着色;
③如果着色不与邻接点冲突,则继续通过相同的方式处理点集中的下一个顶点;如果着色冲突(与邻接点颜色一样),则说明该种着色方法行不通,则尝试另一个颜色,知道色数已经达顶,则退回到上一个结点,将上一个结点的着色改为当前着色的下一种颜色。
④重复上述过程,直到所有的顶点都着色为止,此时确定了一个可行解。通过Graph的display函数打印出来。
⑤得到一个可行解后进行回溯,退回到上一个着色颜色序号小于当前颜色总数的结点上,将其着色改为下一种,并进行如上所示的推理过程。
⑥当存在一个结点没有颜色可以着色时,算法停止。
剪枝过程:
遍历所有点的邻接点,如果邻接点的个数>=图的色数,则不会有可行解,省去探索过程。
测试数据与结果:
图的点数 | 图的边数 | 图的色数 | 解法 |
5 | 10 | 3 | 第 1种解法: 1 1 1 2 2 第 2种解法: 1 1 1 2 3 第 3种解法: 1 1 1 3 2 第 4种解法: 1 1 1 3 3 第 5种解法: 1 1 2 2 3 第 6种解法: 1 1 2 3 3 第 7种解法: 1 1 3 2 2 第 8种解法: 1 1 3 3 2 第 9种解法: 1 2 1 3 3 第 10种解法: 1 2 2 3 3 第 11种解法: 1 3 1 2 2 第 12种解法: 1 3 3 2 2 第 13种解法: 2 1 1 3 3 第 14种解法: 2 1 2 3 3 第 15种解法: 2 2 1 1 3 第 16种解法: 2 2 1 3 3 第 17种解法: 2 2 2 1 1 第 18种解法: 2 2 2 1 3 第 19种解法: 2 2 2 3 1 第 20种解法: 2 2 2 3 3 第 21种解法: 2 2 3 1 1 第 22种解法: 2 2 3 3 1 第 23种解法: 2 3 2 1 1 |
5 | 10 | 3 | 无可行方案! |
6 | 15 | 3 | 第 1种解法: 1 2 2 1 1 3 第 2种解法: 1 2 2 1 2 3 第 3种解法: 1 2 3 1 1 2 第 4种解法: 1 2 3 1 3 2 第 5种解法: 1 3 2 1 1 3 第 6种解法: 1 3 2 1 2 3 第 7种解法: 1 3 3 1 1 2 第 8种解法: 1 3 3 1 3 2 第 9种解法: 2 1 1 2 1 3 第 10种解法: 2 1 1 2 2 3 第 11种解法: 2 1 3 2 2 1 第 12种解法: 2 1 3 2 3 1 第 13种解法: 2 3 1 2 1 3 第 14种解法: 2 3 1 2 2 3 第 15种解法: 2 3 3 2 2 1 第 16种解法: 2 3 3 2 3 1 第 17种解法: 3 1 1 3 1 2 第 18种解法: 3 1 1 3 3 2 第 19种解法: 3 1 2 3 2 1 第 20种解法: 3 1 2 3 3 1 第 21种解法: 3 2 1 3 1 2 第 22种解法: 3 2 1 3 3 2 第 23种解法: 3 2 2 3 2 1 第 24种解法: 3 2 2 3 3 1 |
7 | 30 | 3 | 无可行方案! |
7 | 40 | 4 | 第 1种解法: 1 2 3 4 3 1 2 第 2种解法: 1 2 4 3 4 1 2 第 3种解法: 1 3 2 4 2 1 3 第 4种解法: 1 3 4 2 4 1 3 第 5种解法: 1 4 2 3 2 1 4 第 6种解法: 1 4 3 2 3 1 4 第 7种解法: 2 1 3 4 3 2 1 第 8种解法: 2 1 4 3 4 2 1 第 9种解法: 2 3 1 4 1 2 3 第 10种解法: 2 3 4 1 4 2 3 第 11种解法: 2 4 1 3 1 2 4 第 12种解法: 2 4 3 1 3 2 4 第 13种解法: 3 1 2 4 2 3 1 第 14种解法: 3 1 4 2 4 3 1 第 15种解法: 3 2 1 4 1 3 2 第 16种解法: 3 2 4 1 4 3 2 第 17种解法: 3 4 1 2 1 3 4 第 18种解法: 3 4 2 1 2 3 4 第 19种解法: 4 1 2 3 2 4 1 第 20种解法: 4 1 3 2 3 4 1 第 21种解法: 4 2 1 3 1 4 2 第 22种解法: 4 2 3 1 3 4 2 第 23种解法: 4 3 1 2 1 4 3 第 24种解法: 4 3 2 1 2 4 3 |
…… | …… | …… | …… |
4.4 时间复杂度
-
生成图:O(2v)
v------图的边数
无好坏情况之分,图结构是由点和边组成的,e是点数,但是e不用生成,是从0-e排列的,v是边数,因为是随机数生成,所以生成v条边,需要2v的时间。 -
剪枝:O(1)~O(e)
e------图的点数
遍历图的点,如果图的任意一个点的邻接点个数>=色数,则说明图无可行解。- 最好的情况:
O(1)------如果遍历第一个点的时候,就发现第一个点的邻接点个数>=色数,则结束。 - 最差的情况:
O(e)------如果遍历最后一个点的时候,才发现无可行解。或者所有点的邻接点个数都<色数,则需要时间O(e)。
- 最好的情况:
-
单次探索:O(n+k)
n------该点的邻接点个数
k------该点的可行着色数量
n:遍历邻接点,将他们的颜色都标注。
k:遍历色数,当目前颜色没有被标注,则该点染这个颜色,并进行下一个递归。所以染色和递归的次数取决于该点的可行着色数量,即该点所有邻接点中小标小于该点的数量。 -
整个探索过程:O( f ( 1 ) × f ( 2 ) × … × f ( e ) f(1)\ \times f(2) \times \ldots \times f(e) f(1) ×f(2)×…×f(e))
f ( i n d e x ) f(index) f(index):是下标为index的点的可着色数量。即m-(index的邻接点中已经染色数量)。
且已知 [ m − f ( 1 ) ] + [ m − f ( 2 ) ] + … + [ m − f ( e ) ] = v \left\lbrack m - f(1) \right\rbrack + \left\lbrack m - f(2) \right\rbrack + \ldots + \left\lbrack m - f(e) \right\rbrack = v [m−f(1)]+[m−f(2)]+…+[m−f(e)]=v,即 e ⋅ m − ∑ i = 1 e f ( i ) = v e \cdot m - \sum_{i = 1}^{e}{f(i)} = v e⋅m−∑i=1ef(i)=v。
综上:
最好情况:O(1)
最差情况:O(
f
(
1
)
×
f
(
2
)
×
…
×
f
(
e
)
f(1)\ \times f(2) \times \ldots \times f(e)
f(1) ×f(2)×…×f(e))
700次图着色时间图
4.6 空间复杂度
- 生成图:O(2v+e)
v------边的个数
e------点的个数
图的储存形式是点储存,即对每个点储存它的邻接点。所有点的邻接点加起来是2v个。
图结构中还有一个color数组用来储存每个点的颜色,长度为e。 - 探索过程:O(m+1)
m------图的色数
需要一个数组color记录每个色数的使用情况,如果邻接点中有颜色已经被使用,则该颜色会在color中被标注。该color数组的长度即m。
五、附录
图着色
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e3;
int ansNum = 0;
typedef struct Graph {
vector<set<int>>G;
int color[MAXN] = { 0 };
int e, v, ColorNum;
Graph(int e, int v, int ColorNum) {
this->e = e;
this->v = v;
this->ColorNum = ColorNum;
for (int i = 0; i < e; i++)
{
G.push_back(set<int>());
}
srand((int)time(NULL));
}
void CreatV()
{
for (int i = 0; i < v; i++)
{
int a = rand() % e, b = rand() % e;
printf("第%2d条边 (%d, %d) \n", i + 1, a, b);
if (a == b)
continue;
G[a].insert(b);
G[b].insert(a);
}
printf("--------------------------------------------\n");
}
void display()
{
printf("第%3d种解法:", ++ansNum);
for (int i = 1; i < e; i++)
{
printf("%2d ", color[i]);
}
printf("\n");
}
}Graph;
Graph Input()
{
int e, v, m, mode;
printf("请输入图的点数——");
scanf("%d", &e);
printf("请输入图的边数——");
scanf("%d", &v);
printf("请输入颜色数量——");
scanf("%d", &m);
Graph graph = Graph(e, v, m);
printf("请选择边的产生形式:[0]手动输入 [1]自动输入——");
mode = 1;
//scanf("%d", &mode);
printf("\n\n--------------------------------------------\n");
if (mode)
graph.CreatV();
else
{
for (int i = 0; i < v; i++)
{
int a, b;
scanf("%d %d", &a, &b);
printf("第%2d条边 (%d, %d) \n", i + 1, a, b);
if (a == b)
continue;
graph.G[a].insert(b);
graph.G[b].insert(a);
}
printf("--------------------------------------------\n");
}
return graph;
}
void solve(int index, Graph graph)
{
int e = graph.e;
if (index == e)
{
graph.display();
return;
}
vector<int>color(graph.ColorNum + 1, 0);
for (auto neighbor : graph.G[index])
{
int neighbor_color = graph.color[neighbor];
color[neighbor_color] = 1;
}
for (int i = 1; i <= graph.ColorNum; i++)
{
if (!color[i])
{
graph.color[index] = i;
solve(index + 1, graph);
graph.color[index] = 0;
}
}
}
int main()
{
Graph graph = Input();
solve(0, graph);
if (ansNum == 0)
printf("无可行方案!");
}
打印图
#include<bits/stdc++.h>
#define random(a,b) (rand()%(b-a)+a)
using namespace std;
const int MAXN = 1e3;
int ansNum = 0;
void writeFile(string);
typedef struct Graph {
vector<set<int>>G;
int color[MAXN] = { 0 };
int e, v, ColorNum;
Graph(int e, int v, int ColorNum) {
this->e = e;
this->v = v;
this->ColorNum = ColorNum;
for (int i = 0; i < e; i++)
{
G.push_back(set<int>());
}
srand((int)time(NULL));
}
void CreatV()
{
string str = "";
for (int i = 0; i < v; i++)
{
int a = rand() % e, b = rand() % e;
printf("第%2d条边:(%d, %d) ", i + 1, a, b);
if (i & 1)
printf("\n");
if (a == b)
continue;
G[a].insert(b);
G[b].insert(a);
}
printf("\n\n");
}
}Graph;
int main()
{
srand((int)time(0));
for (int e = 6; e < 10; e++)
{
for (int i = 0; i < 2; i++)
{
int v = random((int)(e * e / 2), (int)(e * e / 1.5));
int m = random((int)(e / 2.5), e);
ansNum = 0;
string ss = "点数 = " + to_string(e) + "; 边数 = " + to_string(v) + "; 色数 = " + to_string(m) + ";\n";
cout << ss;
Graph graph = Graph(e, v, m);
graph.CreatV();
}
}
}
时间散点图代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
import random
def draw(x, y, z):
plt.style.use('seaborn-whitegrid')
colors = [random.random() for i in range(len(x))]
ax = plt.subplot(projection='3d') # 创建一个三维的绘图工程
ax.set_title('Times of Graph coloring') # 设置本图名称
ax.scatter(x, y, z, s=30, c=colors, cmap='viridis')
ax.set_xlabel('e') # 设置x坐标轴
ax.set_ylabel('v') # 设置y坐标轴
ax.set_zlabel('Times') # 设置z坐标轴
plt.tick_params(pad=1) # 刻度距离坐标轴的距离调整
plt.savefig(r"D:\Desktop\a.png", bbox_inches='tight', dpi=1000)
plt.show()
if __name__ == "__main__":
path = r"D:\Desktop\time.txt"
data = open(path).readlines()
x, y, z = [], [], []
for i in data:
a, b, c = i.split(' ')
x.append(int(a))
y.append(int(b))
z.append(float(c))
draw(x, y, z)