回溯法及舉例分析

來源:互聯網
上載者:User

回溯法,按照百度百科的介紹,是指一種選優的搜尋法,按照選優條件向前搜尋,以達到目標,但當搜尋到某一步時發現原先的選擇並不優或者不能達到目標,則退回上一步重新選擇,這種走不通就退回重新選擇再走的方法就是回溯法。

可用回溯法求解的問題P一般可以被如下描述:對於已知的由n元組(x1,x2,…,xn)組成的一個狀態空間E={(x1,x2,…,xn)∣xi∈Si ,i=1,2,…,n},給定關於n元組中的一個分量的一個約束集D,要求E中滿足D的全部約束條件的所有n元組。其中Si是分量xi的定義域,且 |Si| 有限,i=1,2,…,n。我們稱E中滿足D的全部約束條件的任一n元組為問題P的一個解。

對於許多問題,所給定的約束集D具有完備性,即i元組(x1,x2,…,xi)滿足D中僅涉及到x1,x2,…,xi的所有約束意味著j(j<=i)元組(x1,x2,…,xj)一定也滿足D中僅涉及到x1,x2,…,xj的所有約束,i=1,2,…,n。換句話說,只要存在0≤j≤n-1,使得(x1,x2,…,xj)違反D中僅涉及到x1,x2,…,xj的約束之一,則以(x1,x2,…,xj)為首碼的任何n元組(x1,x2,…,xj,xj+1,…,xn)一定也違反D中僅涉及到x1,x2,…,xi的一個約束,n≥i≥j。因此,對於約束集D具有完備性的問題P,一旦檢測斷定某個j元組(x1,x2,…,xj)違反D中僅涉及x1,x2,…,xj的一個約束,就可以肯定,以(x1,x2,…,xj)為首碼的任何n元組(x1,x2,…,xj,xj+1,…,xn)都不會是問題P的解,因而就不必去搜尋它們、檢測它們。回溯法正是針對這類問題,利用這類問題的上述性質而提出來的比枚舉法效率更高的演算法。

使用回溯法的一般步驟:

(1)針對特定問題,確定問題的解空間

(2)確定易於搜尋的解空間結構

(3)以深度優先方式搜尋解空間,並在搜尋過程中用減枝函數避免無效搜尋。

下面我們講述幾個使用回溯法的例子。

首先我們先看一個關於象棋的問題,假設我們在一個4*4的棋盤上有一個馬,馬的位置為(1,1)點,當然也可以是其它點,我們要求列出所有的從(1,1)出發,最終能夠回到起點的所有走法。

下面我們給出實現

package com.application.sample;//使用回溯思想public class backtrackingSample1 {static int[][] direction = {{-1, -1, -2, -2, 2, 2, 1, 1},{-2, 2, 1, -1, 1, -1, 2, -2}};private int[][] chess;private int x,y;private int total=0;public backtrackingSample1(int x,int y){this.x=x;this.y=y;this.chess=new int[4][4];chess[x][y]=1;}private void output(){//if(isOneStep())//{//System.out.println("Total:"+this.total);//for(int i=0;i<4;i++)//{//for(int j=0;j<4;j++)//{//System.out.print(chess[i][j]+" ");//}//System.out.println();//}//System.out.println();//}System.out.println("Total:"+this.total);for(int i=0;i<4;i++){for(int j=0;j<4;j++){System.out.print(chess[i][j]+" ");}System.out.println();}System.out.println();}public void findway(int posx,int posy,int step){int px,py;for(int i=0;i<8;i++){px=posx+direction[0][i];py=posy+direction[1][i];if(px>=0&&px<=3&&py>=0&&py<=3){if(px==this.x&&py==this.y){this.total++;this.output();}else if(chess[px][py]==0){chess[px][py]=step;findway(px,py,step+1);chess[px][py]=0;}}}}public boolean isOneStep(){boolean result=true;for(int i=0;i<4;i++){for(int j=0;j<4;j++){if(chess[i][j]>2){return false;}}}return result;}public static void main(String[] args) {// TODO Auto-generated method stubbacktrackingSample1 sample=new backtrackingSample1(1,1);long start=System.currentTimeMillis();sample.findway(1, 1, 2);long end=System.currentTimeMillis();System.out.println("用時:"+(end-start)+"毫秒");}}

