回溯算法
主要思想
回溯算法的基本思想是:從一條路往前走,能進則進,不能進則退回來,換一條路再試。八皇后問題就是回溯算法的典型,第一步按照順序放一個皇后,然后第二步符合要求放第2個皇后,如果沒有位置符合要求,那么就要改變第一個皇后的位置,重新放第2個皇后的位置,直到找到符合條件的位置就可以了?;厮菰诿詫m搜索中使用很常見,就是這條路走不通,然后返回前一個路口,繼續下一條路?;厮菟惴ㄕf白了就是窮舉法。不過回溯算法使用剪枝函數,剪去一些不可能到達 最終狀態(即答案狀態)的節點,從而減少狀態空間樹節點的生成。回溯法是一個既帶有系統性又帶有跳躍性的的搜索算法。它在包含問題的所有解的解空間樹中,按照深度優先的策略,從根結點出發搜索解空間樹。算法搜索至解空間樹的任一結點時,總是先判斷該結點是否肯定不包含問題的解。如果肯定不包含,則跳過對以該結點為根的子樹的系統搜索,逐層向其祖先結點回溯。否則,進入該子樹,繼續按深度優先的策略進行搜索。回溯法在用來求問題的所有解時,要回溯到根,且根結點的所有子樹都已被搜索遍才結束。而回溯法在用來求問題的任一解時,只要搜索到問題的一個解就可以結束。這種以深度優先的方式系統地搜索問題的解的算法稱為回溯法,它適用于解一些組合數較大的問題。回溯算法也叫試探法,它是一種系統地搜索問題的解的方法。回溯算法的基本思想是:從一條路往前走,能進則進,不能進則退回來,換一條路再試。用回溯算法解決問題的一般步驟為:
- 定義一個解空間,它包含問題的解。
- 利用適于搜索的方法組織解空間。
- 利用深度優先法搜索解空間。
- 利用限界函數避免移動到不可能產生解的子空間。
解決迷宮問題
解決思想
將迷宮問題對應為二維數組,數組中只有兩種值0和1,其中0,1分別表示通路和墻。不過在解決這個問題的時候一般要在最外面添加一個圍墻,這里設置每個圍墻都為1,這樣有利于防止當走到了迷宮的出口處還會向前走,這個并不一定,只是最一般的方法,也是最有利于理解的方法。這里的利用到了回溯法,需要走到了一個位置,然后向四處試探,如果有一個方向可以走了就將當前的點壓入棧,并且標記當前點以便于區分是否走過,如果四處都無出路,只需要回到前一個走到的點,然后從前一個點再換一個方向重新走
代碼
import java.util.Stack;
/**
* Created by chenjiabing on 17-5-5.
*/
class position {
public int row;
public int col;
public position(int row, int col) {
this.col = col;
this.row = row;
}
public position() {
row = 0;
col = 0;
}
public String toString() {
return "(" + (row - 1) + " ," + (col - 1) + ")";
} //這里由于四周圍上了墻,所以這里的輸出就要在原來的基礎上減一
}
class Main {
private int[][] maze = null;
private Stack<position> stack = null; //創建一個棧用于存儲狀態
private int row; //行數
private int col;
boolean[][] p = null; //這里的p是用來標記已經走過的點,初始化為false
public boolean end(int i, int j) {
return i == row && j == col;
}
public Main(int[][] maze) {
stack = new Stack<position>();
row = maze[0].length;// 行數
col = maze.length; //列數
p = new boolean[row + 2][col + 2];
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
p[i][j] = false; //初始化
}
}
this.maze = maze;
}
public void findPath() {
//創建一個新的迷宮,將兩邊都圍上墻,也就是在四周都填上1的墻,形成新的迷宮,主要的目的就是防止走到迷宮的邊界的出口的位置還會繼續向前走
//因此需要正確的判斷是否在邊界線上,所以要在外圍加上一堵墻,
int[][] temp = new int[row + 2][col + 2];
for (int i = 0; i < row + 2; i++) {
for (int j = 0; j < col + 2; j++) {
temp[0][j] = 1; //第一行圍上
temp[row + 1][j] = 1; //最后一行圍上
temp[i][0] = temp[i][col + 1] = 1; //兩邊的圍上
}
}
// 將原始迷宮復制到新的迷宮中
for (int i = 0; i < row; ++i) {
for (int j = 0; j < col; ++j) {
temp[i + 1][j + 1] = maze[i][j];
}
}
int i = 1;
int j = 1;
p[i][j] = true;
stack.push(new position(i, j));
//這里是是將走到的點入棧,然后如果前后左右都走不通的話才出棧
while (!stack.empty() && !end(i, j)) {
//下面就開始在四周試探,如果有路就向前走,順序分別是右,下,上,左,當然這是隨便定義的,不過一般都是現向下和右的
if (temp[i][j + 1] == 0 && p[i][j + 1] == false)//這里如果不在四周加上墻,那么在到達邊界判斷的時候就會出現超出數組的索引的錯誤,因為到達邊界再加一就會溢出
{
p[i][j + 1] = true;
stack.push(new position(i, j + 1));
j++;
} else if (temp[i + 1][j] == 0 && p[i + 1][j] == false)//如果下面可以走的話,講當前點壓入棧,i++走到下一個點
{
p[i + 1][j] = true;
stack.push(new position(i + 1, j));
i++;
} else if (temp[i][j - 1] == 0 && p[i][j - 1] == false) {
p[i][j - 1] = true;
stack.push(new position(i, j - 1));
j--;
} else if (temp[i - 1][j] == 0 && p[i - 1][j] == false) {
p[i - 1][j] = true;
stack.push(new position(i - 1, j));
i--;
} else //前后左右都不能走
{
System.out.println(i + "---------" + j);
stack.pop(); //這個點不能走通,彈出
if (stack.empty()) //如果此棧中已經沒有點了,那么直接跳出循環
{
System.out.println("沒有路徑了,出不去了");
return; //直接退出了,下面就不用找了
}
i = stack.peek().row; //獲得最新點的坐標
j = stack.peek().col;
}
//如果已經到達了邊界,那么直接可以出去了,不需要繼續向前走了,這里是規定邊界的任意為0的位置都是出口
//如果不加這個判斷的話,那么當到達邊界的時候,只有走到不能再走的時候才會輸出路線,那種線路相對這個而言是比較長的
if (j == temp[0].length - 2) { //如果已經到達邊界了,那么當前的位置就是出口,就不需要再走了
Stack<position> pos = new Stack<position>();
System.out.println("路徑如下:");
for (int count = 0; count < stack.size(); count++) {
System.out.println(stack.elementAt(count));
}
}
}
}
public static void main(String args[]) {
int[][] maze = {
{0, 1, 0, 0, 0},
{0, 1, 0, 1, 0},
{0, 0, 0, 0, 0},
{0, 1, 1, 1, 0},
{0, 0, 0, 1, 0}
};
Main main = new Main(maze);
main.findPath();
}
}