Description
int maze[5][5] = {
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,
};
它表示一個迷宮,其中的1表示牆壁,0表示可以走的路,只能橫着走或豎着走,不能斜着走,要求編程序找出從左上角到右下角的最短路線。
Input
Output
Sample Input
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
Sample Output
(0, 0) (1, 0) (2, 0) (2, 1) (2, 2) (2, 3) (2, 4) (3, 4) (4, 4)
分析:這道題很明顯是一道尋找最短路徑的問題,那就應該選擇廣度優先搜索,首先來說說廣搜吧,廣搜的基本思想是這樣的:
從初始狀態S開始,利用規則,生成所有可能的狀態。構成樹的下一層節點,檢查是否出現目標狀態G,若未出現,就對該層所有狀態節點,分別順序利用規則。生成再下一層的所有狀態節點,對這一層的所有狀態節點檢查是否出現G,若未出現,繼續按上面思想生成再下一層的所有狀態節點,這樣一層一層往下展開。直到出現目標狀態為止。
也就是如下圖所示:
我們來看一下維基上的代碼:
1 std::queue<node*> visited, unvisited; 2 node nodes[9]; 3 node* current; 4 5 unvisited.push(&nodes[0]); //先把root放入unvisited queue 6 7 while(!unvisited.empty()) //只有unvisited不空 8 { 9 current = (unvisited.front()); //目前應該檢驗的 10 11 if(current -> left != NULL) 12 unvisited.push(current -> left); //把左邊放入queue中 13 14 if(current -> right != NULL) //右邊壓入。因為QUEUE是一個先進先出的結構,所以即使后面再壓其他東西,依然會先訪問這個。 15 unvisited.push(current -> right); 16 17 visited.push(current); 18 19 cout << current -> self << endl; 20 21 unvisited.pop(); 22 }
首先是有一棵構建好的樹,我們從跟節點開始,訪問第一層子節點,然后是第二層...這些都應該不難理解。問題是,我們應該怎么初始化這樣一棵樹,然后怎么記錄路徑,因為一般給的都只是一個序列,首先你要根據這個序列初始化一棵樹然后才能在這棵樹的基礎上去找。
這里就只考慮迷宮問題吧,首先題目給的是一個二維序列,那么我們就可以直接將這個二維序列看成一棵樹,節點就是迷宮路上的每一個格子(非牆),走迷宮的時候,格子間的關系是什么呢?按照題目意思,我們只能橫豎走,因此我們可以這樣看,格子與它橫豎方向上的格子是有連通關系的,只要這個格子跟另一個格子是連通的,那么兩個格子節點間就有一條邊。如果說本題再修改成斜方向也可以走的話,那么就是格子跟周圍8個格子都可以連通,於是一個節點就會有8條邊(除了邊界的節點)。
下面是記錄路徑的問題,對於記錄路徑,我們可以采用回溯法,在每個node里面設一個變量記錄它前面的元素,那么在遇到正確結果后就可以以該元素為根往回遍歷直到最開始的元素,在每個節點處打印,那這樣就把整條路徑打印出來了。如:
1 struct node{ 2 int pre; 3 int x; 4 int y; 5 } path[100]; 6 7 void print(int i) {//當前節點 8 if (path[i].pre != -1) {//找到前面那個節點 9 print(path[i].pre); 10 cout << "(" << path[i].x << "," << path[i].y << ")" << endl; 11 } else {//最前面的那個節點 12 cout << "(" << path[i].x << "," << path[i].y << ")" << endl; 13 } 14 }
另外一個方法是采用一個stack來專門記錄路徑,下面我用一個流程來說明:
--》
--》...--》
--》
--》
--》
--》...--》
--》...--》
--》...--》
--》
從流程中我們可以看出來了,就不多說了。。。
第三種方法是在node里面設一個list專門記錄路徑,但是這個方法要記得在每次走到下一個節點的時候將它的父節點的路徑記錄加上自己的坐標變成自己的路徑記錄,就像下面這樣:
1 struct node { 2 list<int*> path; 3 int x, y; 4 node(list<int*> fatherList) { 5 path = fatherList; 6 int index[2] = {x, y}; 7 path.push_back(index); 8 } 9 };
那么在找到最后一個的路徑的時候就可以直接打印它的path變量。
有人也許會有疑問,為什么廣搜找到的路徑就是最短了呢?,看看下面這個圖你就明白了:
--》
發現數字的規律了嗎?數字是分層的,在同一層的數字所代表的路徑長度是相同的,一層層的遍歷,當某層第一次到達了出口,這層肯定是最短路徑所在層啦。
下面是那道題的代碼:
1 #include <iostream> 2 3 using namespace std; 4 5 int map[5][5]; 6 7 //相鄰四個節點 8 int borderUponX[4] = {0, 0, 1, -1}; 9 int borderUponY[4] = {1, -1, 0, 0}; 10 11 int front = 0, rear = 1; 12 13 struct node{ 14 int pre; 15 int x; 16 int y; 17 } path[100]; 18 19 void print(int i) {//當前節點 20 if (path[i].pre != -1) {//找到前面那個節點 21 print(path[i].pre); 22 cout << "(" << path[i].x << "," << path[i].y << ")" << endl; 23 } else {//最前面的那個節點 24 cout << "(" << path[i].x << "," << path[i].y << ")" << endl; 25 } 26 } 27 28 void bfsSearch(int x, int y) { 29 //開始節點(出發),前面沒有節點了 30 path[front].x = x; 31 path[front].y = y; 32 path[front].pre = -1; 33 34 //當front == rear的時候說明已經走完了所以“相鄰”節點 35 //且都不通 36 while (front < rear) { 37 for (int i = 0; i != 4; i++) { 38 //相鄰節點坐標 39 int pathX = path[front].x + borderUponX[i]; 40 int pathY = path[front].y + borderUponY[i]; 41 42 //不符合的節點(遇到邊界或已經走過了) 43 if (pathY < 0 || pathX < 0 || pathX > 4 || pathY > 4 || map[pathX][pathY]) 44 continue; 45 else {//將front的相鄰的可以過去的並且是還沒有走過的節點加到路徑里面 46 map[pathX][pathY] = 1; 47 path[rear].x = pathX; 48 path[rear].y = pathY; 49 path[rear].pre = front; 50 rear++; 51 } 52 if (pathX == 4 && pathY == 4) { 53 //找到了一條路徑,又是第一次找到 54 //那么就是最短路徑了 55 print(rear - 1); 56 break; 57 } 58 } 59 front++; 60 } 61 } 62 63 int main(int argc, char const *argv[]) 64 { 65 for(int i = 0;i < 5;i++) 66 for(int j = 0;j < 5;j++) 67 cin >> map[i][j]; 68 69 bfsSearch(0,0); 70 return 0; 71 }