1. 程式人生 > >類似孔明棋,尋找棋局中到達目標點的最短路徑(深搜和廣搜)

類似孔明棋,尋找棋局中到達目標點的最短路徑(深搜和廣搜)

主題內容:有個遊戲玩法很類似孔明棋.

其遊戲的原始規則如下:

原始棋盤為這樣:
假設0為空格 1為棋子0000000
0000000
0000000
0000000
1111111
1111111
1111111
1111111

1.
棋子的移動必須經由跳過其隔壁(可以是水平或是垂直,但不能走斜角)的一顆棋到空格
2.
被跳過的那顆棋子就會從棋盤上消失不見
3.
只要其中一顆棋子跳到了棋盤底端,遊戲就結束但是這個程式並不要是寫出來讓使用者來玩而是要使用者指定出一個空格來做為目的地(這個空格可以是原始棋盤上的任一個空格)
然後接著這個程式會自己去找出從原始棋盤走到被指定的目的地那步棋的最短移動路徑接著就把每一步棋都印出來假設目的地被指定為X:

0000000
0000X00
0000000
0000000
1111111
1111111
1111111

1111111


移動第一步可能為以下七種之一:

1.1 1.2 1.3 1.4 1.5 1.6 1.7

0000000 0000000 0000000 0000000 0000000 0000000 0000000
0000X00 0000X00 0000X00 0000X00 0000X00 0000X00 0000X00
0000000 0000000 0000000 0000000 0000000 0000000 0000000
移動到這> 1000000 0100000 0010000 0001000 0000100 0000010 0000001
跳過的被吃掉>0111111 1011111 1101111 1110111 1111011 1111101 1111110
從這移動> 0111111 1011111 1101111 1110111 1111011 1111101 1111110
1111111 1111111 1111111 1111111 1111111 1111111 1111111
1111111 1111111 1111111 1111111 1111111 1111111 1111111

假設第一步為1.1, 第二步可能為以下八種之一:

2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8

0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000
0000X00 0000X00 0000X00 0000X00 0000X00 0000X00 0000X00 0000X00
0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000
1100000 1010000 1001000 1000100 1000010 1000001 1000000 1000000
0011111 0101111 0110111 0111011 0111101 0111110 1001111 0111111
0011111 0101111 0110111 0111011 0111101 0111110 0111111 1001111
1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111
1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111

但是第一步也可能為1.2, 1.3, 1.4, 1.5, 1.6 1.7 之一而每個第一步又能各產生出八個第二步每個第二步又可以產生出一樣數量的第三步, 以此類推直到到達目的地 X
所以可能會有非常多種可能來走到目的地這個程式被要求如下:

1.
使用者可以指定目的地(若所指定的目的地是不合法的,程式必須提出警告)
2.
必須要存有所有的可能走法(可能會有非常多個陣列)
3.
如果棋盤中的可能走法有重覆,只要保有一個就好(不能有重覆的)
4.
必須把最早到達目的地的那步棋的所有路徑給印出來有其中一種方式的流程提示如下:

首先,應該會有一個列表用來儲存所有可能的走法

1.
先找一顆棋
2.
再找看看它的左邊右邊及上面(三個方向即可)是否也有棋子(若有就接步驟3,沒有就回步驟1找下一顆棋子)
3.
若這顆被找到的棋是在原本那顆棋的左邊被找到就檢查它的左邊是否有空格, 右邊被找到就檢查它的右邊是否有空格, 上面被找到就檢查它的上面是否有空格
(
若有空格就接步驟4,若無就再回步驟1找下一顆棋)
4.
把步驟1找的棋移到步驟3找到的空格,並把步驟2找到的棋子變空格,然後把此步走法到步驟5中去檢驗
5.
檢查這個新走法是否之前已經有被存過(是否有重覆),若有的話就跳步驟1,若沒有重覆則跳步驟6
6.
把這個被檢查過沒有重覆的陣列加入列表中
7.
重覆步驟1~6直到走到了被指定的目的地

本程式方法有二種::

(1)深度優先搜尋演算法<原始碼見附錄1>:

