回溯法解决地图填色问题

news2024/11/16 6:41:25

目录

回溯法

最大度优先

最少可选颜色优先

向前探测

随机产生不同规模的图,分析算法效率与图规模的关系(四色)


回溯法

回溯法的基本思想是采用递归和深度优先搜索的方法,尝试在一组可能的解中搜索出符合要求的解,在搜索过程中,若发现当前所选的方案不能得到正解,就回溯到前面的某一步(即撤销上一次的选择),换一种可能性继续尝试,直到找到符合要求的解或者所有的可能性都已尝试完毕。

在地图填色中,回溯法从某一区域开始,如图4所示,尝试使用不同的颜色进行填充,然后递归地尝试填充相邻的区域,如果发现当前填充颜色与相邻区域的颜色冲突,则回溯到之前的状态重新选择一种颜色进行填充,如此往复直到所有的区域都被填充上颜色或者无解。

图4 回溯法地图填色示例

伪代码

C++代码 

#include<iostream>
#include<fstream>
#include<chrono>
#include<sstream>

using namespace std;
void fillColor(const int);
int map[450][450] = {0};
int color[450] = {0};
const int colorNumber = 4;
int vertexNumber = 0;
int edgeNumber = 0;
int solution = 0;
int done=0;
fstream file("C:\\Users\\Yezi\\Desktop\\C++\\MapColoring\\Map\\le9_4.txt");
string line, word;

int main() {
    if (!file.is_open()) {
        cout << "File error.\n";
        return 1;
    }
    getline(file, line);
    istringstream iss(line);
    iss >> word >> word >> vertexNumber >> edgeNumber;
    int head, tail;
    for (int i = 0; i < edgeNumber; i++) {
        getline(file, line);
        istringstream ISS(line);
        ISS >> word >> head >> tail;
        map[tail - 1][head - 1] = map[head - 1][tail - 1] = 1;
    }
    auto start = chrono::high_resolution_clock::now();
    fillColor(0);
    auto end = chrono::high_resolution_clock::now();
    auto consume = chrono::duration_cast<chrono::milliseconds>(end - start);
    cout << "There is " << solution << " solutions.\n" << "The time consumed is " << consume.count()
         << " ms.\n";
    return 0;
}

bool conflict(const int &vertex) {
    for (int i = 0; i < vertexNumber; i++) {
        if (map[vertex][i] && color[vertex] == color[i])
            return true;
    }
    return false;
}

void fillColor(const int vertex) {
    if(done==vertexNumber){
        solution++;
        for(int i=0;i<vertexNumber;i++)
            cout<<color[i]<<' ';
        cout<<endl;
        return;
    }
    for(int i=1;i<=colorNumber;i++){
        color[vertex]=i;
        if(!conflict(vertex)){
            done++;
            fillColor(vertex + 1);
            done--;
        }
        color[vertex]=0;
    }
}

运行结果

如图5所示,对于小规模地图,回溯法成功在323毫秒内找出480个解,并将每个解打印出来,验证了算法的正确性。

图5 回溯法小规模地图填色

对附件中给定的地图数据填涂;

首先还是用经典回溯法试跑一下,只找一个解的情况,如表1所示。

表1 经典回溯法大规模地图填色

由结果可以看出,当规模大时,回溯法的搜索空间会变得非常庞大,从而需要耗费大量的时间和内存资源来完成搜索过程,这将导致算法的运行时间呈指数级增长,短时间内无法求解。因此,我们需要对回溯法进行优化。

最大度优先

经典回溯法的问题在于解的空间太大,回溯次数太多,而优先选择邻边个数最多的顶点进行填色则会对剩下未填色的顶点产生更多的限制,从而减少回溯的次数,如图6所示,每次填色,我们都优先填度最大的区域。

图6 最大度优先地图填色示例

伪代码

C++代码 

#include<iostream>
#include<fstream>
#include<chrono>
#include<sstream>

