回溯法
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
回溯法的基本思想
在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。
若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。
而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。
回溯法解题的一般步骤:
针对所给问题,确定问题的解空间:
首先应明确定义问题的解空间,问题的解空间应至少包含问题的一个(最优)解。
- 确定结点的扩展搜索规则
- 以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
回溯法的经典示例
示例一:n皇后问题
问题描述:对于一个n方阵的棋盘,使n个皇后在棋盘上满足如下条件:同一行只能有一个皇后,同一列只能有一个皇后,同一个对角线只能有一个皇后,问共有多少种方法方法?
//用来判断第k个象棋的位置是否符合要求
public boolean place(int[] x, int k) {
for(int i= 0; i < k; i++) {
if(x[k] == x[i] || Math.abs(k - i)==Math.abs(x[k] - x[i])) {
return false;
}
}
return true;
}
//返回所有的结果计数
public int queue(int n) {
int count = 0;
int[] x = new int[n];
for(int i = 0; i < n; i++) {
x[i] = -1;
}
int k = 0;
while(k >= 0) {
x[k] = x[k] + 1;
while(x[k] < n && !place(x, k))
x[k] += 1;
if(x[k] < n && k == n-1) {
count++;
}else if(x[k] < n && k < n-1) {
k = k + 1;
}else {
x[k] = -1;
k = k - 1;
}
}
return count;
}
分支限界法
类似于回溯法,也是一种在问题的解空间树T上搜索问题解的算法。但在一般情况下,分支限界法与回溯法的求解目标不同。回溯法的求解目标是找出T中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。
基本思想
所谓“分支”就是采用广度优先的策略,依次搜索E-结点的所有分支,也就是所有相邻结点,抛弃不满足约束条件的结点,其余结点加入活结点表。然后从表中选择一个结点作为下一个E-结点,继续搜索。
分支限界法的一般过程
回溯法以深度优先的方式搜索解空间树T,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树T。
分支限界法的搜索策略是:在扩展结点处,先生成其所有的儿子结点(分支),然后再从当前的活结点表中选择下一个扩展对点。为了有效地选择下一扩展结点,以加速搜索的进程,在每一活结点处,计算一个函数值(限界),并根据这些已计算出的函数值,从当前活结点表中选择一个最有利的结点作为扩展结点,使搜索朝着解空间树上有最优解的分支推进,以便尽快地找出一个最优解。
分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。问题的解空间树是表示问题解空间的一棵有序树,常见的有子集树和排列树。在搜索问题的解空间树时,分支限界法与回溯法对当前扩展结点所使用的扩展方式不同。在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,那些导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被子加入活结点表中。此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所求的解或活结点表为空时为止。
分支限界法的经典示例
示例一:单源最短路径问题。
import java.util.PriorityQueue;
public class BShortest {
static class HeapNode implements Comparable {
int i;
int length;
HeapNode(int i, int length) {
this.i = i;
this.length = length;
}
@Override
public int compareTo(Object o) {
int x = ((HeapNode)o).length;
return length > x? 1: length==x?0:-1;
}
}
static int[][]a; //图的邻接矩阵
public static void shrotest(int v, int[] dist, int[] p) {
int n = p.length -1;
PriorityQueue<HeapNode> heap = new PriorityQueue<>();
HeapNode enode = new HeapNode(v, 0);
for(int j = 1; j <= n; j++ ) {
dist[j] = Integer.MAX_VALUE;
}
dist[v] = 0;
while(true) {
for(int j = 1; j <= n; j++)
if(a[enode.i][j] < Integer.MAX_VALUE && enode.length + a[enode.i][j] < dist[j]) {
dist[j] = enode.length + a[enode.i][j];
p[j] = enode.i;
HeapNode node = new HeapNode(j, dist[j]);
heap.add(node);
}
if(heap.isEmpty())
break;
else
enode = heap.poll();
}
}
}
这个代码看了别人的,以后再改善下,写不下去了。。
总结:回溯法主要使用的是深度优先搜索,通常会借用栈结构;分支限界法主要使用的广度优先搜索,常常借助于队列的接口。学习这两个方法主要看你对图的DFS和BFS的掌握情况了。。