概述
回溯法有”通用解题法”之美称,是一种比枚举”聪明”的效率更高的搜索技术。回溯在搜索过程中动态地产生问题的解空间,系统地搜索问题的所有解。与枚举相比,回溯法的”聪明”之处在于能适时”回头”,若再往前走不可能得到解,就回溯,退一步另找线路,这样可省去大量的无效操作。因此,回溯与枚举相比,回溯更适合量比较大,候选解比较多的案例求解。
回溯法是一种试探求解的方法:通过对问题的归纳分析,找出求解问题的一个线索,沿着这一线索往前试探,若试探成功,即得到解:若试探失败,就逐步往回退,换其他路线再往前试探。因此,回溯法可以形象地概括为"向前走,碰壁回头",这样可大大缩减无效操作,提高搜索效率。
相关例题
通过举例来学习和理解应该最有效的方法
1.皇后问题
在n*n的的方格棋盘上放置n个皇后,使她们互不攻击,即任意两个皇后不允许处在同一横排,同一纵列,也不允许处在同一与棋盘边框成45°角的斜线上
1.1递归回溯法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| import java.util.Scanner;
public class 皇后_回溯_递归回溯法 { static int s,sum=0; static int []ar; static int [][]arr; public static void main(String args[]){ Scanner in=new Scanner(System.in); s=in.nextInt(); ar=new int[s]; arr=new int [s][s]; for(int i=0;i<s;i++) for(int j=0;j<s;j++) arr[i][j]=0; put(0); System.out.println(sum); } public static void put(int n){ if(n==s){ for(int k=0;k<s;k++){ System.out.print(ar[k]+" "); } sum++; System.out.println(); return ; } for(int i=0;i<s;i++){ ar[n]=i; if(OkPut(n,i)) arr[n][i]=1; else continue; put(n+1); arr[n][i]=0; } return ; } public static boolean OkPut(int n,int i){ for(int t=n-1;t>=0;t--) if(arr[t][i]==1) return false; for(int w=n-1,s=i-1;w>=0&&s>=0;w--,s--) if(arr[w][s]==1) return false; for(int z=n-1,x=i+1;z>=0&&x<s;z--,x++) if(arr[z][x]==1) return false; return true; } }
|
1.2迭代回溯法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import java.util.Scanner; public class 皇后问题_回溯_迭代回溯法 { static int ar[]; static int sum=0,n; static Scanner in; static boolean b; public static void main(String args[]){ in=new Scanner(System.in); n=in.nextInt(); ar=new int[n]; hui(); System.out.println(sum); } public void hui(){ int i=0; ar[i]=0; while(true){ b=true; for(int h=i-1;h>=0;h--) if(ar[i]==ar[h]||Math.abs(ar[i]-ar[h])==i-h) b=false; if(b&&i==n-1){ sum++; for(int k=0;k<n;k++){ System.out.print(ar[k]+" "); } System.out.println(); } if(i<n-1&&b){ i++; ar[i]=0; continue; } while(ar[i]==n-1&&i>0){ i--; } if(ar[i]==n-1&&i==0) break; else ar[i]+=1; } } }
|
2.桥本分数式
将1,2,3,…,9这九个数字填入下式的9个方格中(数字不能重复),使下面的分数等式成立:
口/口口+口/口口=口/口口
2.1递归回溯法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class 桥本分数式_递归回溯法 { static int [] ar; public static void main(String args[]) { ar=new int [9]; put(0); } public static void put(int n){ if(n==9){ if(b(ar)&&ar[0]<ar[3]) pritf(ar); return ; } for(int i=1;i<10;i++){ if(is(n,i)) ar[n]=i; else continue; put(n+1); } return ; } public static boolean is(int n,int i){ for(int j=0;j<n;j++){ if(ar[j]==i) return false; } return true; } public static boolean b(int []a){ int a1=a[0]*(a[7]*10+a[8])*(a[4]*10+a[5]); int a2=a[3]*(a[1]*10+a[2])*(a[7]*10+a[8]); int a3=a[6]*(a[1]*10+a[2])*(a[4]*10+a[5]); if(a1+a2==a3) return true; return false; } public static void pritf(int []a){ for(int k=0;k<9;k++){ System.out.print(a[k]+" "); } System.out.println(); } }
|
2.2迭代回溯法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| public class 桥本分数式_迭代回溯法 { static int ar[],i,sum=0; static boolean b; public static void main(String args[]){ ar=new int [9]; hui(); System.out.println(sum); } public void hui(){ i=0;ar[i]=1; while(true){ b=true; for(int k=i-1;k>=0;k--){ if(ar[k]==ar[i]) {b=false; break;} } if(i==8&&b&&ar[0]<ar[3]&&b(ar)){ pritf(ar); sum++; } if(i<8&&b){ i++; ar[i]=1; continue; } while(ar[i]==9&&i>0) i--; if(ar[i]==9&&i==0) break; else ar[i]++; } } public static boolean b(int []a){ int a1=a[0]*(a[7]*10+a[8])*(a[4]*10+a[5]); int a2=a[3]*(a[1]*10+a[2])*(a[7]*10+a[8]); int a3=a[6]*(a[1]*10+a[2])*(a[4]*10+a[5]); if(a1+a2==a3) return true; return false; } public static void pritf(int []a){ for(int k=0;k<9;k++){ System.out.print(a[k]+" "); } System.out.println(); } }
|
总结
1.递归回溯法
递归设计思路容易,但效率较低。基本上是由一个函数实现,当满足条件则进入下一层。当层数达到条件时,进入判断,满足则找到一个解。
关键点在于通过不断调用函数put(int n)进入下一层
基本框架为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| int ar[]; void put(int n){ if(n==(规模)){ if((约束条件)){ printf((输出一个解)); } return ; } for(int k=0;k<(规模);k++){ if((约束条件)){ ar[n]=k; }else continue; put(n+1); } return ; }
|
2.迭代回溯法
相对于递归回溯法来说,迭代回溯法设计思路较为复杂,但效率比递归回溯法高。ps:两个算法的设计模式没有多大的区别,彻底理解递归回溯后,迭代回溯应该没什么问题。
基本框架:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| int i=1 ,ar[i]=(元素最初值); boolean b; while(true){ b=true; for(k=i-1;k>=1;k--) if((约束条件1)) b=false; if(b&&i==(规模)&&(约束条件2)) pritf(一个解); if(b&&i<(规模)){ i++; ar[i]=(元素最初值); continue; } while(a[i]==(回溯点)&&i>1) i--; if(ar[i]==(规模)&&i==1) break; else a[i]+=1; }
|
3.自己的想法
写到这里应该差不多了,如果能看懂这两个例题应该能基本掌握回溯的思想了(当然,想要真正掌握这些知识是完全不够的)。ps:其实是自己不想写了,累了!