using namespace std;
struct Vertex{
    int degree=0;
    int place;
}vertex[450];
void fillColor(const int);
int map[450][450] = {0};
int color[450] = {0};
const int colorNumber = 15;
int vertexNumber = 0;
int edgeNumber = 0;
int solution = 0;
int done=0;
fstream file("C:\\Users\\Yezi\\Desktop\\C++\\MapColoring\\Map\\le450_15b.txt");
string line, word;

int main() {
    if (!file.is_open()) {
        cout << "File error.\n";
        return 1;
    }
    getline(file, line);
    istringstream iss(line);
    iss >> word >> word >> vertexNumber >> edgeNumber;
    int head, tail;
    for (int i = 0; i < edgeNumber; i++) {
        getline(file, line);
        istringstream ISS(line);
        ISS >> word >> head >> tail;
        map[tail - 1][head - 1] = map[head - 1][tail - 1] = 1;
    }
    for(int i=0;i<vertexNumber;i++){
        vertex[i].place=i;
        for(int j=0;j<vertexNumber;j++){
            vertex[i].degree+=map[i][j];
        }
    }
    sort(vertex,vertex+vertexNumber,[](const Vertex&a,const Vertex&b){return a.degree>b.degree;});
    auto start = chrono::high_resolution_clock::now();
    fillColor(0);
    auto end = chrono::high_resolution_clock::now();
    auto consume = chrono::duration_cast<chrono::milliseconds>(end - start);
    cout << "We had found " << solution << " solutions.\n" << "The time consumed is " << consume.count()
         << " ms.\n";
    return 0;
}

bool conflict(const int &vertexIndex) {
    for (int i = 0; i < vertexNumber; i++) {
        if (map[vertexIndex][i] && color[vertexIndex] == color[i])
            return true;
    }
    return false;
}

void fillColor(const int vertexIndex) {
    if(done==vertexNumber){
        solution++;
        return;
    }
    for(int i=1;i<=colorNumber;i++){
        color[vertex[vertexIndex].place]=i;
        if(!conflict(vertex[vertexIndex].place)){
            done++;
            fillColor(vertexIndex + 1);
            if(solution>0)
                return;
            done--;
        }
        color[vertex[vertexIndex].place]=0;
    }
}

运行结果

先在小规模地图上验证算法的正确性,如图7所示,最大度优先可以在325毫秒内找出480个解。

图7 最大度优先小规模地图填色

然后尝试填涂三个大规模地图,只找一个解的情况,如表2所示。

表2 最大度优先大规模地图填色

由结果可知,我们的最大度优先优化策略略显成效,但是第一个和第二个地图还是无法在短时间内找到解,我们需要继续努力。

最少可选颜色优先

每次选择区域进行填色时优先选择剩余可用颜色最少的区域进行填色,这样可以减少剩余可用颜色最多的地区需要尝试不同颜色的次数,如图8所示,每填完一个区域就更新邻近区域的可选颜色,然后优先选择可选颜色最少的区域进行填色。

图8 最少可选颜色优先地图填色示例

伪代码

C++代码 

#include<iostream>
#include<fstream>
#include<chrono>
#include<sstream>
#include<vector>
using namespace std;
void fillColor(const int);
int map[450][450] = {0};
int color[450] = {0};
const int colorNumber = 25;
bool colorAccess[450][colorNumber+1];
int degree[450]={0};
int colorAccessNumber[450];
int vertexNumber = 0;
int edgeNumber = 0;
int solution = 0;
int done=0;
fstream file("C:\\Users\\Yezi\\Desktop\\C++\\MapColoring\\Map\\le450_25a.txt");
string line, word;

