資料結構與演算法——棧 迷宮演算法 回溯法
阿新 • • 發佈:2020-10-22
找迷宮通路需要使用回溯法,找迷宮通路是對回溯法的一個很好的應用,實現回溯的過程用到資料結構—棧!
回溯法:對一個包括有很多個結點,每個結點有若干個搜尋分支的問題,把原問題分解為若干個子問題求解的 演算法;當搜尋到某個結點發現無法再繼續搜尋下去時,就讓搜尋過程回溯(回退)到該節點的前一個結點,繼續 搜尋該節點外的其他尚未搜尋的分支;如果發現該結點無法再搜尋下去,就讓搜尋過程回溯到這個結點的前一 結點繼續這樣的搜尋過程;這樣的搜尋過程一直進行到搜尋到問題的解或者搜尋完了全部可搜尋分支沒有解存 在為止。
程式碼實現
maze.h
1 #pragma once 2 #include<stdio.h> 3#include<stdlib.h> 4 5 #define MAXSIZE 100 //預先分配空間,這個數值根據實際需要預估確定 6 7 typedef struct _Position //迷宮座標 8 { 9 int _x; 10 int _y; 11 }Position; 12 13 typedef Position ElemType; 14 15 typedef struct _SqStack 16 { 17 ElemType *base; //棧底指標 18 ElemType *top; //棧頂指標 19 }SqStack; 20 21 bool InitStack(SqStack &S) //構造一個空棧 S 22 { 23 S.base = new ElemType[MAXSIZE]; //為順序棧分配一個最大容量為 MAXSIZE 的空間 24 if (!S.base) //空間分配失敗 25 return false; 26 S.top=S.base; //top 初始為 base,空棧 27 28 return true; 29 } 30 31 bool PushStack(SqStack &S, ElemType e) // 插入元素 e 為新的棧頂元素 32 { 33 if (S.top-S.base == MAXSIZE) //棧滿 34 return false; 35 36 *(S.top++) = e; //元素 e 壓入棧頂,然後棧頂指標加 1,等價於*S.top=e; 37 S.top++; 38 39 return true; 40 } 41 42 bool PopStack(SqStack &S, ElemType &e) //刪除 S 的棧頂元素,暫存在變數 e 中 43 { 44 if (S.base == S.top) //棧空 45 { 46 return false; 47 } 48 e = *(--S.top); //棧頂指標減 1,將棧頂元素賦給 e 49 50 return true; 51 } 52 53 ElemType* GetTop(SqStack &S) //返回 S 的棧頂元素,棧頂指標不變 54 { 55 if (S.top != S.base) //棧非空 56 { 57 return S.top - 1; //返回棧頂元素的值,棧頂指標不變 58 } 59 else 60 { 61 return NULL; 62 } 63 } 64 65 int GetSize(SqStack &S) //返回棧中元素個數 66 { 67 return (S.top-S.base); 68 } 69 70 bool IsEmpty(SqStack &S) //判斷棧是否為空 71 { 72 if (S.top == S.base) 73 { 74 return true; 75 } 76 else 77 { 78 return false; 79 } 80 } 81 82 void DestoryStack(SqStack &S) //銷燬棧 83 { 84 if(S.base) 85 { 86 free(S.base); 87 S.base = NULL; 88 S.top = NULL; 89 } 90 }
maze.cpp
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include "maze.h" 5 #include <assert.h> 6 7 #define ROW 6 8 #define COL 6 9 10 typedef struct _Maze 11 { 12 int map[ROW][COL]; 13 }Maze; 14 15 void InitMaze(Maze* m, int map[ROW][COL]) //迷宮的初始化 16 { 17 for (int i = 0;i< ROW ;++i) 18 { 19 for (int j = 0; j < COL; ++j) 20 { 21 m->map[i][j] = map[i][j]; 22 } 23 } 24 } 25 26 void PrintMaze(Maze* m) //列印迷宮 27 { 28 for (int i = 0; i < ROW; ++i) 29 { 30 for (int j = 0; j < COL; ++j) 31 { 32 printf("%d ",m->map[i][j]); 33 } 34 printf("\n"); 35 } 36 printf("\n"); 37 } 38 39 int IsValidEnter(Maze* m, Position cur) //判斷是否是有效的入口 40 { 41 assert(m); 42 if ((cur._x == 0 || cur._x == ROW - 1) || (cur._y == 0 || cur._y == COL - 1) && (m->map[cur._x][cur._y] == 1)) 43 return 1; 44 else 45 return 0; 46 } 47 48 int IsNextPass(Maze* m,Position cur, Position next) //判斷當前節點的下一個節點能否走通 49 { 50 assert(m); 51 52 //判斷 next 節點是否為 cur 的下一節點 53 if(((next._x == cur._x) && ((next._y == cur._y+1)||(next._y == cur._y-1))) ||((next._y == cur._y) && ( (next._x == cur._x+1)||(next._x == cur._x-1)))) 54 { 55 //在同一行上並且相鄰 56 //或在同一列上並且相鄰 57 //判斷下一個節點是否在迷宮裡面 58 if (((next._x >= 0 && next._x < ROW) || (next._y >= 0 && next._y < COL)) &&(m->map[next._x][next._y] == 1)) 59 { 60 return 1; 61 } 62 } 63 return 0; 64 } 65 66 int IsValidExit(Maze* m, Position cur,Position enter) //判斷當前節點是不是有效的迷宮出口 67 { 68 assert(m); 69 //這裡首先得保證該節點不是入口點,其次只要它處在迷宮的邊界即可 70 if ((cur._x != enter._x || cur._y != enter._y) && ((cur._x == 0 || cur._x == ROW - 1) || (cur._y == 0 || cur._y == COL - 1))) 71 return 1; 72 else 73 return 0; 74 } 75 76 int PassMaze(Maze* m,Position enter,SqStack* s) //找迷宮通路 77 { 78 assert(m && IsValidEnter(m,enter) == 1); //對給的迷宮的入口進行合法性判斷 79 Position cur = enter; 80 Position next; 81 PushStack(*s, cur); //首先將迷宮的入口壓入棧中 82 m->map[cur._x][cur._y] = 2; //將入口值改為 2 83 84 //PrintMaze(m); 85 while (!IsEmpty(*s)) 86 { 87 cur = *GetTop(*s); 88 //printf("cur: %d %d\n",cur._x, cur._y); 89 if (IsValidExit(m,cur,enter) == 1) //判斷當前位置是否出口 90 return 1; 91 92 //嘗試向左一步:看當前節點的左一個節點能不能走通 93 next = cur; 94 next._y = cur._y - 1; 95 if (IsNextPass(m, cur, next) == 1) 96 { 97 PushStack(*s, next); 98 m->map[next._x][next._y] = m->map[cur._x][cur._y] + 1; 99 //PrintMaze(m); 100 continue; 101 } 102 103 //嘗試向上一步:看當前節點的上一個節點能不能走通 104 next = cur; 105 next._x = cur._x - 1; 106 if (IsNextPass(m,cur,next) == 1) //next 節點能夠走通時,將其壓入棧中 107 { 108 PushStack(*s,next); 109 m->map[next._x][next._y] = m->map[cur._x][cur._y]+1; //將 next 節點的值等於 cur 節點的值加 1 110 //PrintMaze(m); 111 continue; 112 } 113 114 //右:看當前節點的向右的一個節點能不能走通 115 next = cur; 116 next._y = cur._y + 1; 117 if (IsNextPass(m, cur,next) == 1) 118 { 119 PushStack(*s, next); 120 m->map[next._x][next._y] = m->map[cur._x][cur._y] + 1; 121 //PrintMaze(m); 122 continue; 123 } 124 125 //下:看當前節點的下一個節點能不能走通 126 next = cur; 127 next._x = cur._x + 1; 128 if (IsNextPass(m, cur,next) == 1) 129 { 130 PushStack(*s, next); 131 m->map[next._x][next._y] = m->map[cur._x][cur._y] + 1; 132 //PrintMaze(m); 133 continue; 134 } 135 136 //走到這裡說明當前節點的四個方向都走不通,進行回溯,看前一個節點未被遍歷的方向是否還能走通 137 Position tmp; 138 PopStack(*s, tmp); 139 } 140 return 0; 141 } 142 int main() 143 { 144 //用二維陣列描繪迷宮:1 代表通路,0 代表牆 145 int map[ROW][COL] = 146 { 147 0,0,1,0,0,0, 148 0,0,1,1,1,0, 149 0,0,1,0,0,0, 150 0,1,1,1,1,0, 151 0,0,1,0,1,0, 152 0,0,0,0,1,0 153 }; 154 155 Maze m; 156 Position enter; //迷宮入口 157 enter._x = 0; 158 enter._y = 2; 159 InitMaze(&m, map); 160 PrintMaze(&m); 161 162 //system("pause");//exit(0); 163 SqStack s; //定義棧,儲存已走過的座標軌跡,便於回溯 164 InitStack(s); //棧的初始 165 int ret = PassMaze(&m,enter,&s); //使用棧和回溯法解開迷宮 166 if(ret) 167 { 168 printf("恭喜你!終於找到了出口~\n"); 169 } 170 else 171 { 172 printf("不是我笨!實在沒有出口~\n"); 173 } 174 175 PrintMaze(&m); 176 system("pause"); 177 178 return 0; 179 }