回溯法求地图填色(剪枝)

文章目录

  • 回溯法求地图填色(剪枝)
    • (一) 问题求解
      • 思维风暴(之前瞎想的,可以跳过)
    • (二)算法思想:回溯
      • 伪代码:
      • 时间复杂度分析:
    • (三)剪枝方向
      • 1. 下层节点选取
        • 1) 按邻边个数降序选取
        • 2) 搜寻邻边时以分支少优先
      • 2.向前探查
        • 1) 探查1步
        • 2) 探查2步
        • 3) 探查多步
      • 3. 数学等效
        • 1) 利用完全子图 × 阶乘数
        • 2) 利用颜色等效 × 等效数
    • 总结

(一) 问题求解

我们可以将地图转换为平面图,每个地区变成一个节点,相邻地区用边连接,我们要为这个图形的顶点着色,并且两个顶点通过边连接时必须具有不同的颜色。

思维风暴(之前瞎想的,可以跳过)

关键: 如何提前探知不可行的解?

  1. 第一个展开的节点怎么选择?下一层又怎么选择?
  • 以邻接关系为标准:

    • 以顶点序号0展开:每次选择邻边的节点(DFS) (默认无影响)
  • 以邻边个数为标准:
    • 以邻边个数最少的展开:由于先前填色产生的约束最少,导致下一层选择的情况会增多,(负优化)
    • 以邻边个数最多的展开:由于先前填色产生的约束最多,导致下一层选择的情况会减少(合法性判断),得到优化。
  1. 如何通过一个节点的填色判断不可行? (结束条件)

    设置每个顶点的可能性大小(如填四色就为0,1,2,3,4),每次填色之后就判断周边的节点可能性是否为0,若为0则不可行。

  2. 展开节点使用什么颜色? 优先已填颜色数量最小的颜色?

    由于每次填色均在一个for循环中填色,暂时不能理解是否有效。

  3. 是否能够通过 可能性大小 而决定下层填色节点的优先级 来提前探测到 结束条件

  • 由于已有第一种遍历方法,且图中节点个数多,填色种类少(4色,15色,25色),单单使用可能性大小的区别度不大。所以不能单纯摈弃第一种方法而以可能性作为下层节点选择的判断,因为单单使用可能性大小的区别度不大。
  • 如果兼容第一种方法的话,可以选择在邻边个数相同的情况下,再以可能性大小作为优先级来选择下层节点。
    • 如果兼容选择的话,需要以可能性大的优先还是以可能性小的优先?

      • 以可能性大的优先:?
      • 以可能性小的优先:优先探测可能性为1的点附近是否有该点唯一的颜色,如果有,则剪枝。(比如1号节点填了红色,导致2号节点只有蓝色的可能性,那么我就探测2号节点附近是否有蓝色的节点。)
  1. 如果只要求方案总数,而不要求具体方案的话

以四色为例,第一个点就有四种可能。由对称性知,如果第一个节点涂上红色的方案数有x种,那么涂上其他三色中的任意一种也会得到相同的方案数x。即:

总方案数 = 第一个节点的第一种方案数 × 颜色种类数

假如原方法时间为1。那么此方法可以固定变为: 1/颜色种类数 的时间

  1. 细枝末节 如果相邻节点已有 唯一可能性的颜色 或者 也只剩下唯一的同色可能性

    • 可以排除,但是回溯时的恢复需要考虑全面。
  2. 颜色数量 占比可以吗 ? 比如某种颜色填涂次数超过了全部节点的70%,我就直接pass掉该方案。
  3. 数据结构存储是否会带来时间的提升,比如是用链表还是用矩阵?(当然两个一起更好了。)

后续才知道的总结:必须通过邻边来选取下层节点,便于后续剪枝!

邻边 --> 好剪枝 --> 大规模肯定用


(二)算法思想:回溯

对一个节点进行填色时,如果颜色合法,则填下一层节点。如果所填颜色非法,则回溯到上一层节点重新填色。直至地图所有节点填色完毕。

伪代码:

void paint ( v , color )setColor( v , color )if ( v == num -1)sum++ else for ( c = 1 to colorNum) if ( legal(v+1,c) ) //如果颜色合法paint ( v+1,c)  //涂色deleteColor(v+1,c)  //涂完回溯

时间复杂度分析:

由于每层节点涂色时有colorNum种颜色选择,需递归n层节点才能得出解。故时间复杂度:
T(n)=O(colorNumn)T(n) = O(colorNum ^n) T(n)=O(colorNumn)
可见时间复杂度为指数级,故涂色大规模地图时不得不需要减少解空间来缩小所需时间。

(三)剪枝方向

1. 下层节点选取

1) 按邻边个数降序选取

策略概述:以邻边个数降序排序所有顶点,从最大度数的顶点开始涂色,下一层节点涂度数次多的节点,直到涂到最少度数的节点。

优点:虽然不按邻边搜寻节点,但是此方法可以在前几步就对地图节点产生了相对最多的约束。无需数学等效,跑小规模的示例地图(9顶点)仅需3ms。

