分支限界法
与回溯法一样,分支限界法也是在解空间中搜索问题的解。
二者区别在于回溯法采用深度优先搜索(利用栈),分支限界法采用广度优先搜索,利用队列/优先队列存储节点的数据类型。
广度优先遍历与队列的性质吻合,常见的分支搜索方式有队列式分支限界法和优先队列式分支限界法,本文采用优先队列式分支限界法。
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.length
与bestd[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];
}
}