1. 程式人生 > >迷宮求解演算法(棧DFS以及佇列BFS)

迷宮求解演算法(棧DFS以及佇列BFS)

我們首先給出一個迷宮,它的規格是5 * 5,在這裡我使用int的二維陣列表示迷宮,其中1表示障礙,0表示可以通行的道路,要求從(0,0)座標走到(4, 4)座標,並輸出走過的座標路徑。

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,

};

①使用棧來實現DFS(深度優先搜尋)
首先,我們給出棧在迷宮中的的結構定義。

/*  
 *  x是迷宮中的橫座標
 *  y是迷宮中的縱座標
 *  direction表示在當前座標內要走的下一個方向
 *  next指標則指向棧中的下一個座標
 */
typedef struct LNode { int x; int y; int direction; struct LNode *next; }LNode; /* * top指標指向棧的頂部 * 不需要base指標 */ typedef struct { LNode *top; }Stack;

接著,我們定義一些棧的基本函式操作:

/*
 * IntiStack()
 * 初始化一個棧,傳入一個Stack結構體的地址或結構指標
 * top指標指向一個頭結點,該頭結點不包含座標但next指標指向棧的頂部座標結點
 */
void
IntiStack(Stack *result){ result->top = (LNode *)malloc(sizeof(LNode)); result->top->next = NULL; }
/*
 * Push()
 * 將一個新的座標結點放入棧中,所使用的方法類似於表頭插入法
 * 其中direction是現在的座標點下一步要走的方向
 */ 
void Push(Stack *result, int x, int y, int direction){
    LNode *topNode = (LNode *)malloc(sizeof(LNode));
    topNode->
x = x; topNode->y = y; topNode->direction = direction; topNode->next = result->top->next; result->top->next = topNode; }
/*
 * Pop()
 * 該函式用於走到迷宮的死路時向後退以及遍歷輸出結點
 * 彈出top指標中next指標所指向的結點,也就是棧的頂部節點
 * 類似於從表頭刪除一個節點,並返回該節點
 */ 
LNode *Pop(Stack *result){
    LNode *temp = result->top->next;
    if(temp != NULL){
        result->top->next = temp->next;
    }
    temp->next = NULL;
    return temp;
}

我們再給出main函式的定義

int main(void){
    /*
     * MAX的值為5
     * 定義一個二維陣列
     * 輸入迷宮的數值,並初始化一個棧
     */ 
    int maze[MAX][MAX];
    int i, j;
    for(i = 0;i < MAX;i++){
        for(j = 0;j < MAX;j++){
            scanf("%d", &maze[i][j]);
        }
    }
    Stack result;
    IntiStack(&result);

    /*
     * 當處於一個迷宮結點的時候,我們應該分別檢測四個方向是否有路走
     * 暫定direction = 1, 2, 3, 4分別表示東,南,西,北
     * flagDirection 則表示已經找到一個可行的方向,它是一個哨兵
     * row, col表示當前結點的座標
     * 首先將(0, 0)結點入棧,direction預設從1開始,它還將經過一次順時針遍        歷,所以它的東方向即使是障礙也沒關係
     * 因為已經遍歷過(0, 0)這個點我們不會再走這個點所以把它堵上
     * current指標指向棧的頂部節點
     */ 
    int flagDirect = 1;
    int row = 0;
    int col = 0;
    int direction = 1;
    Push(&result, row, col, direction);
    maze[row][col] = 1;
    LNode *current;

    /*
     * 設定了一個無限迴圈,跳出迴圈的條件是到達點(4, 4)
     * 如何順時針的遍歷四個方向?通過使用switch判斷可行的方向,當方向不可行的        時候direction加1測試下一個方向是否可行
     * 但是當所有方向都不可行時證明該座標是迷宮中的一條死路需要退回去
     * 這時通過Pop操作出棧退回到有路可走的座標點
     * 之前把走過的座標點堵上就是為了不再走重複的路避免在死路里迴圈,也方便出棧
     */ 
    while(1){
        if(row == (MAX - 1) && col == (MAX - 1)){
            break;
        }
        flagDirect = 1;
        current = result.top->next;
        row = current->x;
        col = current->y;
        while(flagDirect){
            switch(current->direction){
                case 1:if(col + 1 < MAX && maze[row][col + 1] == 0){
                           maze[row][col] = 1;
                           col++;
                           Push(&result, row, col, 1);
                           flagDirect = 0;
                       }
                       else{
                           current->direction++;
                       }
                       break;
                case 2:if(row + 1 < MAX && maze[row + 1][col] == 0){
                           maze[row][col] = 1;
                           row++;
                           Push(&result, row, col, 1);
                           flagDirect = 0;
                       }
                       else{
                           current->direction++;
                       }
                       break;
                case 3:if(col - 1 >= 0 && maze[row][col - 1] == 0){
                           maze[row][col] = 1;
                           col--;
                           Push(&result, row, col, 1);
                           flagDirect = 0;
                       }
                       else{
                           current->direction++;
                       }
                       break;
                case 4:if(row - 1 >= 0 && maze[row - 1][col] == 0){
                           maze[row][col] = 1;
                           row--;
                           Push(&result, row, col, 1);
                           flagDirect = 0;
                       }
                       else{
                           current->direction++;
                       }
                       break;
                default:Pop(&result);
                        flagDirect = 0;
                        break;
            }
        }
    }
    show(&result);
    return 0;
}