int main() {
    if (!file.is_open()) {
        cout << "File error.\n";
        return 1;
    }
    getline(file, line);
    istringstream iss(line);
    iss >> word >> word >> vertexNumber >> edgeNumber;
    int head, tail;
    for (int i = 0; i < edgeNumber; i++) {
        getline(file, line);
        istringstream ISS(line);
        ISS >> word >> head >> tail;
        map[tail - 1][head - 1] = map[head - 1][tail - 1] = 1;
    }
    for(int i=0;i<vertexNumber;i++){
        for(int j=1;j<=colorNumber;j++)
            colorAccess[i][j]= true;
        for(int j=0;j<vertexNumber;j++)
            degree[i]+=map[i][j];
    }
    auto start = chrono::high_resolution_clock::now();
    fillColor(0);
    auto end = chrono::high_resolution_clock::now();
    auto consume = chrono::duration_cast<chrono::milliseconds>(end - start);
    cout << "There is " << solution << " solutions.\n" << "The time consumed is " << consume.count()
         << " ms.\n";
    return 0;
}

bool conflict(const int &vertex) {
    for (int i = 0; i < vertexNumber; i++) {
        if (map[vertex][i] && color[vertex] == color[i])
            return true;
    }
    return false;
}
int MRV(const int vertex,vector<int>&MRVRecover){
    for(int i=0;i<vertexNumber;i++){
        if(map[vertex][i]){
            if(colorAccess[i][color[vertex]]){
                MRVRecover.push_back(i);
                colorAccess[i][color[vertex]]= false;
            }
        }
    }
    int next=0;
    int minColor=colorNumber;
    for(int i=0;i<vertexNumber;i++){
        colorAccessNumber[i]=0;
        for(int j=1;j<=colorNumber;j++){
            if(colorAccess[i][j])
                colorAccessNumber[i]++;
        }
        if(minColor>colorAccessNumber[i]&&color[i]==0){
            minColor=colorAccessNumber[i];
            next=i;
        }
    }
    return next;
}
void MRV_Recover(const int vertex,vector<int>&MRVRecover){
    for(auto&it:MRVRecover){
        colorAccess[it][color[vertex]]= true;
    }
}
void fillColor(const int vertex) {
    if(done==vertexNumber){
        solution++;
//        for(int i=0;i<vertexNumber;i++)
//            cout<<color[i]<<' ';
//        cout<<endl;
        return;
    }
    for(int i=1;i<=colorNumber;i++){
        color[vertex]=i;
        if(!conflict(vertex)){
            done++;
            vector<int>MRVRecover;
            int next= MRV(vertex,MRVRecover);
            fillColor(next);
            if(solution>0)
                return;
            MRV_Recover(vertex,MRVRecover);
            done--;
        }
        color[vertex]=0;
    }
}

运行结果

先在小规模地图上验证算法的正确性,如图9所示,可以在321毫秒内找出480个解。

图9 最少可选颜色小规模地图填色

然后尝试填涂三个大规模地图,结果如表3所示

表3 最少可选颜色优先大规模地图填色

由结果可知,最少可选颜色优先的优化策略使得第一个图也可以在2秒内找到解了,通过算法的优化,原本短时间内无解的问题可以迅速解决。

然后我们尝试将最大度优先和最少可选颜色优先结合去填涂三个大规模地图,结果如表4所示。

表4 最少可选颜色+最大度地图填色

由结果可知,将最少可选颜色优先和最大度优先相结合后,三个地图均可以迅速找到解,其中第一个地图需要600毫秒,而第二个地图在3秒内终于找到了一个解。

继续测试,对第一个地图找全部解,对第二个和第三个地图找10万个解,结果如表5所示,可知该优化策略可以迅速找解。

表5 最少可选颜色+最大度找多解

向前探测

每次选择区域进行填色的时候,先判断该填涂的颜色是否会导致邻近的区域无色可填,如果导致了邻近区域无色可填则直接换一种颜色填涂,如图10所示,每填一个区域就更新邻近区域的可用颜色,如果可用颜色为0则说明此处不能填这个颜色,进行剪枝。

图10 向前探测地图填色示例

伪代码

C++代码 

#include<iostream>
#include<fstream>
#include<chrono>
#include<sstream>
#include<vector>
using namespace std;
void fillColor(const int);
int map[450][450] = {0};
int color[450] = {0};
const int colorNumber = 5;
bool colorAccess[450][colorNumber+1];
int colorAccessNumber[450];
int vertexNumber = 0;
int edgeNumber = 0;
int solution = 0;
int done=0;
fstream file("C:\\Users\\Yezi\\Desktop\\C++\\MapColoring\\Map\\le450_5a.txt");
string line, word;

