分支限界法求解TSP问题

本文介绍了分支限界法在旅行商问题(TSP)中的应用,重点讲解了如何利用优先队列进行广度优先搜索,通过节点的路径长度排序确保高效搜索。涉及的操作包括优先队列的offer、poll和isEmpty,以及如何通过限界函数和剪枝优化搜索过程。
摘要由CSDN通过智能技术生成
分支限界法

与回溯法一样,分支限界法也是在解空间中搜索问题的解。

二者区别在于回溯法采用深度优先搜索(利用栈),分支限界法采用广度优先搜索,利用队列/优先队列存储节点的数据类型。

广度优先遍历与队列的性质吻合,常见的分支搜索方式有队列式分支限界法和优先队列式分支限界法,本文采用优先队列式分支限界法。

TSP问题

       求解TSP问题,从起始点出发,经过n个不同不重复的点,最终回到起始点,求解最短路径。归类于图的问题,图需要用邻接矩阵存储节点及节点之间的关系。

在网络上找到一张排列树框架的图:

优先队列相关操作及问题

在TSP问题中使用到的优先队列相关操作有

  • offer(E e): 向队列添加一个元素并返回是否成功。
  • poll(): 移除并返回队列头部的元素(即按照优先级最高的元素),如果队列为空则返回 null。
  • isEmpty(): 如果队列为空,返回 true;否则返回 false。

为什么使用优先队列呢?

  • 存储 TSP 问题的搜索过程中的节点状态,即 QNode 对象。
  • 根据节点的路径长度进行排序,确保每次从队列中取出的都是当前已知路径长度最短的节点。
  • 允许算法按照广度优先的方式进行搜索,即按照路径长度的递增顺序探索节点。
  • 由于TSP问题的目的,优先顺序按照比较QNode节点的length大小排序,越小越靠近队头,也就越先出队。
细节:
  • 分支限界法采用广度优先遍历,一个节点搜索完毕立即出队,不会再次遍历
  • final关键字用于声明一个不可变的变量,final int INF=0x3f3f3f3f
  • 优先队列中的比较器问题:根据长度排序规则,返回值>0,排在后面晚出队,返回值<0,排在前面先出队
  • Arrays.fill 是 Java 中的一个静态方法,属于 java.util.Arrays 类。这个方法用于用指定的值填充整个数组或数组的一部分。这个方法非常实用,特别是在初始化数组时,需要将所有元素设置为一个特定的值。例:填充整个数组:
    int[] array = new int[5]; Arrays.fill(array, 1); 
    // 将数组的所有元素设置为1
  • continue 语句是用于跳出当前 for 循环的当前迭代,类似于break(我自己类比的);直接结束当前环节的迭代,跳到下一层迭代。

  • 创建的最短路径数组bestd在bfs函数中,是多个相同vno比较,最终bestd为此编号最小数组,在最后的tsp函数中,还需与其他编号进行比较。

  • bfs中广度优先遍历的出口是到达叶子结点,未到达叶子结点之前,需要不断的将新节点入队。

  • 优先队列的操作比如加入新元素,删除对头元素,判空。

  • 一个节点的状态可以用【节点层次,节点编号,起点到当前节点长度】表示。

  • used整型在TSP问题中多次运用,调用辅助函数检查是否访问跳过和使其已访问,都需要used整型,判重以及放入。

  • 辅助函数运用位运算(逻辑位运算符和位移运算符),例:1 | (1 << 1):这是一个位运算表达式,其中 | 是按位或运算符,<< 是左移运算符,1 << 1:将数字 1 左移一位,结果是 2。在二进制中,1 表示为 0001,左移一位后变为 0010,即十进制的 2,1 | 2:执行按位或运算,将数字 1(二进制 0001)和数字 2(二进制 0010)进行按位或操作。在二进制中,两个数的按位或操作规则是:只有当两个相应的位都是 0 时,结果位才是 0,否则结果位是 1。因此,0001 | 0010 的结果是 0011,即十进制的 3。

