回溯法
先祭上回溯法的知识树:
现在开始一个一个算法过一遍了。
1、N皇后问题(子集树问题的实例)
N后属于子集树问题,时间复杂度为O(nnn^nnn)
俗话说好的模板可以事半功倍,那么就先祭出回溯法子集树问题的模板:
void Backtrack(int n){
if(t>n)Output(x);
else{
for(int i=f(n,t);i<=g(n,t);i++){
x[t] = h[i];
if(Constraint(t)&&Bound(t))
Backtrack(t+1);
}
}
}
public static int backTrack(int t){
if (t >= N ){
sum += 1;
}
else{
//core code
for(int i=0; i<N; i++){
x[t] = i;
if(check(t,x[t])==true){
backTrack(t+1);
}
}
}
return sum;
}
public static boolean check(int row,int idx){
//对角线冲突或列冲突
for(int i = 0;i<row;i++){
if(x[i] == idx||abs(row-i)==abs(idx-x[i])){
return false;
}
}
return true;
}
public static int abs(int i) {
if(i>=0) {
return i;
}
else {
return -i;
}
}
在递归的方法求解N后问题中需要用到O(n)的递归栈空间
2、旅行售货员问题(排列树问题的实例)
TSP属于排列问题,时间复杂度为O(n!)
对于排列树问题常用的模板如下:
void Backtrack(int n){
if(t>n)Output(x);
else{
for(int i=t+1;i<=n;i++){
//与未选择的元素逐一交换位置进行验证
Swap(x[t],x[i]);
if(Constraint(t)&&Bound(t)){
Backtrack(t+1);
}
//恢复现场
Swap(x[t],x[i]);
}
}
}
排列树与子集树的差别就在于swap操作,因为为排列问题,解向量上面每一维度相应的数值都是不一样的。还有对于排列树问题解解向量x一开始需要初始化为1…n的数组。
package trackback;
import java.util.Arrays;
public class TSP {
public static int N;
public static int min = Integer.MIN_VALUE;//当两个城市之间没有边相连接的时候就将其记为min
public static int max = Integer.MAX_VALUE;
public static int[][] a;
public static int bestc = max;
public static int[] bestx;
//current solution
public static int[] x;
//current best cost
public static int cc = 0;
public static void backtrack(int t) {
if(t==N-1) {
//判断当前的解是否优于已找到的最优解
if((a[x[t-1]][x[t]] != min && a[x[t]][0] != min)) {
if(bestc>cc+a[x[t-1]][x[t]]+a[x[t]][0]) {
bestc = cc+a[x[t-1]][x[t]]+a[x[t]][0];
for(int i=0;i<x.length;i++) {
bestx[i] = x[i]+1;
}
}
}
}
else {
//core code
for(int i = t;i<N;i++) {//不能从t+1开始,因为swap(x[t],x[t])不进行交换本身也是一种需要考虑的情况
swap(x, t, i);
if(a[x[t-1]][x[t]]!=min&&cc+a[x[t-1]][x[t]]<bestc){
cc += a[x[t-1]][x[t]];
backtrack(t+1);
cc -= a[x[t-1]][x[t]];//记得递归结束之后全局的变量要恢复现场
}
swap(x, t, i);
}
}
}
//实现解空间中不中位置的分量之间的位置交换
public static boolean swap(int[]x, int a,int b) {
int tmp;
tmp = x[a];
x[a] = x[b];
x[b] = tmp;
return true;
}
public static void main(String[] args) {
N =4;
x = new int[N];
bestx = new int[N];
for(int i=0;i<N;i++)
x[i] = i;
a = new int[][]{{min,30,6,4},{30,min,5,10},{6,5,min,20},{4,10,20,min}};
backtrack(1);
System.out.println("The best path is:" + Arrays.toString(bestx));
System.out.println("The min cost is:" + bestc);
}
}
3、装载问题
4、图的m着色问题
5、最大团问题:
PS:最后由于分支限界法与回溯法基本类似,只不过一般采用BFS或函数优先的搜索方式再配合上一些高效的界函数进行剪枝提高了回溯法的效率,故不再专门开设一个系列来写分支限界法,这里就给出一张分支限界法专题的思维导图。