最後當我們輸出棧的路徑的時候,我們是從終點倒回到起點出棧輸出的,這時,我們可以使用一個遞迴輸出起點到終點

void show(Stack *result){
    LNode *temp;
    if(result->top->next != NULL){
        temp = Pop(result);
        show(result);
        printf("(%d, %d)\n", temp->x, temp->y);
    }
}

②使用佇列來實現BFS(廣度優先搜尋)
佇列在迷宮中的資料結構定義

/*
 * 該佇列為一個雙重連結串列, 通過prev指標向前索引,但該索引關係不符合順序關係,      next指標符合順序關係
 */ 
typedef struct QNode {
    int x;
    int y;
    struct QNode *prev;
    struct QNode *next;
}QNode;
typedef struct {
    QNode *front;
    QNode *rear;
}Queue;

接著給出佇列基本函式操作定義:

/*
 * IntiQueue()
 * 初始化佇列,建立front指標的頭結點
 * rear指標一開始的時候也指向頭結點
 */ 
void IntiQueue(Queue *result){
    result->front = (QNode *)malloc(sizeof(QNode));
    result->front->prev = NULL;
    result->rear = result->front;
}
/*
 * Push()
 * 將當前座標的結點入隊,其中prev指標表示該結點的上一個座標
 * 表尾插入法就好了
 */ 
void Push(Queue *result, int x, int y, QNode *prev){
    QNode *temp = (QNode *)malloc(sizeof(QNode));
    temp->x = x;
    temp->y = y;
    temp->prev = prev;
    temp->next = NULL;
    result->rear->next = temp;
    result->rear = temp;
}

給出main函式定義

int main(void){
     /*
     * MAX的值為5
     * 定義一個二維陣列
     * 輸入迷宮的數值,並初始化一個佇列
     */
    int maze[MAX][MAX];
    int i, j;
    for(i = 0;i < MAX;i++){
        for(j = 0;j < MAX;j++){
            scanf("%d", &maze[i][j]);
        }
    }
    Queue result;
    IntiQueue(&result);
    /*
     * row, col 為當前的橫縱座標
     * (0, 0)的先前座標是佇列的頭結點,也就是result.front
     * find是一個哨兵,標誌著是否到達終點
     * current指標指向當前的座標結點,並將當前座標節點周圍可走的座標入隊,如果       current為空的時候則表明找不到終點,佇列的遍歷類似於樹中的層序遍歷
    int row = 0, col = 0;
    Push(&result, row, col, result.front);
    maze[row][col] = 0;
    int direction = 1;
    int find = 0;
    QNode *current = result.front->next;
    /*
     * 使用順時針遍歷,能走的座標就入隊,並且為了防止重複的座標入隊,必須將走過       的路堵上
     */
    while(current != NULL && !find){
        direction = 1;
        row = current->x;
        col = current->y;
        if(row == MAX - 1 && col == MAX - 1){
            find = 1;
            break;
        }
        while(direction <= 4){
            switch(direction){
                case 1:if(col + 1 < MAX && maze[row][col + 1] == 0){
                           Push(&result, row, col + 1, current);
                           maze[row][col + 1] = 1;
                       }
                       break;
                case 2:if(row + 1 < MAX && maze[row + 1][col] == 0){
                           Push(&result, row + 1, col, current);
                           maze[row + 1][col] = 1;
                       }
                       break;
                case 3:if(col - 1 >= 0 && maze[row][col - 1] == 0){
                           Push(&result, row, col - 1, current);
                           maze[row][col - 1] = 1;
                       }
                       break;
                case 4:if(row - 1 >= 0 && maze[row - 1][col] == 0){
                           Push(&result, row - 1, col, current);
                           maze[row - 1][col] = 1;
                       }
                       break;
            }
            direction++;
        }
        current = current->next;
    }
    show(&result, current);
    return 0;
}

show函式只要通過prev指標索引就能得到一條從起點走向終點的路徑,當然需要遞迴。

void show(Queue *result, QNode *current){
    if(current->prev != NULL){
        show(result, current->prev);
        printf("(%d, %d)\n", current->x, current->y);
    }
}