有趣的迷宮問題(遊戲) 遞迴回溯演算法詳解

2021-10-08 02:56:04 字數 4765 閱讀 6721

3、迷宮解決

4、迷宮遊戲(完整**)

傳送門:

《全國大學生計算機技能應用(2023年)——c++科目決賽程式設計題解》

迷宮問題簡介:現有乙個n * n的方格迷宮圖,相當於二維陣列,初始化中值為2表示█(牆壁),值為0表示可走空地。現在給定入口位置(starti,startj),出口位置(endi,endj)設計一種演算法,演算法中,當到達迷宮出口時,輸出走出迷宮的通路;若未到達迷宮出口,並且存在可走路徑即迷宮中為" "時,依次進行各個方向上的探索,直到找到將所有可能的結果試探完為止。如下圖:

//給出的迷宮圖,migo[1][0]為出發點,migo[7][8]為出口點

int migo[9]

[9]=

,,,,

,,,,

};

右上面進行圖形化迷宮就像這樣:

從圖中我們很直觀就看到走出迷宮的路徑,以下座標點為簡化表示,可對應陣列。出發點是(1,0),出口點(7,8)。

既然這樣,如何實現演算法找到這一條路呢?

我們看看題目,發現要求是找出迷宮通路,沒有其它約束或者額外的要求,目的明確,只要找到通路,對於不同迷宮圖,存在的通路可能不止一條。如此甚好,我們將給定的出發點視為根結點,以此搜尋子樹,對每個子通道進行深度優先搜尋,找到乙個到達目的點通路時,回溯到根再次搜尋問題解。

經過對問題解一番分析之後,不難發現,符合回溯法解題框架:遞迴回溯

(1)基本思想

回溯法基本思想:回溯法有「通用的解題法」之稱,可以系統搜尋乙個問題的所有解或者任一解,既帶有系統性又帶有跳躍性的搜尋演算法。

回溯法求問題所有解時候,要回溯到根,且此時根結點的所有子樹都已經被搜尋過才結束。

回溯法求問題乙個解時,只要搜尋到問題的乙個解就可以結束。

(2)子集樹與排列樹

剛剛提到的解空間樹,常見的兩類是子集樹和排列數。

如何來區分呢?

劃重點a.當所給問題是從n個元素的集合s中找出滿足某種性質的子集時,相應的解空間樹為子集樹。

b.當所給問題是確定n個元素滿足某種性質的排列時,其解空間為排列樹。

//回溯法子集樹一般演算法

void

backtrack

(int t)

}}

//回溯法排列樹一般演算法

void

backtrack

(int t)

}}

典型例子:

0-1揹包問題解空間就是子集樹,它是從n個物品中選擇滿足要求並且價值最大的子集。

旅行售貨員問題解空間就是排列樹,它是確定n個地點找到最佳路徑的乙個排列。

以上演算法知識的了解和題目解析之後,回頭看迷宮問題,它就是乙個很好的可以使用回溯演算法解決的問題,而且可知解空間樹為子集樹,根據上面的說法,換言之,我們要從n個可走的點找到乙個子集,作為走出迷宮的通路!

(1)遞迴出口

解題過程:

定義陣列int way_value[9][9];來設定乙個權值,用來判斷迷宮的方向,指定1代表→,2代表↓,3代表← ,4代表向↑。

運用回溯子集樹演算法思想,我們的函式宣告為void visit(int i,int j,int way),( i , j )為當前所在位置,way的值表示前進方向。首先,問題解的輸出,搜尋迷宮路徑相對應為:

/*

此為遞迴出口,如果一步步試探成功,即到達迷宮出口,則輸出迷宮圖"█"及路徑"→↓←↑"

*/if(i==endi&&j==endj)

//判斷有沒有到到達迷宮出口,列印出所有走出迷宮的方法

else

cout<<

" ";}

cout<

}}

(2)確定回溯方法

問題關鍵在於遞迴回溯的函式體部分,我們猜想,如果進入函式中,判斷傳值的點是否可走,並以此為根結點來搜尋子樹,那如何構造這個部分的函式?

我想到的方法是,既然要確定下一步走的方向,那就判斷當前點的上下左右4個方向是否訪問過並且可走,對可走的方向進一步搜尋前進方向,否則,不對此結點進一步搜尋。

