深度優先搜尋演算法的通用解法

2021-07-31 18:21:36 字數 4701 閱讀 1589

一、深度優先搜尋

深度優先搜尋演算法(depth first search),是圖論中的經典演算法。

一些經典的問題,比如八皇后、馬走日、迷宮等,都可以通過深度優先搜尋演算法來解決。

為了方便描述,下文用dfs來做為深度優先搜尋演算法的簡稱。

二、我對dfs的認識 對於dfs,我相信很多人第一次接觸很難設計出相應的演算法,即便是有不錯的程式設計經驗。我第一次幾乎沒辦法設計出解決八皇后的演算法,即便是想了很久。最後沒辦法只好參照別人寫的遞迴式的dfs。之後,雖然對這個演算法有一點了解,但由於了解不夠深度,過了幾天就記不得了,下次又完全不知道怎麼入手。然後需要再到網上搜下**,看一遍後大概才雙知道。而且發現每次寫**的時候心裡總覺得不踏實,一開始總有錯誤的地方,並且每次寫的**都有些不同。總之,寫過很多次後,依然是停留到了解的階段,沒辦法進一步提公升,特別是非遞迴式的dfs一直都停留到靠腦力記憶而不是理解的階段。

今天週末有點時間,覺得有必要解決這些問題,試著花時間去歸納總結dfs的本質,看能否做到一勞永逸。

我設定的目標是:

1、不僅停留到理解階段,而是要知道這個演算法每一步的實現

2、捉住其中的本質,給出這個演算法的設計框架。

3、在1與2的基礎中,可以熟練寫出遞迴與非遞迴兩種實現方式 。

經過乙個下午的研究,我發現任何dfs只需要通過下面幾步就可以實現,無論是遞迴還是非遞迴方式。我給這幾步分別做了乙個命名,分別是find、forward、done、back。

如下:1、find(right):在樹的當前層,橫向遍歷,嘗試找到ok的節點。(這一步通常被叫做剪枝,只留下ok的。)

2、forward(down):若在當前層找到ok的結點,並且當前層不是最後一層:把ok的節點放到當前層;進入下一層第乙個結點。跳到find

3、done(right):若在當前層找到ok的結點,並且當前層是最後一層:列印出結果;進入當前層的下乙個結點。跳到find

4、back(up):在當前層沒有找到ok的節點:返回上一層當前結點的下乙個兄弟節點。跳到find

其實最重要的是find。然後後面的forward、done、back只是用來控制搜尋走向。這四步可以進一步總結成兩步。 為了了解演算法,我想最好的切入方式是從一些例項開始。下面分別從八皇后以及馬走日等問題做為切入點來分析dfs

三、用dfs解八皇后

1、問題描述

八皇后問題是乙個以西洋棋為背景的問題:如何能夠在8×8 的西洋棋棋盤上放置八個皇后,使得任何乙個皇后都無法直接吃掉其他的皇后?

也就是說,使得棋盤中每個橫向、縱向、左上至右下斜向、右上至左下斜向均只有一枚皇后。

八皇后有92組解,下面給出其中一種解的圖例:

2、 問題分析

規則是每乙個皇后與前面的所有皇后不能在同一行、同一列、同一對角線。我們可以從第0行,第0列開始擺放,然後按照深度優先的原則,按照規則往更下面的行擺放皇后,直到擺放完8行。因為解不只乙個,當某一行(包括最後一行跟最後一行之前的所有行)的所有列都被嘗試過,再回溯返回到上一行,繼續深度優先,直到遍歷完整個棋盤的所有情況。得出所有的解。 八皇后問題可以看成是在深度為8的8叉樹中,找出所有的解。

3、**實現

遞迴演算法:

#include 

#include

/*八皇后問題是在8*8的棋盤上放置8枚皇后,使得棋盤中每個橫向、縱向、左上至右下斜向、右上至左下斜向均只有一枚皇后。

求解出所有擺法,一共有92種擺法*/

const

int n = 8; //棋盤行數

int a[n] = ; //表示棋盤,若a[2]=2,則表示在第3行第2列放乙個皇后,因為同一行不能放兩個皇后,所以只需要1維陣列就可以表示乙個棋盤。

int solution = 0;//解的個數

//row行,col列, 是否可以擺皇后

bool isok(int row, int col)

}return

true;

}void display()

