趣学算法系列-分支限界法
趣学算法系列-分支限界法
声明:本系列为趣学算法一书学习总结内容,在此推荐大家看这本算法书籍作为算法入门,
原作者博客链接,本书暂无免费电子版资源,请大家支持正版
第六章 分支限界法
在树搜索法中,从上到下为纵,从左向右为横,纵向搜索是深度优先,而横向搜索是广
度优先。前面讲的回溯法就是一种深度优先的算法。分支限界法就是以广(宽)度优先的树搜索方法
- 分支限界法的算法思想
*从根开始,常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。
首先将根结点加入活结点表(用于存放活结点的数据结构)
*接着从活结点表中取出根结点,使其成为当前扩展结点,一次性生成其所有孩子结点,判断孩子结点是舍 弃还是保留,舍弃那些导致不可行解或导致非最优解的孩子结点,其余的被保留在活结点表中。
*再从活结点表中取出一个活结点作为当前扩展结点,重复上述扩展过程,直到找到所需的解或活结点表为
空时为止。由此可见,每一个活结点最多只有一次机会成为扩展结点。
*活结点表的实现通常有两种形式:一是普通的队列,即先进先出队列;一种是优先级队
列,按照某种优先级决定哪个结点为当前扩展结点
- 分支限界法算法步骤
(1)定义解空间 确定解空间包括解的组织形式和显约束(范围限定)
(2)确定解空间的组织结构 通常用解空间树形象的表达(只是辅助理解并不是真的树)
(3)搜索解空间 按照广度优先搜索,根据限制条件,搜索问题的解
- 分支限界法解题秘籍
(1)定义解空间 确定解空间包括解的组织形式和显约束(范围限定)
(2)确定解空间的组织结构 通常用解空间树形象的表达(只是辅助理解并不是真的树)
(3)搜索解空间 按照深度优先搜索,根据限制条件,搜索问题的解
- 回溯法的典型应用
树的广度优先遍历
旅行商问题
实际案例分析-旅行商问题
- 问题描述
终于有一个盼望已久的假期!立马拿出地图,标出最想去的 n 个
景点,以及两个景点之间的距离 dij,为了节省时间,我们希望在最短
的时间内看遍所有的景点,而且同一个景点只经过一次。怎么计划行
程,才能在最短的时间内不重复地旅游完所有景点回到家呢?
问题分析
现在我们从 1 号景点出发, 游览其他 3 个景点, 先给景点编号 1~4,每个景点用一个结点表示,可以直接到达的景点有连线,连线上的数字代表两个景点之间的路程(时间)。那么要去的景点地图就转化成了一个无向带权图,如图 6-34 所示。
在无向带权图 G=( V, E)中,结点代表景点,连线上的数字代表景点之间的路径长度算法设计
(1)定义问题的解空间
奇妙之旅问题解的形式为 n 元组: {x1, x2,…, xi,…, xn},分量 xi 表示第 i 个要去的旅
游景点编号,景点的集合为 S={1, 2,…, n}。景点不可重复走,xi 的取值为 S−{x1, x2,…, xi−1}, i=1, 2,…, n。
(2)解空间的组织结构
问题解空间是一棵排列树,树的深度为n=4,如图 6-35 所示。
(3)搜索解空间
约束条件
用二维数组 g[][]存储无向带权图的邻接矩阵,如果 g[i][j]≠∞表示城市 i 和城市 j 有边相连,能走通
限界条件
clbestl, cl 的初始值为 0, bestl 始值为+∞。
cl:当前已走过的城市所用的路径长度。
bestl:表示当前找到的最短路径的路径长度。
搜索过程
如果采用普通队列(先进先出)式的分支限界法,那么除了最后一层外,所有的结点都
会生成,这显然不是我们想要的
可以使用优先队列式分支限界法,加速算法的搜索速度
设置优先级:当前已走过的城市所用的路径长度 cl。 cl 越小,优先级越高完美图解
(1)数据结构
设置地图的带权邻接矩阵为 g[][],即如果从顶点 i 到顶点 j 有边,就让 g[i][j]等于i, j>
的权值,否则 g[i][j]=∞(无穷大),如图 6-37 所示。
(2)初始化
当前已走过的路径长度 cl=0,当前最优值 bestl=∞。初始化解向量 x[i]和最优解 bestx[i],
如图 6-38 和图 6-39 所示。
(3)创建 A 节点
A0 作为初始结点,因为我们是从 1 号结点出发,因此 x[1]=1, 生成 A 结点。 创建 A 结点 Node( cl, id),cl=0, id=2; cl 表示当前已走过的城市所用的路径长度,id 表示层号;解向量 x[]=( 1, 2, 3, 4), A 加入优先队列 q 中,如图 6-40 所示。
(此处的优先队列不是普通的队列,是队列和堆结构实现的)优先队列中按自定义的优先规则排序
具体查看[优先队列和堆]http://blog.csdn.net/ahafg/article/details/60581286
(4)扩展 A 结点
队头元素 A 出队,一次性生成 A 结点的所有孩子,用 t 记录 A 结点的 id, t=2。
搜索 A 结点的所有分支, for(j=t; j=n; j++)。对每一个 j,判断 x[t−1]结点和 x[j]结点是否有
边相连,且 cl+g[x[t−1]][[x[j]]bestl,即判定是否满足约束条件和限界条件。如果满足则生成新
结点 Node(cl, id),新结点的 cl=cl+g[x[t−1]][[x[j]],新结点的 id=t+1,复制父结点 A 的解向量,
并执行交换操作 swap(x[t], x[j]),刚生成的新结点加入优先队列;如果不满足,则舍弃。
• j=2:因为 x[1]结点和 x[2]结点有边相连,且 cl+g[1][2]=0+15=15bestl=∞,满足约束
条件和限界条件,生成 B 结点 Node( 15, 3)。复制父结点 A 的解向量 x[]=( 1, 2,
3, 4),并执行交换操作 swap( x[t], x[j]),即 x[2]和 x[2]交换,解向量 x[]=( 1, 2,
3, 4)。 B 加入优先队列。
• j=3:因为 x[1]结点和 x[3]结点有边相连,且 cl+g[1][3]=0+30=30bestl=∞,满足约束
条件和限界条件,生成 C 结点 Node( 30, 3)。复制父结点 A 的解向量 x[]=( 1, 2,
3, 4),并执行交换操作 swap( x[t], x[j]),即 x[2]和 x[3]交换,解向量 x[]=( 1, 3,
2, 4)。 C 加入优先队列。
• j=4:因为 x[1]结点和 x[4]结点有边相连,且 cl+g[1][4]=0+5=5bestl=∞,满足约束条
件和限界条件,生成 D 结点 Node( 8, 3)。复制父结点 A 的解向量 x[]=( 1, 2, 3,
4),并执行交换操作 swap( x[t], x[j]),即 x[2]和 x[4]交换,解向量 x[]=( 1, 4, 3,
2)。 D 加入优先队列
后续过程略
伪代码详解
(1)定义结点结构体
struct Node //定义结点,记录当前结点的解信息 { double cl; //当前已走过的路径长度 int id; //景点序号 int x[N]; //记录当前路径 Node() {} Node(double _cl,int _id) { cl = _cl; id = _id; } };
(2)优先队列式分支限界法搜索函数
//Travelingbfs 为优先队列式分支限界法搜索 double Travelingbfs() { int t; //当前处理的景点序号 t Node livenode,newnode; //定义当前扩展结点 livenode,生成新结点 newnode priority_queue<Node> q; //创建优先队列,优先级为已经走过的路径长度cl, cl 值越小,越优先 newnode=Node(0,2); //创建根节点 for(int i=1;i<=n;i++) { newnode.x[i]=i; //初时化根结点的解向量 } q.push(newnode); //根结点加入优先队列 while(!q.empty()) { livenode=q.top(); //取出队头元素作为当前扩展结点 livenode q.pop(); //队头元素出队 t=livenode.id; //当前处理的景点序号 // 搜到倒数第 2 个结点时个景点的时候不需要往下搜索 if(t==n) //立即判断是否更新最优解, //例如当前找到一个路径(1243),到达 4 号结点时,立即判断 g[4][3]和 g[3][1]是 否有边相连,如果有边则判断当前路径长度 cl+g[4][3]+g[3][1]<bestl,满足则更新最优值和最优解 { //说明找到了一条更好的路径,记录相关信息 if(g[livenode.x[n-1]][livenode.x[n]]!=INF&&g[livenode.x[n]][1]!=INF) if(livenode.cl+g[livenode.x[n-1]][livenode.x[n]]+g[livenode .x[n]][1]<bestl) { bestl=livenode.cl+g[livenode.x[n-1]][livenode.x[n]]+g[l ivenode.x[n]][1]; for(int i=1;i<=n;i++) { bestx[i]=livenode.x[i];//记录最优解 } } continue; } //判断当前结点是否满足限界条件,如果不满足不再扩展 if(livenode.cl>=bestl) continue; //扩展 //没有到达叶子结点 for(int j=t; j<=n; j++)//搜索扩展结点的所有分支 { if(g[livenode.x[t-1]][livenode.x[j]]!=INF)//如果 x[t-1]景点与 x[j] 景点有边相连 { double cl=livenode.cl+g[livenode.x[t-1]][livenode.x[j]]; if(cl<bestl)//有可能得到更短的路线 { newnode=Node(cl,t+1); for(int i=1;i<=n;i++) { newnode.x[i]=livenode.x[i];//复制以前的解向量 } swap(newnode.x[t], newnode.x[j]);//交换 x[t]、 x[j]两个 元素的值 q.push(newnode);//新结点入队 } } } } return bestl; //返回最优值 }
- 实战演练
//program 6-2#include <iostream>#include <algorithm>#include <cstring>#include <cmath>#include <queue>using namespace std; const int INF=1e7; //设置无穷大的值为 10^7 const int N=100; double g[N][N]; //景点地图邻接矩阵 int bestx[N]; //记录当前最优路径 double bestl; //当前最优路径长度 int n,m; //景点个数 n,边数 m struct Node //定义结点,记录当前结点的解信息 { double cl; //当前已走过的路径长度 int id; //景点序号 int x[N]; //记录当前路径 Node() {} Node(double _cl,int _id) { cl = _cl; id = _id; } }; //定义队列的优先级。以 cl 为优先级, cl 值越小,越优先 bool operator <(const Node &a, const Node &b) { return a.cl>b.cl; } //Travelingbfs 为优先队列式分支限界法搜索 double Travelingbfs() { int t; //当前处理的景点序号 t Node livenode,newnode; //定义当前扩展结点 livenode,生成新结点 newnode priority_queue<Node> q; //创建一个优先队列,优先级为已经走过的路径长度 cl, cl 值 越小,越优先 newnode=Node(0,2); //创建根节点 for(int i=1;i<=n;i++) { newnode.x[i]=i; //初时化根结点的解向量 } q.push(newnode); //根结点加入优先队列 while(!q.empty()) { livenode=q.top(); //取出队头元素作为当前扩展结点 livenode q.pop(); //队头元素出队 t=livenode.id; //当前处理的景点序号 // 搜到倒数第 2 个结点时个景点的时候不需要往下搜索 if(t==n) //立即判断是否更新最优解 //例如当前找到一个路径(1243),到达 4 号结点时,立即判断 g[4][3]和 g[3][1]是 否有边相连,如果有边则判断当前路径长度 cl+g[4][3]+g[3][1]<bestl,满足则更新最优值和最优解 { //说明找到了一条更好的路径,记录相关信息 if(g[livenode.x[n-1]][livenode.x[n]]!=INF&&g[livenode.x[n]][1]!=INF) if(livenode.cl+g[livenode.x[n-1]][livenode.x[n]]+g[livenod e.x[n]][1]<bestl) { bestl=livenode.cl+g[livenode.x[n-1]][livenode.x[n]]+g[l ivenode.x[n]][1]; cout<<endl; //记录当前最优的解向量 for(int i=1;i<=n;i++) { bestx[i]=livenode.x[i]; } } continue; } //判断当前结点是否满足限界条件,如果不满足不再扩展 if(livenode.cl>=bestl) continue; //扩展 //没有到达叶子结点 for(int j=t; j<=n; j++)//搜索扩展结点的所有分支 { if(g[livenode.x[t-1]][livenode.x[j]]!=INF)//如果 x[t-1]景点与 x[j] 景点有边相连 { double cl=livenode.cl+g[livenode.x[t-1]][livenode.x[j]]; if(cl<bestl)//有可能得到更短的路线 { newnode=Node(cl,t+1); for(int i=1;i<=n;i++) { newnode.x[i]=livenode.x[i];//复制以前的解向量 } swap(newnode.x[t], newnode.x[j]);//交换 x[t]、 x[j]两个 元素的值 q.push(newnode);//新结点入队 } } } } return bestl;//返回最优值 } void init()//初始化 { bestl=INF; for(int i=0; i<=n; i++) { bestx[i]=0; } for(int i=1;i<=n;i++) for(int j=i;j<=n;j++) g[i][j]=g[j][i]=INF;//表示路径不可达 } void print()//打印路径 { cout<<endl; cout<<"最短路径: "; for(int i=1;i<=n;i++) cout<<bestx[i]<<"--->"; cout<<"1"<<endl; cout<<"最短路径长度: "<<bestl; } int main() { int u, v, w;//u,v 代表城市, w 代表 u 和 v 城市之间路的长度 cout << "请输入景点数 n(结点数): "; cin >> n; init(); cout << "请输入景点之间的连线数(边数): "; cin >> m; cout << "请依次输入两个景点 u 和 v 之间的距离 w,格式:景点 u 景点 v 距离 w: "<<endl; for(int i=1;i<=m;i++) { cin>>u>>v>>w; g[u][v]=g[v][u]=w; } Travelingbfs(); print(); return 0; }
- 算法时间复杂度分析
(1)时间复杂度:最坏情况下,如图 6-46 所示。时间复杂度为 O(n!)。
(2)空间复杂度:程序中我们设置了每个结点都要记录当前的解向量 x[]数组,占用空间为 O(n),结点的
个数最坏为 O(n!),所以该算法的空间复杂度为 O(n*n!)。
趣学算法系列-分支限界法相关推荐
- 趣学算法系列-回溯法
趣学算法系列-回溯法 声明:本系列为趣学算法一书学习总结内容,在此推荐大家看这本算法书籍作为算法入门, 原作者博客链接,本书暂无免费电子版资源,请大家支持正版 第五章 回溯法 回溯法是一种选优搜索法, ...
- 趣学算法系列-动态规划
趣学算法系列-动态规划 声明:本系列为趣学算法一书学习总结内容,在此推荐大家看这本算法书籍作为算法入门, 原作者博客链接,本书暂无免费电子版资源,请大家支持正版,更多实例分析请查看原书内容 第四章 动 ...
- 趣学算法系列-分治法
趣学算法系列-分治法 声明:本系列为趣学算法一书学习总结内容,在此推荐大家看这本算法书籍作为算法入门, 原作者博客链接,本书暂无免费电子版资源,请大家支持正版,更多实例分析请查看原书内容 第三章 分治 ...
- 趣学算法系列-贪心算法
趣学算法系列-贪心算法 声明:本系列为趣学算法一书学习总结内容,在此推荐大家看这本算法书籍作为算法入门, 原作者博客链接,本书暂无免费电子版资源,请大家支持正版,更多的案例分析请查看原书内容. 第二章 ...
- 《趣学算法(第2版)》读书笔记 Part 1 :如何高效学习算法
14天阅读挑战赛 系列笔记链接 <趣学算法(第2版)>读书笔记 Part 1 :如何高效学习算法 <趣学算法(第2版)>读书笔记 Part 2 :算法入门 <趣学算法(第 ...
- 《趣学算法(第2版)》读书笔记 Part 4 :贪心算法基础(操作)
14天阅读挑战赛 系列笔记链接 <趣学算法(第2版)>读书笔记 Part 1 :如何高效学习算法 <趣学算法(第2版)>读书笔记 Part 2 :算法入门 <趣学算法(第 ...
- 序列复杂度怎么看_《趣学算法》作者又一力作上架,再分享您一篇算法复杂度...
不知道读者们有没有看过陈小玉的<趣学算法>这本书,该书在出版后受到广大读者一致好评,在一年内重印了10次,并输出了繁体版的版权.不知道读过这本书的朋友们感觉第一本怎么样?欢迎留言给我们.接 ...
- 信号处理趣学D0——系列专栏的说明与目录
信号处理趣学专栏是小虎同学在学习测试技术的时候做的一系列笔记和心得的集合,定位是利用仿真软件(主要是MATLAB)带大家领略信号处理的一些基本概念. 目录 信号处理趣学D0--系列专栏的说明与目录 信 ...
- 【趣学算法】一棋盘的麦子
14天阅读挑战赛 努力是为了不平庸~ 算法学习有些时候是枯燥的,这一次,让我们先人一步,趣学算法! 案例背景 有一个古老的传说,一位国王的女儿不幸落水,水中有很多鳄鱼,国王情急之下下令:"谁 ...
最新文章
- 在集群的操作机上执行命令为什么会出现权限被拒绝_如何使用 TDengine 2.0 最新开源的集群功能?
- http 412 precondition failed
- python实现多表格合并_python 如何把两个表格数据,合并为一个呢?
- vue过滤器的那点事
- 腾讯QQ看点信息流推荐业务:内容分发场景的多目标架构实践
- win10关机后自动重启_win10电脑关机后自动开机怎么解决
- 客户端向hdfs读写数据流程
- 运用ffmpeg生成MP4文件
- 翻译: 3.3. 线性回归的简明实现 pytorch
- linux怎么打开隐藏文件夹,如何在文件管理器中隐藏文件和文件夹
- Redis RDB和AOF
- S7-1200PLC 与温控仪表Modbus RTU通信
- Linux常用的设置文件安全上下文命令使用方法
- php 购物车 原理及实现,纯干货丨PHP实现购物车的构建
- 华为云等保解决方案,全流程等保服务,帮助企业守护信息安全
- ZYNQ之路--初级开发流程介绍
- 关于Linux性能调优之内存负载调优
- 【Java学习笔记】1:Java语言基础
- linux磁盘加密bitlocker,亲身体验Windows 2008 Server R2下的BitLocker驱动器加密
- Android 9-patch 九图的制作与使用
热门文章
- 普通主机装服务器系统安装,普通主机安装服务器系统安装
- Keepalive高可用 漂移
- React-ajax-解决异步小技巧-异步下beforeSend小用法
- java练习:编写工资系统,实现不同类型员工(多态)的按月发放工资。如果当月出现某个Employee对象的生日,则将该雇员的工资增加100元。
- 七夕王者荣耀服务器维护,七夕王者荣耀有什么福利 本文告诉你
- C# SharpZipLib 压缩文件夹包括自身
- python数据分析实战案例logistic_从零开始学Python【27】--Logistic回归(实战部分)...
- 关于数据库的卸载及服务残留问题
- ktl工具实现mysql向mysql同步数据方法
- C/C++基础题056.菱形