缺点:此方法不适用于顶点个数大的大规模图。涂色失败后为了更改周边邻点往往会向上回溯多层直到邻边节点的涂色层数。如图1所示,数字代表涂色层数,当涂色到第55层节点时,由于判断第58层节点没有可涂颜色了,所以得回退更改28层的颜色。但是为了更改第28层节点的颜色,需要从55层向上回溯,然而每次回溯到其余合法层,仍会换种颜色向下涂色到55层。造成大量时间的浪费。

​ 图一

2) 搜寻邻边时以分支少优先

策略概述:优先搜索可涂颜色数最小的节点(MRV),如果可涂颜色相同则优先搜索邻边数量最多的节点(DH)。

优点:容易导致失败的变量先赋值,可以早些发现是否注定失败。如果可涂颜色少的节点注定失败,可以提前回溯,否则会先涂其他颜色的节点后才发现失败。

剪枝节选伪代码:(以插入排序得到最优节点)

//遍历未涂色邻接点存入数组next[num]void sort(int next[], int num) { //排序next[]数组后得到最优先的节点下标放入next[0]for (i = 1 to num ) key = next [i]for (j = i – 1 downTo= 0) {//可能性小的优先 || 邻边小的优先if ((colorEnableNum[next [j]] > colorEnableNum[key])|| ((colorEnableNum[next[j]]==colorEnableNum[key])&&(adjoin[next[j]]< adjoin[key]) ) )next[j + 1] = next[j]//若前面数据较大,则往后移动一位elsebreaknext[j + 1] = key //前面数据小于key时,将key插入到数组中。

(可以按此思路改成归并)

2.向前探查

1) 探查1步

如图2所示两种失败情况,通过对点1涂蓝色后,探查周边邻接点是否没可涂色的可能性,或者邻接点是否仅剩蓝色可涂而造成冲突。这样可以提前预知失败而停止回溯。

图 2

2) 探查2步

对点1涂黄色后,如果导致周边邻接点2号的可填涂颜色仅剩一种颜色—蓝色,那么可以继续沿着上述思路探查2号邻接点3的可能性。

图 3

3) 探查多步

递归通过前两步,可以设计向前探查的递归函数,但实际运行后,发现重复递归探查运行时间无较大改善,甚至导致运行代码增多而导致时间增大。

分析发现递归探查的剪枝可能性小,且容易覆盖第1步探查的剪枝效果导致效率无提升。(排除)

算法真代码:

//填色并减少周边节点可填涂颜色后
bool checkPointIColorJ(int i, int j) {node *q = list[i]->next;int k;while (q != nullptr) {  //k 是 点i 的邻接点k = q->index;if (colorEnableNum[k] == 0) {  //如果k的可填色数为0
//                  cout << "2";return false;} else if (colorEnableNum[k] == 1 && colorP[k][j] == true) { //如果k和i都只能涂同一个色return false;} else if (colorEnableNum[k] == 1 ) { //此时k只能填其他色 int w;int nowColor;for (w = 0; w < colorType; w++) {if (colorP[k][w] == true) {nowColor = w;//仅剩nowColorP色可涂break;}}node *p = list[k]->next;while (p != nullptr) {  //找到与k相邻接的w w = p->index;if (w != i) {if (colorKind[w] == nowColor) {  //如果k的唯一可能颜色已经被填 return false;}if (colorEnableNum[w] == 1 && colorP[w][nowColor] == true) { //k 和 w 都只能涂一个色return false;}}p = p->next;}}q = q->next;}return true;
}

3. 数学等效

1) 利用完全子图 × 阶乘数

剪枝概述:由于填涂颜色的对称性,四种颜色之间可以建立一个一一对应的双射关系,,使得颜色轮换后仍满足填色方案。第一个节点四种颜色均等效,第二个节点剩余三种颜色等效…只要找到一个4阶完全子图(在大规模的图中出现概率大),就能实现方案数乘可涂颜色总数的阶乘得到全部方案数。

优点:利用完全n阶子图就能直接乘n!的等效方案数,效率提升显著。

缺点:需要提前找到n阶完全子图。


如上图所示,找到4阶完全子图(同时也是4色)即可使用
总方案数 = (4!) × (后续方案数)。

2) 利用颜色等效 × 等效数

剪枝概述:某点填色的时候,如果填到该点 有x种颜色从未被使用过,那么这x种颜色是等效的。如果一条分支中第一次使用 某颜色 ,剩余从未被使用过颜色数为y,就可以找到一种解方案乘y得到所有解。

优点:无需找到完全子图。

缺点:不能直接乘阶乘,仍有部分方案数需要求解,提升的效率较第一种方案低。

图 4

算法伪代码:

//准备涂color色
unUseColorNum = getUnUseColorNum()
if (unUseColor[color] == false) //如果color色从未用过unUseColor[color] = truesum += paint(v , color) * unUseColorNum
else //用过该色则不使用数学等效sum += paint(v,color)

总结

  1. 回溯法的实质是一种蛮力法,通过在解空间遍历所有的解得出结果。而地图填色是一种解空间随颜色或者顶点数增多而指数型增长的问题。使用回溯法的时候就需要通过剪枝来减少解空间。
  2. 如果找到了colorNum阶完全子图并且仅仅求m种方案数的话,只要colorNum阶乘超过m的话只需求得一解即可(后续解轮换得到)。这或许就是为什么那么多论文算法都只是求1个解而已的原因吧。