//判斷當前位置右邊4個方向,如果未訪問過,則標記way_values的值,遞迴 

if(migo[i]

[j+1]==

0)//判斷右

visit

(i,j+1,

1);//對該點下一步搜尋,way=1標記方向為右

if(migo[i+1]

[j]==0)

//判斷左,以此類推

visit

(i+1

,j,2);

if(migo[i]

[j-1]==

0)visit

(i,j-1,

3);if

(migo[i-1]

[j]==0)

visit

(i-1

,j,4

);

此步驟可見得,只實現了遞迴,根據上面講到的回溯思想和演算法,搜尋結束,要回溯根結點,需要回溯上乙個狀態,所以加上migo[i][j]=0;//恢復狀態即可。

演算法完整**如下:

void

visit

(int i,

int j,

int way)

else

cout<<

" ";}

cout<

//判斷當前位置右邊4個方向,如果未訪問過,則標記way_values的值,遞迴

if(migo[i]

[j+1]==

0)//判斷右

visit

(i,j+1,

1);//對該點下一步搜尋,way=1標記方向為右

if(migo[i+1]

[j]==0)

//判斷左,以此類推

visit

(i+1

,j,2);

if(migo[i]

[j-1]==

0)visit

(i,j-1,

3);if

(migo[i-1]

[j]==0)

visit

(i-1

,j,4);

migo[i]

[j]=0;

//恢復狀態

}

迷宮問題完整程式:

#include

using

namespace std;

//初始化迷宮,點為2表示迷宮圖為"█",點為0表示迷宮圖為" "

int migo[9]

[9]=

,,,,

,,,,

};//建立出迷宮圖

int way_value[9]

[9];

//設定乙個權值,用來判斷迷宮的方向

int starti=

1,startj=0;

//設定出發點為1,0

int endi=

7,endj=8;

//出口為7,7

/*遞迴回溯演算法:

演算法中,當到達迷宮出口時,輸出走出迷宮的通路;

若未到達迷宮出口,並且存在可走路徑即迷宮中為" "時,依次進行

各個方向上的探索,直到找到將所有可能的結果試探完為止。

*/void

visit

(int i,

int j,

int way)

else

cout<<

" ";}

cout<

//判斷當前位置右邊4個方向,如果未訪問過,則標記way_values的值,遞迴

if(migo[i]

[j+1]==

0)//判斷右

visit

(i,j+1,

1);//對該點下一步搜尋,way=1標記方向為右

if(migo[i+1]

[j]==0)

//判斷左,以此類推

visit

(i+1

,j,2);

if(migo[i]

[j-1]==

0)visit

(i,j-1,

3);if

(migo[i-1]

[j]==0)

visit

(i-1

,j,4);

migo[i]

[j]=0;

//恢復狀態

}/********

main函式:

首先顯示給出的迷宮圖,然後呼叫visit函式,對迷宮進行探索

********/

intmain()

memset

(way_value,0,

sizeof

(way_value));

//初始化way_value陣列全部為0

cout<<

"迷宮路徑如下:"

;visit

(starti,startj,1)

;return0;

}

使用遞迴回溯解決迷宮問題

目的 從左上角走到右下角。地圖用陣列表示,0表示可走,1表示牆,2表示已走過 不能再走 3表示已走過但走不通。策略 向下 向右 向上 向左 初始地圖 1 1 1 1 1 1 1 1 0 0 0 0 0 1 1 0 0 0 0 0 1 1 1 1 0 1 0 1 1 0 0 1 0 0 1 1 0 0...

遞迴演算法 迷宮回溯問題

package com.czn.recursion public class migong for int i 0 i 8 i map 3 1 1 map 3 2 1 for int i 0 i 8 i system.out.println boolean setway setway map,1,1...

迷宮問題讓你深度理解遞迴 回溯)

當你看到遞迴時,如果腦子裡想著迴圈,一層層向下呼叫,一層層回溯,總想著計算機的每一步是怎麼做的,這樣就會陷入學習遞迴的思維誤區 正確的做法是什麼呢?遮蔽遞迴細節 假設a問題,可以細分為bcd這三個小問題,那麼我們就應該考慮bcd這三者怎麼解決,然後能解決之後再考慮bcd和a的關係 例 如果要解決我坐...