一.实验目的
- 掌握回溯法算法设计思想。
- 掌握地图填色问题的回溯法解法。
二、实验内容
1.问题描述:
我们可以将地图转换为平面图,每个地区变成一个节点,相邻地区用边连接,我们要为这个图形的顶点着色,并且两个顶点通过边连接时必须具有不同的颜色。
2.回溯法:
回溯法采用试错的思想,它尝试分步的去解决一个问题,是一种遍历问题的整个解空间树。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况:
a.找到一个可能存在的正确的答案;
b.在尝试了所有可能的分步方法后宣告该问题没有答案;
图1 回溯法伪代码
function Retrospective(n) |
第一步:判断输入或者状态是否非法? if invalid: return 第二步:判读递归是否应当结束? if match_condition: return rusult 第三步:遍历所有可能出现的下一步的情况 for (all possible cases) solution.push(case) result = fn(m) // 递归 第四步:回溯到上一步 solution.pop(case) |
3.回溯剪枝:
随着问题规模增大,朴素回溯法的效率越来越低。而剪枝策略可以剪掉找不到可行解的分支,减少搜索、避免无效搜索,提高搜索效率。因此设计好的剪枝策略时提高回溯算法效率的关键。
常用剪枝函数:
a.用约束函数在扩展结点处剪去不满足约束的子树;
b.用限界函数剪去得不到最优解的子树。
三、实验步骤与结果
- 小规模地图如图1所示,利用四色填色测试算法的正确性;
图2 小规模地图
(1)前期准备
将地图转换为平面图,每个地区变成一个节点,相邻地区用边连接。
图3 地图编号
用图的邻接矩阵形式存储位置信息:(1代表相邻,0代表不相邻或者自身)
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
1 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 |
2 | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 0 |
3 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
4 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 0 | 0 |
5 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 1 |
6 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 1 | 1 |
7 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
8 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 1 |
9 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
节点信息存储方式:
图4 用结构体进行存储节点信息
struct country: |
int id; int count; |
(2)伪代码
图5 回溯算法解决地图填色问题伪代码
Function Get_color(*color,di) |
if di>n //遍历完成 if right //判断是否合法 putout answer //输出解法 else for i: 1 -> m //遍历m种颜色的情况 color[di]=i //记录当前选择 Get_color(*color,di+1) //递归 |
function if_right() |
for i: 0 -> n-1 for j: i+1 -> n if c[i][j]=1 and color[i]=color[j] //如果两地相邻且颜色相同 return FALSE return TRUE |
(3)算法流程图:
图6 回溯算法解决地图填色问题流程图
(4)结果
共有480种解法:
(5)优化
a.剪枝
随着问题规模增大,朴素回溯法的效率越来越低。而剪枝策略可以剪掉找不到可行解的分支,减少搜索,提高搜索效率。
具体操作:剪掉相邻的国家且颜色相同的情况分支。
图9 剪枝优化伪代码
Function Get_color(*color,di) |
if di>n //遍历完成 if right //判断是否合法 putout answer //输出解法 else for i: 1 -> m //遍历m种颜色的情况 //若当前节点的相邻节点有该颜色,则剪去该分支 for k: 0 ->di if(c[di][k] and color[di-1]=j) continue color[di]=i //记录当前选择 Get_color(*color,di+1) //递归 |
效果:
对给定的小数据(9点4色)进行填涂,并将可行解全部输出,将程序运行20次取时间平均值做表如下:
剪枝优化前 | 剪枝优化后 |
14ms | 3ms |
b.最大度选择策略
对于每次搜索出的节点,拓展的节点为当前的可行解个数。而且,不论从哪个点开始搜索,得到的可行解不变。分析发现,分支产生的越早,分支就会产生的越多。在采取剪枝优化策略的基础上,将度更大的点放在离根节点更近的位置可以有效的降低搜索树树根处分支数。
具体操作:按照点度数降序顺序依次搜索。
图10 最大度选择策略图示
图11 最大度选择策略伪代码
TO_DO_SORT |
//用结构体存储国家的信息,包括编号和约束度(相邻国家数) struct country { int id; int count; } //重定义排序规则 bool comp(const country c1, const country c2) { return c1.count < c2.count; } //对国家节点进行排序 sort(country,country+n,comp) |
效果:
对给定的大数据(450点5色)进行填涂,并将可行解全部输出,将程序运行20次取时间平均值做表如下:
最大度选择策略优化前 | 最大度选择策略优化后 |
449.4s | 109.4s |
2.对附件中三个地图数据尝试分别使用5个(le450_5a),15个(le450_15b),25个(le450_25a)颜色为地图着色。
常规的回溯算法只适用于数量级较小的数据,当数据量较大时,运行时间需要几个小时以上,故需要进行优化。本实验采用上述优化算法进行求解。
①le450_5a:450个国家用5个颜色填色
算法:回溯+剪枝优化+最大度路径决策
图12 le450_5a地图填色结果 (共有3840种解法)
②le450_15b:450个国家用15个颜色填色
算法:回溯+剪枝优化+最大度路径决策
③le450_25b:450个国家用25个颜色填色
算法:回溯+剪枝优化+最大度路径决策
3、随机产生不同规模的图,分析算法效率与图规模的关系(四色)。
图15 不同规模地图填色运行时间
图16 不同规模地图填色运行输出截图
图17 不同规模地图填色运行时间比较
四.实验心得
本次实验中我探究了回溯算法,尝试利用回溯法证明地图填色问题,学习了解了几种剪枝算法和路径策略。实验过程中,我发现合适的优化策略可以大大提高算法效率,合适的数据结构也可以在一定程度上降低程序的运行时间。