C語言中深度優(yōu)先搜索(DFS)算法的示例詳解
迷宮問題
有一個(gè)迷宮:
S**.
....
***T
(其中字符S表示起點(diǎn),字符T表示終點(diǎn),字符*表示墻壁,字符.表示平地。你需要從S出發(fā)走到T,每次只能向上下左右相鄰的位置移動(dòng),不能走出地圖,也不能穿過墻壁,每個(gè)點(diǎn)只能通過一次。)
現(xiàn)在需要你求出是否可以走出這個(gè)迷宮
我們將這個(gè)走迷宮過程稱為dfs(深度優(yōu)先搜索)算法。
思路
當(dāng)我們搜索到了某一個(gè)點(diǎn),有這樣3種情況:
1.當(dāng)前我們所在的格子就是終點(diǎn)。
2.如果不是終點(diǎn),我們枚舉向上、向下、向左、向右四個(gè)方向,依次去判斷它旁邊的四個(gè)點(diǎn)是否可以作為下一步合法的目標(biāo)點(diǎn),如果可以,那么我們就進(jìn)行這一步,走到目標(biāo)點(diǎn),然后繼續(xù)進(jìn)行操作。
3.當(dāng)然也有可能我們走到了“死胡同”里(上方、下方、左方、右方四個(gè)點(diǎn)都不是合法的目標(biāo)點(diǎn)),那么我們就回退一步,然后從上一步所在的那個(gè)格子向其他未嘗試的方向繼續(xù)枚舉。
怎樣才能算“合法的目標(biāo)點(diǎn)”?
1.必須在所給定的迷宮范圍內(nèi)
2.不能是迷宮邊界或墻。
3.這個(gè)點(diǎn)在搜索過程中沒有被走過(這樣做是因?yàn)?,如果一個(gè)點(diǎn)被允許多次訪問,那么肯定會(huì)出現(xiàn)死循環(huán)的情況——在兩個(gè)點(diǎn)之間來回走。)
實(shí)現(xiàn)代碼
#include <iostream> using namespace std; int n, m; string maze[105]; int sx, sy; bool vis[105][105]; int dir[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};//四個(gè)方向的方向數(shù)組 bool in(int x, int y) { return 0 <= x && x < n && 0 <= y && y < m; } bool dfs(int x, int y) { vis[x][y] = 1;//點(diǎn)已走過標(biāo)記 if (maze[x][y] == 'T') {//到達(dá)終點(diǎn) return 1; } for (int i = 0; i < 4; ++i) { int tx = x + dir[i][0]; int ty = y + dir[i][1]; if (in(tx, ty) && !vis[tx][ty] && maze[tx][ty] != '*') { /* 1.in(tx, ty) : 即將要訪問的點(diǎn)在迷宮內(nèi) 2.!vis[tx][ty] : 點(diǎn)沒有走過 3.maze[tx][ty] != '*' : 不是墻 */ if (dfs(tx, ty)) { return 1; } } } return 0; } int main() { cin >> n >> m; for (int i = 0; i < n; ++i) { cin >> maze[i]; } for (int i = 0; i < n; ++i) { for (int j = 0; j < m; ++j) { if (maze[i][j] == 'S') { //記錄起點(diǎn)的坐標(biāo) sx = i; sy = j; } } } if (dfs(sx, sy)) { puts("Yes"); } else { puts("No"); } return 0; }
深搜的剪枝優(yōu)化
可行性剪枝
剪枝,顧名思義,就是通過一些判斷,砍掉搜索樹上不必要的子樹。有時(shí)候,在搜索過程中我們會(huì)發(fā)現(xiàn)某個(gè)結(jié)點(diǎn)對應(yīng)的子樹的狀態(tài)都不是我們要的結(jié)果,那么我們其實(shí)沒必要對這個(gè)分支進(jìn)行搜索,直接“砍掉”這棵子樹(直接 return退出
),就是"剪枝"。
我們舉一個(gè)例子:
給定n個(gè)整數(shù),要求選出K個(gè)數(shù),使得選出來的K個(gè)數(shù)的和為sum。
如上圖,當(dāng)k=2的時(shí)候,如果已經(jīng)選了2個(gè)數(shù),再往后選更多的數(shù)是沒有意義的。所以我們可以直接減去這個(gè)搜索分支,對應(yīng)上圖中的剪刀減去的那棵子樹。
又比如,如果所有的數(shù)都是正數(shù),如果一旦發(fā)現(xiàn)當(dāng)前和的值都已經(jīng)大于sum了,那么之后不管怎么選,選擇數(shù)的和都不可能是sum了,就可以直接終止這個(gè)分支的搜索。
例:從1,2,3,?,30這30個(gè)數(shù)中選8個(gè)數(shù),使得和為200。
我們可以加如下剪枝
if (數(shù)字個(gè)數(shù) > 8) return ;
if (總和 > 200) return ;
經(jīng)過嘗逝后發(fā)現(xiàn):
沒有剪枝
加剪枝:
最優(yōu)性剪枝
我們再看一個(gè)問題:
有一個(gè)n×m大小的迷宮。其中字符S
表示起點(diǎn),字符T
表示終點(diǎn),字符*
表示墻壁,字符.
表示平地。你需要從S
出發(fā)走到T
,每次只能向上下左右相鄰的位置移動(dòng),并且不能走出地圖,也不能走進(jìn)墻壁。保證迷宮至少存在一種可行的路徑,輸出S
走到T
的最少步數(shù)。
對于求最優(yōu)解(從起點(diǎn)到終點(diǎn)的最小步數(shù))這種問題,通常可以用最優(yōu)性剪枝,比如在求解迷宮最短路的時(shí)候,如果發(fā)現(xiàn)當(dāng)前的步數(shù)已經(jīng)超過了當(dāng)前最優(yōu)解,那從當(dāng)前狀態(tài)開始的搜索都是多余的,因?yàn)檫@樣搜索下去永遠(yuǎn)都搜不到更優(yōu)的解。通過這樣的剪枝,可以省去大量冗余的計(jì)算。
此外,在搜索是否有可行解的過程中,一旦找到了一組可行解,后面所有的搜索都不必再進(jìn)行了,這算是最優(yōu)性剪枝的一個(gè)特例。
現(xiàn)在我們考慮用dfs來解決這個(gè)問題,第一個(gè)搜到的答案res并不一定是正解,但是正解一定小于等于res。于是如果當(dāng)前步數(shù)大于等于res就直接剪枝。
在dfs函數(shù)內(nèi)加入如下代碼
if (目前步數(shù) >= res) return ;
if (目前所處的位置字符 == 'T') { 答案 = 目前步數(shù);//因?yàn)槲覀冊趧偛乓呀?jīng)進(jìn)行了一次剪枝,所以我們現(xiàn)在是可以保證目前答案大于之前答案的 return ; }
好啦,到這里就結(jié)束了捏~
到此這篇關(guān)于C語言中深度優(yōu)先搜索(DFS)算法的示例詳解的文章就介紹到這了,更多相關(guān)C語言深度優(yōu)先搜索算法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談C++中虛函數(shù)實(shí)現(xiàn)原理揭秘
下面小編就為大家?guī)硪黄獪\談C++中虛函數(shù)實(shí)現(xiàn)原理揭秘。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-06-06C語言之?dāng)?shù)組名與數(shù)組起始地址的關(guān)系解析
這篇文章主要介紹了C語言之?dāng)?shù)組名與數(shù)組起始地址的關(guān)系,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07C語言實(shí)現(xiàn)整數(shù)逆序的情況解析
今天通過本文給大家介紹C語言實(shí)現(xiàn)整數(shù)逆序的情況,本文通過實(shí)例代碼多種舉例給大家介紹的非常詳細(xì),對C語言整數(shù)逆序相關(guān)知識(shí)感興趣的朋友跟隨小編一起看看吧2021-11-11淺析VSCode launch.json中的各種替換變量的意思 ${workspaceFolder} ${file} $
這篇文章主要介紹了VSCode launch.json中的各種替換變量的意思 ${workspaceFolder} ${file} ${fileBasename} ${fileDirname}等,非常不錯(cuò)具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03C++實(shí)現(xiàn)數(shù)獨(dú)快速求解
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)數(shù)獨(dú)快速求解的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03C++實(shí)現(xiàn)八個(gè)常用的排序算法 插入排序、冒泡排序、選擇排序、希爾排序等
這篇文章主要介紹了C++如何實(shí)現(xiàn)八個(gè)常用的排序算法:插入排序、冒泡排序、選擇排序、希爾排序 、快速排序、歸并排序、堆排序和LST基數(shù)排序,需要的朋友可以參考下2015-07-07