對於當前某個棋盤,計算它下一步能走的所有路徑,並按順序儲存在一個向量中,由於是深度優先遍歷,所以可以設定一個變數iIndexLst記錄其按序訪問的索引(即先訪問第一個,下次再訪問第二個),每次訪問時要將訪問的路徑(即要走的棋子的座標,方向和當前走法的索引值iIndexLst)入棧,以便此分支下無路可走或找到目標點時能夠回溯(出棧並恢復棋盤).然後走棋(更新棋盤,設定0,1),不過為了防止iIndexLst訪問時越界,需與當前所有走法的最大值進行比較,如果不越界,表明仍有路可走,則繼續往前走,進入下一個分支(iIndexLst= 0).否則表示當前走法已走完,此時要回溯,此處用到了迴圈, 因為可能會回溯幾次,如果不用迴圈,則會跳出大的while( iIndexLst< vWalk.size()),這肯定是錯誤的.在回溯時,要出棧,恢復棋盤,重新計算可走路線ComputeWalkable(),並將iIndexLst1,以表示這條路線已經走完,可以進入下一條路線.

深度優先演算法節省記憶體,只用一個棧記錄行走路徑,以便回退即可. 但缺點是比較耗時,不適合找最短路徑.這個程式我跑了一個晚上都沒跑完,存在很大問題,所以後來我改用下面的廣度優先搜尋演算法.如下圖

(2)廣度優先搜尋演算法<原始碼見附錄2>:

此演算法是按層次遍歷,即先把當前qCurr的走法全部訪問完(訪問時需計算出下一步的所有走法,並將這些走法全部入隊qNext)後再訪問下一步的走法.如果當前qCurr訪問完畢,則訪問下一層(qNext中取),有點像雙緩衝,一前一後相互配合.每次訪問時需將當前點和下一步的走法(深度優先用iIndexLst索引記錄訪問索引值,而此處不用,因為它只需按順從佇列中取即可,效果是一樣的)入隊qNext.不過還要判斷是否到達終點.此程式中將所有的棋子走法全部儲存了下來,類似樹的結構,而程式中用的是雙鏈表,以便能夠從起點到終點對各條路線進行訪問.不過如果路徑很長,則可能很耗時,所以要剪枝,以避免訪問重複的棋盤.如下圖:

附錄1 深度優先搜尋演算法原始碼:

// ConsoleTemplate.cpp : 定義控制檯應用程式的入口點。

//

#include "stdafx.h"

#include <iostream>

#include <stack>

#include <vector>

using namespace std;

#definem8//迷宮的寬

#definen8//迷宮的長

typedefstruct

{ int x,y;

} item ;

typedef struct

{int x,y,d;//橫縱座標及方向

}DataType ;

typedef struct

{

DataType t;

int iIndex;//當前點在整個向量中的索引,以便恢復

}WalkDataWithIndex;

vector< DataType> vWalk;

typedef stack< WalkDataWithIndex> SeqStack;

int nPathShortest= 1000000;//最短的路徑長度

vector< DataType> g_ShortestPathVector;//儲存最短的路徑經過的點

//計算可走的棋子

void ComputeWalkable( int maze[m ][n ], item *move, vector< DataType> & vWalk);

intmazepath(int maze[m ][n ], item *move, int xE, int yE, vector< DataType> & vWalk)

{

//求迷宮路徑,入口引數:maze為指向迷宮陣列的指標,move為指向移動方向的指標

//開始點(x0,y0),到達點(xE,yE)

SeqStackS ;

DataTypetemp ;

WalkDataWithIndex wkIndexTem;

int x, y, d, i, j ;

int iRowNext1, iColNext1, iRowNext2, iColNext2;

ComputeWalkable( maze, move, vWalk);//計算可走的棋子

bool bFind= false;//是否找到

int iIndexLst= 0;//佇列索引

//S.push( temp);//S.Push_Stack(temp) ;

while (iIndexLst< vWalk.size() )

{

temp= vWalk[ iIndexLst];//按順序取

//更新棋盤(當前0,當前下一方向0,當前下下一方向1)

iRowNext1= temp.x+move[ temp.d].x;//下一方向

iColNext1= temp.y+move[ temp.d].y;

iRowNext2= iRowNext1+move[ temp.d].x;//下下一方向

iColNext2= iColNext1+move[ temp.d].y;

maze[ temp.x][ temp.y] = 0;

maze[ iRowNext1][ iColNext1]= 0;

maze[ iRowNext2][ iColNext2]= 1;

//當前點及方向入棧,以便恢復

wkIndexTem.t= temp;

wkIndexTem.iIndex= iIndexLst;