回溯法求地图填色实验(剪枝)相关推荐

  1. 算法设计与分析 实验三 回溯法求解地图填色问题

    回溯法求解地图填色问题 一.实验目的与要求 1.实验基本要求: 2.实验亮点: 二.实验内容与方法 三.实验步骤与过程 1.未优化的回溯: (1)算法描述: (2)编程实现 (3)运行并测试: 2.对 ...

  2. 回溯法求解消消乐实验

    回溯法求解消消乐问题 实验概述 掌握回溯法设计思想. 掌握消消乐问题的回溯法解法. <开心消消乐>是一款乐元素研发的三消类休闲游戏.游戏中消除的对象为小动物的头像,包括小浣熊.小狐狸.小青 ...

  3. java背包算法回溯法_【算法分析】实验 4. 回溯法求解0-1背包等问题

    [TOC] 实验内容 本实验要求基于算法设计与分析的一般过程(即待求解问题的描述.算法设计.算法描述.算法正确性证明.算法分析.算法实现与测试),通过回溯法的在实际问题求解实践中,加深理解其基本原理和 ...

  4. 消消乐实验回溯法(深大算法实验3)报告+代码

    实验代码 + 报告资源: 链接: https://pan.baidu.com/s/1CuuB07rRFh7vGQnGpud_vg 提取码: ccuq 目录 写在前面 实验要求 求解问题的算法原理描述 ...

  5. 用回溯法求子集和的c++代码

    用回溯算法解决问题的一般步骤为: 一.定义一个解空间,它包含问题的解. 二.利用适于搜索的方法组织解空间. 三.利用深度优先法搜索解空间. 四.利用限界函数避免移动到不可能产生解的子空间. 问题的解空 ...

  6. 分油问题回朔法c语言算法,用回溯法求“韩信分油”问题所有解

    裴南平 摘要:回溯法是一种常用的计算机程序设计方法.使用回溯法解决"韩信分油问题"也称"泊松分酒问题",在算法中保存每一步执行的中间结果,程序扩展前,判斷程序是 ...

  7. 回溯法求最佳工作分配方案

    这个问题是典型的回溯法,比八皇后问题简化一点,重要的是其中一个部分:在深搜过程中要进行值的判断,来决定是否停止当前的搜索,这对以减少运行时间十分重要,一开始我没有考虑这个问题,就导致很多样例都超时了. ...

  8. 回溯法求解装载问题(DFS + 剪枝策略)

    参考:https://blog.csdn.net/m0_38015368/article/details/80196634 问题描述: 有n个集装箱要装上2艘载重量分别为c1和c2的轮船,其中集装箱i ...

  9. 递归回溯法求数独全部解

    项目介绍 QT5做的数独求解程序,可以判断数独解的个数(如果非唯一解). 运行截图 源码说明 使用MSVC + QT5平台,故* .cpp和* .h文件均采用UTF8 + BOM编码.如果切换到Min ...

最新文章

  1. 服务器 上传文件 杀毒,一种实现文件上传网站后自动进行杀毒的方法及系统
  2. 为LUKS加密的磁盘/分区做增量备份
  3. Cisco 2960 交换机密码设置
  4. java jsf_使用Java和JSF构建一个简单的CRUD应用
  5. nat 网卡间数据包转发_nat端口转发示例
  6. Spark 调用 hive使用动态分区插入数据
  7. 怎么做笔记标签贴_小红书笔记互动到底该怎么做?
  8. Equals和==的差别
  9. 【AtCoder】AGC009
  10. vue使用canvas开发漂亮的多功能手写板组件
  11. matlab costas环,MATLAB写COSTAS环的问题。
  12. 【信息安全导论】HIT2022春季学期《信息安全导论》复习概要
  13. ds18b20工作原理和测温原理介绍
  14. 二叉堆/二项堆/斐波那契堆
  15. linux关机会自动重启,linux——如何在linux下让系统定时自动重启(关机)
  16. 51单片机之动态数码管显示
  17. 青提WiFi微信小程序安装教程常见错误解析
  18. Octotree | 树形展示 GitHub 项目代码结构
  19. 当 AI 掌握「读心术」:DeepMind AI 已经学会相互理解
  20. activiti工作流项目中显示流程进度(流程图高亮显示)

热门文章

  1. System Power Tools Suite
  2. 一个语法分析器的实现
  3. python里面Nose和pytest的区别
  4. JVM内存不足增大运行时内存
  5. 学术报告系列(八) - Fault-tolerant control of unmanned aerial vehicles
  6. swoole-redis连接池的问题总结
  7. 试用了多款报表工具,终于找到了基于.Net 6开发的一个了
  8. STM32CUBEMX驱动lcd1602,使用的是stm32f103c8t6芯片
  9. 计算机发展史之查尔斯·巴贝奇
  10. 虚拟机由于硬盘存储空间过满导致启动异常+虚拟机扩容