運行結果如下所示:

Total:10 0 5 2 4 1 0 0 0 0 3 6 0 0 0 0

Total:29 12 5 2 4 1 8 11 13 10 3 6 0 7 14 0

........

Total:3690 6 0 0 4 1 10 7 11 8 5 2 0 3 12 9 Total:3700 0 0 0 4 1 0 0 0 0 5 2 6 3 0 0 用時:78毫秒

 

接下來我們看另外一個經典的問題九宮格。九宮格就是一個9*9的網格,而這個網格又可以被分成9個3*3的子網格,向網格中填入1~9的數字,要求每行,每列以及每個子網格中1-9都只出現一次,即不能有重複數字出現。如下所示,0表示待填入資料的格子

103000509
002109400
000704000
300502006
060000050
700803004
000401000
009205800
804000107

我們將9個子網格的編排如下所示:

0 1 2
3 4 5
6 7 8

對於用於標識元素在大的9*9網格中位置的變數i和j,有0<=i,j<=8,為了擷取(i,j)元素在哪個小子網格中,我們做如下處理

i i/3 j j/3
0 0 0 0
1 0 1 0
2 0 2 0
3 1 3 1
4 1 4 1
5 1 5 1
6 2 6 2
7 2 7 2
8 2 8 2

假設k為小的3*3子網格的序號,則我們根據下面的關係可以得到(i,j)與k的關係

i/3 j/3 k
0 0 0
0 1 1
0 2 2
1 0 3
1 1 4
1 2 5
2 0 6
2 1 7
2 2 8

我們可以容易得到k=(i/3)*3+(j/3);

下面給出java的實現:

package com.application.sample;//使用回溯法求解九宮格問題import java.util.Scanner;public class BackTrackingSample2 {private int[][] table;private boolean[][] rstate;//rstate[i][value]=true表示value在第i行出現過private boolean[][] cstate;//cstate[j][value]=true表示value在第j行出現過private boolean[][] gstate;//gstate[k][value]=true表示value在第k個小網格中出現過private int[] pos;//存放需要填數位方格位置private int posnum=0;//需要填的格子總數private boolean flag=false;//搜尋結束標誌public static void main(String[] args) {// TODO Auto-generated method stubBackTrackingSample2 sample=new BackTrackingSample2();sample.searchSolution(1);}public BackTrackingSample2(){table=new int[9][9];rstate=new boolean[9][10];cstate=new boolean[9][10];gstate=new boolean[9][10];pos=new int[81];System.out.println("輸入九宮格的資料(使用0表示待填入資料的格子):");Scanner in=new Scanner(System.in);String array=null;for(int i=0;i<9;i++){array=in.nextLine();for(int j=0;j<9;j++){table[i][j]=array.charAt(j)-'0';if(table[i][j]!=0){rstate[i][table[i][j]]=true;cstate[j][table[i][j]]=true;int k=(i/3)*3+(j/3);gstate[k][table[i][j]]=true;}else{pos[posnum++]=i*9+j;}}}pos[posnum]=-1;}private void output()//輸出結果{System.out.println();System.out.println("結果:");for(int i=0;i<9;i++){for(int j=0;j<9;j++){System.out.printf("%2d", table[i][j]);}System.out.println();}}public void searchSolution(int n)//搜尋處理第n個待填入資料的格子{if(n>this.posnum)//如果所有的格子都處理完了則輸出{output();return;}int row=pos[n-1]/9;//擷取第n個待處理格子的座標位置和子網格位置int column=pos[n-1]%9;int k=(row/3)*3+(column/3);for(int i=1;i<=9&&!flag;i++)//遍曆,分別用1-9中的數進行試探{if(rstate[row][i]) //判斷數組i在格所在的行和列以及子網格中是否出現過continue;if(cstate[column][i])continue;if(gstate[k][i])continue;table[row][column]=i;//填入該數,並設定標誌rstate[row][i]=true;cstate[column][i]=true;gstate[k][i]=true;searchSolution(n+1);//回溯table[row][column]=0;//清除標誌rstate[row][i]=false;cstate[column][i]=false;gstate[k][i]=false;}}}

