一、深度優先搜尋
深度優先搜尋演算法(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迴圈?好吧,你肯定錯了,其實他考的就是...