int main() {
    if (!file.is_open()) {
        cout << "File error.\n";
        return 1;
    }
    getline(file, line);
    istringstream iss(line);
    iss >> word >> word >> vertexNumber >> edgeNumber;
    int head, tail;
    for (int i = 0; i < edgeNumber; i++) {
        getline(file, line);
        istringstream ISS(line);
        ISS >> word >> head >> tail;
        map[tail - 1][head - 1] = map[head - 1][tail - 1] = 1;
    }
    for(int i=0;i<vertexNumber;i++){
        for(int j=1;j<=colorNumber;j++)
            colorAccess[i][j]= true;
    }
    auto start = chrono::high_resolution_clock::now();
    fillColor(0);
    auto end = chrono::high_resolution_clock::now();
    auto consume = chrono::duration_cast<chrono::milliseconds>(end - start);
    cout << "There is " << solution << " solutions.\n" << "The time consumed is " << consume.count()
         << " ms.\n";
    return 0;
}

bool conflict(const int &vertex) {
    for (int i = 0; i < vertexNumber; i++) {
        if (map[vertex][i] && color[vertex] == color[i])
            return true;
    }
    return false;
}

void FC_Recover(const int vertex,vector<int>&FCRecover){
    for(auto&it:FCRecover){
        colorAccess[it][color[vertex]]= true;
    }
}
bool FC(const int vertex,vector<int>&FCRecover){
    for(int i=0;i<vertexNumber;i++){
        if(map[vertex][i]){
            if(colorAccess[i][color[vertex]]){
                FCRecover.push_back(i);
                colorAccess[i][color[vertex]]= false;
            }
        }
    }
    for(int i=0;i<vertexNumber;i++){
        colorAccessNumber[i]=0;
        for(int j=1;j<=colorNumber;j++){
            if(colorAccess[i][j])
                colorAccessNumber[i]++;
        }
        if(colorAccessNumber[i]==0&&color[i]==0){
            FC_Recover(vertex,FCRecover);
            return false;
        }
    }
    return true;
}
void fillColor(const int vertex) {
    if(done==vertexNumber){
        solution++;
//        for(int i=0;i<vertexNumber;i++)
//            cout<<color[i]<<' ';
//        cout<<endl;
        return;
    }
    for(int i=1;i<=colorNumber;i++){
        color[vertex]=i;
        vector<int>FCRecover;
        if(!conflict(vertex)&&FC(vertex,FCRecover)){
            done++;
            fillColor(vertex+1);
            if(solution>0)
                return;
            done--;
            FC_Recover(vertex,FCRecover);
        }
        color[vertex]=0;
    }
}

运行结果

先在小规模地图上验证算法的正确性,如图11所示,可以在412毫秒内找出480个解。

图11 向前探测小规模地图填色

然后尝试填涂三个大规模地图,结果如表6所示。

表6 向前探测大规模地图填色

由结果可知,单纯的向前探测无法在短时间内找出三个地图的解,下面我们将向前探测和最大度优先结合起来,填涂三个大规模地图,结果如表7所示。

表7 向前探测+最大度地图填色

再加上最少可选颜色优先,填涂三个大地图,结果如表8所示。

表8 向前探测+最少可选颜色+最大度地图填色

对第一个地图找全部解,对第二个和第三个地图找10万个解,结果如表9所示。

表9 向前探测+最少可选颜色+最大度找多解

由此可知,与最大度优先和最少可选颜色优先相比,向前探测的优化效果不是特别明显。

随机产生不同规模的图,分析算法效率与图规模的关系(四色)

(1)固定边

固定图的边数为1000条边,然后随机生成顶点数为100到1000的平面图,测试多组数据取众数,结果如图12所示。

图12 固定边为1000不同顶点数的地图填色

具体数据如表10所示。

表10 固定边为1000不同顶点数的地图填色