思路:
  • 定义数据结构:QNode类用来表示优先队列中的节点,包含当前节点的层次、编号、已访问顶点的位集合、以及从起点到当前节点的路径长度以上属性。
  • 辅助函数:分为两类,一类用于检查集合中是否包含特定节点(判重跳过),另一类将特定节点添加到位结集合中(添加已访问节点),这两类函数都需用自定义一个整型数据类型。
  • 广度优先搜索(bfs):首先使用优先队列按自定的排序方式排序节点;其次从起点开始,将起点加入队列,最后当队列非空时,重复以下步骤(出队长度最短节点,遍历所有未访问过的顶点,为其创建新节点并将其加入队列,若访问到叶节点,更新最短路径数组,否则,将新节点入队)
  • 优化:采用限界函数,通过比较当前节点的路径长度与bestd数组中对应顶点的值来判断是否有希望找到更短的路径;还可以采用剪枝,在将新节点e1加入队列之前,通过检查e1.lengthbestd[e1.vno]的值来剪枝,避免了对不可能产生更短路径的节点的进一步搜索。此外,优先队列本身已是一种优化,每次都是处理当前已知最短路径的节点。
部分代码:

//广度优先遍历出口(未采用限界函数)

if(h.i==n-1)            
    bestd[h.no]=Math.min(bestd[h.no],h.length);
if(h.i<n-1) {           
    if(h.length<bestd[h.no]) { 
        yxdl.offer(h);           
        System.out.printf("进队\n");
    }
}

//比较最终最短路径大小

 int ans=INF; 
    for(int i=0;i<n;i++) { 
      if(i!=s) {
        if(bestd[i]+A[i][s]<ans) 
          ans=bestd[i]+A[i][s]; 
      }
    }

  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
支限界法类又称为剪枝限界法或分支定界法,它类似于回溯法,也是一种在问题的解空间树T上搜索问题解的算法。它与回溯法有两点不同:①回溯法只通过约束条件剪去非可行解,而分支限界法不仅通过约束条件,而且通过目标函数的限界来减少无效搜索,也就是剪掉了某些不包含最优解的可行解。②在解空间树上的搜索方式也不相同。回溯法以深度优先的方式搜索解空间树,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树。分支限界法的搜索策略是:在扩展结点处,先生成其所有的儿子结点(分支),然后再从当前的活结点表中选择下一个扩展结点。为了有效地选择下一扩展结点,以加速搜索的进程, 在每一活结点处,计算一个函数值(限界),并根据这些已计算出的函数值,从当前活结点表中选择一个最有利的结点作为扩展结点,使搜索朝着解空间树上有最优解的分支推进,以便尽快地找出一个最优解。 从活结点表中选择下一扩展结点的不同方式导致不同的分支限界法。最常见的有以下两种方式: ①队列式(FIFO)分支限界法:队列式分支限界法将活结点表组织成一个队列,并按队列的先进先出原则选取下一个结点为当前扩展结点。 ②优先队列式分支限界法:优先队列式分支限界法将活结点表按照某个估值函数C(x)的值组织成一个优先队列,并按优先队列中规定的结点优先级选取优先级最高的下一个结点成为当前扩展结点。 影响分支限界法搜索效率的有两个主要因素:一是优先队列Q的优先级由C(x)确定,它能否保证在尽可能早的情况下找到最优解,如果一开始找到的就是最优解,那么搜索的空间就能降低到最小。二是限界函数u(x),它越严格就越可能多地剪去分支,从而减少搜索空间。 在用分支限界法解决TSP问题时,有不少很好的限界函数和估值函数已经构造出来出了(限于篇幅,这里不做详细介绍), 使得分支限界法在大多数情况下的搜索效率大大高于回溯法。但是,在最坏情况下,该算法的时间复杂度仍然是O(n!),而且有可能所有的(n-1)!个结点都要存储在队列中。 近似算法是指不能肯定找到最优解的算法,但通常找到的也是比较好的解,或称近似最优解。[20]一般而言,近似算法的时间复杂度较低,通常都是多项式时间内的。由于近似算法的时间效率高,所以在实际应用中,主要是使用近似算法,这一类算法也一直是研究的主要对象。传统的近似算法以采用贪心策略和局部搜索为主,而几十年来,随着以遗传算法为代表的新型启发式搜索算法的逐步完善,在解决TSP问题上获得了巨大的成功。遗传算法、模拟退火算法、蚁群算法等已经成为公认的好算法。在本节中,将介绍传统的近似算法

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值