棧應用--迷宮問題
一、棧
- 棧是一種重要的線性結構,在資料結構而言,其本質操作是線性表操作的子集。但在資料型別來說,是一種重要的抽象資料型別。在面向物件的程式設計中,棧是多資料型別。
- 棧只允許在固定的一端進行插入和刪除元素操作。進行資料插入和刪除操作的一端稱為棧頂,另一端稱為棧底。不含任何元素的棧稱為空棧,棧又稱為後進先出的線性表,簡稱LIFO結構。棧可以將資料從一種序列變到另一種序列。
二、棧的分類
- 順序棧,即棧的順序儲存結構是利用一組地址連續的儲存單元依次存放自棧底到棧頂的元素,一般使用過程中所需的最大空間,故先給棧分配一個基本容量,在使用過程中然後增容。
- 鏈式棧,是一種不連續的儲存結構,元素以Node的形式儲存,包含資料域和指標域,指標域指向下一個節點,每個節點隨機分佈,為動態開闢節點。
三、棧的基本模組
無論是鏈式棧還是順序棧其基本操作都一樣,包含棧的建立,初始化,銷燬,插入,刪除,取棧頂元素等,只是其實現方式不同。下面就是順序棧的模組。
- 棧程式碼:
標頭檔案"Stack.h"
#pragma once #include <stdio.h> #include <malloc.h> #include <assert.h> #include <stdlib.h> // //typedef int DataType; // //#define N 10 //typedef struct Stack //{ // DataType _a[N];//規定陣列空間大小 // int _top; // 棧頂 //}Stack;//靜態棧 typedef struct Pos { int _row; int _col; }Pos;//迷宮位置點 typedef Pos DataType; typedef struct Stack { DataType* _a; //節點指標 int _top; // 棧頂 int _capacity; // 容量 }Stack; void StackInit(Stack* ps);//初始化 void StackDestory(Stack* ps);//銷燬 void StackPush(Stack* ps, DataType x);//壓棧 void StackPop(Stack* ps);//出棧 DataType StackTop(Stack* ps);//取棧頂元素 int StackEmpty(Stack* ps);//判空 int StackSize(Stack* ps);//棧的大小 //void TestStack();
函式檔案"Stack.c"
#include "Stack.h" void StackInit(Stack* ps) { ps->_a = (DataType*)malloc(sizeof(DataType)* 3);//動態開闢空間 assert(ps);//防止開闢失敗 ps->_capacity = 3; ps->_top = 0; } void StackDestory(Stack* ps) { assert(ps);//斷言 if (ps->_a) { free(ps->_a); ps->_a = NULL; ps->_capacity = ps->_top = 0; } while (ps->_top--) { free(ps->_top); } ps->_capacity = 0; } void StackPush(Stack* ps, DataType x) { assert(ps); if (ps->_top == ps->_capacity)//越界 { ps->_a = (DataType*)realloc(ps->_a, sizeof(DataType)*(ps->_capacity * 2));//增容 assert(ps->_a); ps->_capacity *= 2; } ps->_a[ps->_top] = x; ps->_top++; } void StackPop(Stack* ps) { assert(ps->_a); assert(ps->_top > 0); ps->_top--; } DataType StackTop(Stack* ps) { assert(ps->_a && ps->_top > 0); return ps->_a[ps->_top - 1]; } //空 0 //非空 1 int StackEmpty(Stack* ps) { assert(ps); return ps->_top == 0 ? 0 : 1; } int StackSize(Stack* ps) { assert(ps); return ps->_top; } //測試程式碼 //void TestStack() //{ // Stack s; // StackInit(&s); // // StackPush(&s, 1); // StackPush(&s, 2); // StackPush(&s, 3); // StackPush(&s, 4); // // while (StackEmpty(&s)) // { // printf("%d ",StackTop(&s)); // StackPop(&s); // } // printf("\n"); // // // StackDestory(&s); //}
四、迷宮
- 迷宮問題,望文生義就是在迷宮當中找到出口,出入口自定義,解決迷宮問題的過程中以回溯法為思想,應用棧的知識來解決這個問題。對於迷宮問題的思考是由解決簡單的迷宮然後到環路迷宮這樣一個過程的。
回溯法:對一個包括有很多個結點,每個結點有若干個搜尋分支的問題,把原問題分解為若干個子問題進行求解的演算法;當搜尋到某個結點發現無法再繼續搜尋下去時,就讓搜尋過程回溯到該結點的前一個結點,繼續搜尋該結點外的其他尚未搜尋的分支;如果發現該結點無法再搜尋下去,就讓搜尋過程回溯到這個結點的前一個結點繼續這樣的搜尋過程。
1.簡單迷宮--------從出口處將開始進行壓棧,判斷其上,下,左,右方向是否可走,若某個方向可走將其座標進行壓棧,並將座標上的值置為2,再探測該座標的分支,直到出口,返回1即找到出口,若在某一個結點處四個方向均不可走則返回上一個座標,並將該座標從棧中釋放,若棧為空時仍未到出口處則返回0即未找到出口。
0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 1 1 0 0 0 1 0 1 1 0 0 1 0 0 0
2.多路迷宮--------在簡單迷宮的基礎上當找到出口時並不返回值,當棧為空即回溯到出口時返回到主函式。
0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 1 1 1 0 0 1 0 0 0 0 0 1 1 1 1 0 0 1 0 0 0
3.環路迷宮--------在多路迷宮的基礎上,在設定壓入棧的座標的值時換為前一個座標的值加1,即遞增的。在判斷某個座標是否可走時變換條件,進而達到目的。
0 0 0 0 0 0 0 0 1 1 1 0 0 0 1 0 1 0 0 0 1 0 1 0 0 0 1 1 1 1 0 0 1 0 0 0
- 迷宮程式碼(環路迷宮程式碼): 標頭檔案"maze.h"
#pragma once
#include"Stack.h"
#define N 6
int size;
static int maze[N][N] = {//static,只在maze.c中可見
/*{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 1 },
{ 0, 0, 1, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 1 },
{ 0, 0, 1, 0, 0, 0 },*/
/*{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 0 },
{ 0, 0, 1, 0, 1, 0 },
{ 0, 0, 1, 0, 1, 0 },
{ 0, 0, 1, 1, 1, 1 },
{ 0, 0, 1, 0, 0, 0 },*/
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 0 },
{ 0, 0, 1, 0, 1, 1 },
{ 0, 0, 1, 0, 0, 0 },
};
int GetMazePath(Pos entry, Pos exit);//獲取迷宮通路:找到通路/找不到通路
int CheckAccess(Pos cur, Pos next);
void PrintfMaze();
void TestMaze();
函式檔案"maze.c"
#include"Maze.h"
int CheckAccess(Pos cur, Pos next)
{
if (next._row >= 0 && next._row < N
&&next._col >= 0 && next._col < N
&& (maze[next._row][next._col] == 1 || maze[next._row][next._col] > maze[cur._row][cur._col] + 1))//當下一個座標的值為1 或者比當前值加一還大就可以走
{
return 1;
}
else
{
return 0;
}
}
int size = 0;
int GetMazePath(Pos entry, Pos exit)
{
Stack path;
int flag = 0;
StackInit(&path);
StackPush(&path, entry);
maze[entry._row][entry._col] = 2;//入口初始化為2
while (StackEmpty(&path))
{
//Pos cur = entry;//當前位置
Pos cur = StackTop(&path);//棧頂取出的當前位置
//if (cur._col == 5)//多條通路
if (cur._row == exit._row
&&cur._col == exit._col)//當一條路徑找到出口時,列印路徑並且看路徑長短
{
flag = 1;
for (int i = 0; i < path._top; i++)
{
printf("[%d %d]->", path._a[i]._row, path._a[i]._col);
}
printf(" Exit\n\n");
if (size == 0 || StackSize(&path) < size)
{
size = StackSize(&path);//路徑的長短
}
}
Pos next;
//上
next = cur;//當前位置
next._row -= 1;//下一個位置
if (CheckAccess(cur, next))
{
maze[next._row][next._col] = maze[cur._row][cur._col] + 1;
StackPush(&path, next);//下一個位置可以通,入棧
continue;
}
//下
next = cur;
next._row += 1;
if (CheckAccess(cur, next))
{
maze[next._row][next._col] = maze[cur._row][cur._col] + 1;
StackPush(&path, next);//下一個位置可以通,入棧
continue;
}
//左
next = cur;
next._col -= 1;
if (CheckAccess(cur, next))
{
maze[next._row][next._col] = maze[cur._row][cur._col] + 1;
StackPush(&path, next);//下一個位置可以通,入棧
continue;
}
//右
next = cur;
next._col += 1;
if (CheckAccess(cur, next))
{
maze[next._row][next._col] = maze[cur._row][cur._col] + 1;
StackPush(&path, next);//下一個位置可以通,入棧
continue;
}
//走到四個方向不通的位置
//回溯
StackPop(&path);
}
if (flag == 0)//判斷是否找到出口
return 0;
else
return 1;
}
void PrintfMaze()
{
for (int i = 0; i < N; ++i)
{
for (int j = 0; j < N; ++j)
{
printf("%d ", maze[i][j]);
}
printf("\n");
}
printf("\n");
}
主函式檔案"main.c"
#include "maze.h"
void TestMaze()
{
Pos entry;
entry._row = 5;
entry._col = 2;
Pos exit;
exit._row = 4;
exit._col = 5;
printf("迷宮地圖:\n");
PrintfMaze();
if (GetMazePath(entry, exit))
{
printf("有出口,最短路徑為:%d \n\n", size);
}
else
{
printf("無出口!\n\n");
}
printf("走出後迷宮地圖:\n");
PrintfMaze();
}
int main()
{
TestMaze();
system("pause");
return 0;
}
五、執行結果
1.簡單迷宮
2.多路迷宮
3.環路迷宮
結合之前的棧的基本操作程式碼就可以解決迷宮問題了。-( ̄▽ ̄)-*