由结果可知,边数固定的情况,顶点数越多,消耗的时间和资源也更多,解的搜索空间变大,搜索时间更长。

(2)固定点

固定图的顶点数为100,随机生成边数为100到1000的平面图,测试多组数据取众数,结果如图13所示。

图13 固定点为100不同边数的地图填色

具体数据如表10所示。

表10 固定点为100不同边数的地图填色

由结果分析,算法执行的时间先是随着边数的增加而增加,这是因为解的搜索空间增加了,而后当边数达到一定程度,边密度越大,图变得更加复杂,可选的颜色减少,算法剪枝的效率更高,所以搜索效率会更高。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/731368.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Langchain的新课程;Mozilla开发的AI文档工具遭到开发者批评

&#x1f989; AI新闻 &#x1f680; Mozilla开发的AI文档工具遭到开发者批评&#xff0c;已下线 摘要&#xff1a;Mozilla开发的基于生成式AI的工具AI Help在开发者群体中遭到广泛批评。开发者认为该工具提供的信息常常错误&#xff0c;甚至认为它没有这个功能反而更好。针对…

Flutter 仿抖音、豆瓣、知乎、番茄小说的评论弹窗开发实践

最近用flutter做了一个评论弹窗的功能&#xff0c;本来以为很简单的烂大街的一个功能&#xff0c;结果却遇到了不少的问题&#xff0c;而且这些问题我觉得很有意义&#xff0c;以至于我觉得我如果分享出来可能会对其他人很有帮助。 要做一件事情可能会很容易&#xff0c;但做好…

ModaHub魔搭社区: AI模型社区ModelScope和Hugging Face行业分析报告

目录 引言&#xff1a; 一、github星数&#xff1a; 二、模型数&#xff1a; 三、数据集&#xff1a; 四、演示应用程序数&#xff1a; 五、下载数&#xff1a; 六、开发者、付费企业和公司用户数&#xff1a; 结论&#xff1a; 引言&#xff1a; AI模型开源社区在近年…

MQTT资料储备

1、官网 https://mqtt.org/ MQTT 5.0官方协议 https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html 说明&#xff1a;官网可以获取到好多资料&#xff08;比如常用软件、协议、usecase等&#xff09; 2、安装部署EMQX 当前有好多MQTT服务器&#xff0c;初步选择了EM…

MyBatis查询数据库【秘籍宝典】

0.MyBatis执行流程1.第一个MyBatis查询1.创建数据库和表1.2.添加MyBatis框架依赖【新项目】1.3.添加MyBatis框架依赖【旧项目】1.4.配置连接数据库1.5.配置MyBatis的XML路径2.MyBatis模式开发2.1 添加MyBatis的xml配置 3.增查改删&#xff08;CRUD&#xff09;5.1.增加操作5.2.…

机器学习之多元微积分

机器学习的多元微积分跟高等数学中的多元微积分有很多不同之处。 机器学习中的变量都是向量或者矩阵机器学习中的函数一般都是线性函数&#xff0c;而不是高数中的曲线和曲面、体积等函数。因此&#xff0c;机器学习中的微积分跟线性代数结合在一起。机器学习中导数的分子分母…

Chapter 4: Functions | Python for Everybody 讲义笔记_En

文章目录 Python for Everybody课程简介FunctionsFunction callsBuilt-in functionsType conversion functionsMath functionsRandom numbersAdding new functionsDefinitions and usesFlow of executionParameters and argumentsFruitful functions and void functionsWhy fun…

GEE:GEE上一些好看的可视化参数

作者&#xff1a;CSDN _养乐多_ 为了方便使用&#xff0c;总结了几种可视化参数。 文章目录 一、连续型1.11.21.31.41.5 二、离散型2.1 一、连续型 1.1 var phenoPalette [ff0000,ff8d00,fbff00,4aff00,00ffe7,01b8ff,0036ff,fb00ff]1.2 var evapotranspirationVis {min…

异地手机卡如何在当地办理宽带【纯教程篇】