else

}printf("\n");

}printf("-----------------\n");

}void dsf(int row)

else}}

//back

}int main()

非遞迴演算法:

#include 

#include

#include

using

namespace

std;

/*八皇后問題是在8*8的棋盤上放置8枚皇后,使得棋盤中每個橫向、縱向、左上至右下斜向、右上至左下斜向均只有一枚皇后*/

const

int n = 8; //棋盤行數

int a[n] = ; //表示棋盤,若a[2]=2,則表示在第3行第2列放乙個皇后,因為同一行不能放兩個皇后,所以只需要1維陣列就可以表示乙個棋盤。

int solution = 0;//解的個數

struct node

;//row行,col列, 是否可以擺皇后

bool isok(node node)

}return

true;

}//列印出所有解

void print()

else

}printf("\n");

}printf("-----------------\n");

}void dsf()

if (node.col < n)

else

}else

node = stack.top();

node.col++;

stack.pop();

stack.push(node);}}

}int main()

三、馬走日

1、問題描述

在n*n的棋盤中,馬只能走"日"字。馬從位置(0,0)出發,把棋盤的每一格都走一次且只走一次。找出所有路徑。 5*5的棋盤上,有304種解。

下面是其中一種路徑的圖例:

2、問題分析

搜尋過程是從(0,0)出發,按照深度優先的原則,從8個方向中嘗試乙個可以走的點,直到嘗試過所有的方向,走完棋盤上的所有點,得出所有的解。

馬走日問題可以看成是在層數為n*n的8叉樹中,找出所有的解。

3、**實現

同樣的,也可以把上面的演算法框架,套用於馬走日的身上。

遞迴演算法:

#include 

/*馬走日*/

const

int n = 5; //棋盤行數跟列數

int matrix[n][n] = ; //表示棋盤

int solution = 0;//解的個數

int count = 0; //第幾步

int move[8][2]=,, ,,,,,};//八個方向

//在棋盤範圍內,而且可放棋

bool isok(int x, int y)

else

}//列印出所有解

void display()

printf("\n");

}printf("-----------------\n");

}void dfs(int x, int y)

else}}

//--back

}int main()

非遞迴演算法:

#include 

#include

using

namespace

std;

/*馬走日*/

const

int n = 5; //棋盤行數跟列數

int matrix[n][n] = ; //表示棋盤

int solution = 0;//解的個數

int count = 0; //第幾步

int move[8][2]=,, ,,,,,};//八個方向

//注意find這一步當前層的的結點,結點的座標不是x與y,而通過node中的x與y與direction三者計算後得到當前層的結點

struct node

;//在棋盤範圍內,而且可放棋

bool isok(node node)

else

}//列印

void print()

printf("\n");

}printf("-----------------\n");

}void dfs()

if (node.direction < 8)

else

}else

node = stack.top();

//注意先清除當前結點的資料

x = node.x + move[node.direction][0];

y = node.y + move[node.direction][1];

matrix[x][y] = 0;

count--;

node.direction++;

stack.pop();

stack.push(node);}}

}int main()

四、dfs有更多的變種,但都可以通過上面所說的四個步驟雲解決。未完,待續。。。。

五、**:

深度優先搜尋演算法的通用解法

一 深度優先搜尋 二 我對dfs的認識 對於dfs,我相信很多人第一次接觸很難設計出相應的演算法,即便是有不錯的程式設計經驗。我第一次幾乎沒辦法設計出解決八皇后的演算法,即便是想了很久。最後沒辦法只好參照別人寫的遞迴式的dfs。之後,雖然對這個演算法有一點了解,但由於了解不夠深度,過了幾天就記不得了...

深度優先搜尋演算法

include include define vertexnum 9 struct node typedef struct node graph struct node head vertexnum 定義圖形結構 int visited vertexnum 頂點陣列 深度優先搜尋 void dfs ...

深度優先搜尋演算法

今天我們來複習一下萬能的搜尋演算法之深度優先搜尋演算法。深度優先搜尋演算法顧名思義就是按照樹的延伸不停的往下搜尋,直到樹的盡頭之後再一步一步的回溯回來。好吧,我們直接問你乙個問題,給你乙個數n,讓你輸出從1到這個樹的全排列,你會怎麼寫,會不會想到去用若干個for迴圈?好吧,你肯定錯了,其實他考的就是...