運行結果如下所示:

輸入九宮格的資料(使用0表示待填入資料的格子):103000509002109400 000704000300502006 060000050700803004 000401000009205800804000107結果: 1 4 3 6 2 8 5 7 9 5 7 2 1 3 9 4 6 8 9 8 6 7 5 4 2 3 1 3 9 1 5 4 2 7 8 6 4 6 8 9 1 7 3 5 2 7 2 5 8 6 3 9 1 4 2 3 7 4 8 1 6 9 5 6 1 9 2 7 5 8 4 3 8 5 4 3 9 6 1 2 7

我們再講一個比較經典的問題,那就是八皇后問題。什麼是八皇后問題呢?八皇后問題,是一個古老而著名的問題,是回溯演算法的典型例題。該問題是十九世紀著名的數學家高斯1850年提出:在8X8格的國際象棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。

Java代碼實現如下所示:

package com.application.sample;//使用回溯法解決八皇后問題public class BackTracingSample4 {private boolean[][] table=new boolean[8][8];private int queencount=0;private int total=0;public boolean lineHasQueen(int line)//line列全為false則返回true,否則返回false{for(int i=0;i<8;i++){if(table[i][line]==true)return true;}return false;}public boolean catercornerHasQueen(int x,int y)//判斷對角線方向是否含有皇后,如果有則返回true,否則返回false{int i,j;for(i=x,j=y;i<8&&j<8;i++,j++){if(table[i][j]==true)return true;}for(i=x,j=y;i>=0&&j>=0;i--,j--){if(table[i][j]==true)return true;}for(i=x,j=y;i>=0&&j<8;i--,j++){if(table[i][j]==true)return true;}for(i=x,j=y;i<8&&j>=0;i++,j--){if(table[i][j]==true)return true;}return false;}public void searchSolution(int line){if(line==8)return;for(int i=0;i<8;i++){if((!lineHasQueen(i))&&(!catercornerHasQueen(line,i))){table[line][i]=true;queencount++;if(queencount==8){System.out.println();output();table[line][i]=false;queencount--;continue;}searchSolution(line+1);table[line][i]=false;queencount--;}}}public void output(){total++;System.out.println("Total:"+total);for(int i=0;i<8;i++){for(int j=0;j<8;j++){if(table[i][j]==true){System.out.print("1");}else{System.out.print("0");}}System.out.println();}}public static void main(String[] args) {// TODO Auto-generated method stubBackTracingSample4 sample=new BackTracingSample4();sample.searchSolution(0);}}

運行結果如下所示:

Total:1
10000000
00001000
00000001
00000100
00100000
00000010
01000000
00010000

Total:2
10000000
00000100
00000001
00100000
00000010
00010000
01000000

.............

Total:91
00000001
00100000
10000000
00000100
01000000
00001000
00000010
00010000

Total:92
00000001
00010000
10000000
00100000
00000100
01000000
00000010
00001000

最後我們來看一個大家基本都見過的題目,那就是上樓梯。對於一個含有n階的樓梯,如果我們每次只能上一階或者兩階,問我們總共可以有多少種走法,每種走法怎麼走?

對於這個問題,我們很明顯也可以使用回溯法進行求解。求解過程如下所示:

package com.application.sample;public class BackTracingSample5 {private int[] step;public BackTracingSample5(int n){step=new int[n+1];step[0]=n;}public void goUp(int level,int n){if(n>step[0])return;if(n==step[0]){output(level);return;}for(int i=1;i<=2;i++){step[level+1]=i;goUp(level+1,n+i);step[level+1]=0;}}private void output(int level){System.out.println("步數:"+level);for(int i=1;i<=level;i++){System.out.print(step[i]+" ");}System.out.println();}public static void main(String[] args) {// TODO Auto-generated method stubBackTracingSample5 sample=new BackTracingSample5(5);sample.goUp(0, 0);}}

運行結果如下所示:

步數:5
1 1 1 1 1
步數:4
1 1 1 2
步數:4
1 1 2 1
步數:4
1 2 1 1
步數:3
1 2 2
步數:4
2 1 1 1
步數:3
2 1 2
步數:3
2 2 1

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.