有一些小伙伴常年在外漂泊会经常使用异地卡套餐&#xff0c;但是当你需要办理宽带业务的时候&#xff0c;很多时候当地的营业厅会让你再办理一张他们本地的手机卡&#xff0c;不方便不说还要多支付一张卡的月租也不划算。 其实呢运营商早就有异地办理宽带的业务&#xff0c;只不…

公司新来了个卷王,还是个00后,我们这帮老油条真干不过...

都说00后躺平了&#xff0c;但是有一说一&#xff0c;该卷的还是卷。这不&#xff0c;前段时间我们公司来了个00后&#xff0c;工作没两年&#xff0c;跳槽到我们公司起薪18K&#xff0c;都快接近我了。后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了。 …

浅析集群、分布式、负载均衡

平时开发或者面试中进场听到集群、分布式、负载均衡等系列的名词&#xff0c;他们之间有什么联系呢&#xff0c;本文就简要的抛砖引玉一下。 集群 1.什么是集群 集群一般指的是服务器集群。集群其实就是一组相互独立的计算机&#xff0c;通过高速的网络组成一个计算机系统。…

深度学习笔记之Transformer(六)Position Embedding铺垫:Skipgram与CBOW模型

深度学习笔记之Transformer——PositionEmbedding铺垫&#xff1a;Skipgram与CBOW模型 引言回顾&#xff1a; Word2vec \text{Word2vec} Word2vec模型补充&#xff1a;关于 Word2vec \text{Word2vec} Word2vec的一些说明 引言 上一节介绍了 Word2vec \text{Word2vec} Word2vec…

【动态规划算法】第九题:64. 最小路径和

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你…

Todo-List案例版本一

初级使用e.target.value 记得安装npm i nanoid与UUID类似 快捷键ctrlH替换内容 src/components/MyHeader.vue <template><div class"todo-header"><input type"text" placeholder"请输入你的任务名称&#xff0c;按回车键确认&quo…

express框架中使用ejs

1.设置模块引擎为ejs app.set("view engine","ejs") 2. 设置模版文件存放位置 说明&#xff1a;模版文件&#xff1a;具有模版语法内容的文件。 app.set(vies,path.resolve(__dirname,"./views")) 3.render渲染 app.get(/home,(req,res)>{/…

MySQL第三天(简单单表查询)

前言 今天的三个题目是针对于单表查询和多表查询的课后作业&#xff0c;针对于初学者还是很合适的听有用处的&#xff0c;我会把我的答题过程一步一步写出来&#xff0c;有需要的小伙伴可以参考哦… 第一题、单表信息查询 题目代码如下&#xff1a; 作业&#xff1a;1.创建表…

【计算机组成原理总结】

第一章计算机系统概述 第二章数据的表示与运算 第三章存储系统 第四章 指令系统 第五章 中央处理器 第六章 总线 第七章 输入输出设备

Mac如何在终端使用diskutil命令装载和卸载推出外接硬盘

最近用 macOS 装载外接硬盘的时候&#xff0c;使用mount死活装不上&#xff0c;很多文章也没详细的讲各种情况&#xff0c;所以就写一篇博客来记录一下。 如何装载和卸载硬盘&#xff08;或者说分区&#xff09; mount和umount是在 macOS 上是不能用的&#xff0c;如果使用会…

Clickhouse入门(一)

第一章 Clickhouse简介 ClickHouse (C编写)是俄罗斯的Yandex(相当于百度)于2016年开源的列式存储数据库&#xff08;DBMS&#xff09;&#xff0c;使用C语言编写&#xff0c;主要用于在线分析处理查询&#xff08;OLAP&#xff09;&#xff0c;能够使用SQL查询实时生成分析数据…

电脑各配置跟不上,造成频繁花屏。

本人用的是i3 7350K&#xff0c;然而散热器是二十多块的杂牌&#xff0c;CPU温度经常不稳定&#xff0c;可以在监控软件看到比较详细的情况&#xff0c;然后我的显卡是gtx1080&#xff0c;内存加到双条24G。 最近一直花屏&#xff0c;我甚至怀疑是不是显卡坏了&#xff0c;然后…