深度優先搜索與廣度優先搜索

mevincent 8年前發布 | 13K 次閱讀 深度優先搜索

今天做筆試題遇到兩個很有代表性的題目,分別用到了廣度優先搜索和深度優先搜索,可以記錄并分析一下。

廣度優先搜索

首先來看一下題目:

題目描述
定義一個二維數組N*M(其中2<=N<=10;2<=M<=10),如5 × 5數組下所示: 
int maze[5][5] = {

        0, 1, 0, 0, 0,

        0, 1, 0, 1, 0,

        0, 0, 0, 0, 0,

        0, 1, 1, 1, 0,

        0, 0, 0, 1, 0,

};
它表示一個迷宮,其中的1表示墻壁,0表示可以走的路,只能橫著走或豎著走,
不能斜著走,要求編程序找出從左上角到右下角的最短路線。
入口點為[0,0],既第一空格是可以走的路。
Input
一個N × M的二維數組,表示一個迷宮。數據保證有唯一解,不考慮有多解的情況,即迷宮只有一條通道。
Output
左上角到右下角的最短路徑,格式如樣例所示。
Sample Input
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
Sample Output
(0, 0)
(1, 0)
(2, 0)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(3, 4)
(4, 4)

從題意來看的話因為要搜索的是最短路徑,所有應該是用廣度優先搜索算法沒跑了。

廣度優先搜索(Breadth First Search, BFS),又叫寬度優先搜索,是一種窮舉的搜索算法,算法把所有展開的節點放入一個先進先出的隊列中,依次搜索解,因此,如果算法有解的話,一定是最優解或最優解之一。BFS常用隊列或鏈表實現。

直接上代碼:

#include <iostream>
#include <list>
#include <vector>
using namespace std;

typedef struct node
{
    int x;
    int y;
    int id; // 節點ID
    int patent; // 父節點ID
}node;

inline bool isvalid(int x, int y, int m, int n)
{
    if (x >= 0 && x < m &&
        y >= 0 && y < n)
        return true;
    return false;
}

int BFS(vector<vector<int>> &maze, vector<node> &node_table, int start_x, int start_y, int end_x, int end_y)
{
    // 基本思想,廣度優先,找最短路徑
    int m, n, id = 0,
        new_x, new_y;
    n = maze.size();
    m = maze[0].size();
    // open table
    list<node> open = { node_table[id] };
    id++;
    // close table
    vector<vector<bool>> visited(n, vector<bool>(m, false));
    visited[0][0] = true;
    // 四個方向
    vector<pair<int, int>> dir = { { -1, 0 },{ 0, -1 },{ 1, 0 },{ 0, 1 } };
    // 廣度優先
    while (!open.empty())
    {
        node cur = open.front(); // 每次從open表取出一個節點
        open.pop_front();
        // 已經找到
        if (cur.x == end_x && cur.y == end_y) return cur.id;
        // 四個方向擴展子節點
        for (int i = 0; i < 4; ++i)
        {
            new_x = cur.x + dir[i].first;
            new_y = cur.y + dir[i].second;
            if (isvalid(new_x, new_y, n, m) && maze[new_x][new_y] != 1 && !visited[new_x][new_y])
            {
                node &tmp = node_table[id];
                tmp.x = new_x;
                tmp.y = new_y;
                tmp.id = id;
                tmp.patent = cur.id;
                open.push_back(tmp);
                visited[new_x][new_y] = true;
                id++;
            }
        }
    }
    return -1;
}

// 遞歸輸出路徑
void print_path(vector<node> &node_table, int id)
{
    if (node_table[id].patent != -1)
        print_path(node_table, node_table[id].patent);

    cout << "(" << node_table[id].x << "," << node_table[id].y << ")" << endl;
}

int main(void)
{
    int n, m, id;
    while (cin >> n >> m)
    {
        vector<vector<int>> maze(n, vector<int>(m, 0));
        // node 表
        vector<node> node_table(m * n, { 0, 0, 0, -1 });
        for (int i = 0; i < n; ++i)
        {
            for (int j = 0; j < m; ++j)
            {
                cin >> maze[i][j];
            }
        }
        id = BFS(maze, node_table, 0, 0, n - 1, m - 1);
        print_path(node_table, id);
    }
    return 0;
}

可以看到代表并不是很復雜,BFS算法都在BFS這個函數中,而node這個結構主要是用來記錄路徑用的,便于找到路徑之后通過遞歸展開輸出路徑。BFS函數中的思路也比較清晰,不斷從open表頭出去節點,并把擴展的子節點加入到open表尾部,即先進先出的思想。

由于BFS中已經被訪問過的節點如果第二次被訪問,那么第二次訪問時候的路徑長度必然大于(或等于)第一次訪問時的路徑長度,因此就第二次訪問的時候就不需要考慮該節點了,所以我們只需要記錄被訪問過的節點,而不用撤銷訪問(和DFS有所區別)。

深度優先搜索

如果說廣度優先搜索適合搜索最優解,那么深度優先搜索就是適合搜索是否存在解。還是先來看問題:

請設計一個函數,用來判斷在一個矩陣中是否存在一條包含某字符串所有字符的路徑。
路徑可以從矩陣中的任意一個格子開始,每一步可以在矩陣中向左,向右,向上,向下移動一個格子。
如果一條路徑經過了矩陣中的某一個格子,則該路徑不能再進入該格子。 
例如:
abce
sfcs
adee 
矩陣中包含一條字符串"bcced"的路徑,但是矩陣中不包含"abcb"路徑,
因為字符串的第一個字符b占據了矩陣中的第一行第二個格子之后,路徑不能再次進入該格子。

從題意可以看出,需要找到是否包含該路徑,也就是找到是否存在解,所以此處用深度優先搜索比較合適。深度優先搜索展開子節點后把子節點加入到open表的頭部,因此適合用遞歸來實現。

直接上代碼:

#include <iostream>
#include <list>
#include <vector>
using namespace std;

class Solution {
public:
    bool hasPath(char* matrix, int rows, int cols, char* str)
 {
        this->matrix = matrix;
        this->str = str;
        this->rows = rows;
        this->cols = cols;
        // visited
        vector<vector<bool>> visited(rows, vector<bool>(cols, false));
        // 尋找迷宮起點
        for (int i = 0; i < rows * cols; ++i)
        {
            if (matrix[i] == str[0])
            {
                visited[i / cols][i % cols] = true;
                if (DFS(i / cols, i % cols, 0, visited)) return true;
                visited[i / cols][i % cols] = false;
            }
        }
        return false;
    }

    bool isvalid(int x, int y)
 {
        if (x >= 0 && x < rows && y >= 0 && y < cols)
            return true;
        return false;
    }

    bool DFS(int x, int y, int str_index, vector<vector<bool>> &visited)
 {
        // 四個方向
        static pair<int, int> dir[] = { {-1, 0}, {0, -1}, {1, 0}, {0, 1} };
        int new_x, new_y;

        if (str[str_index] == matrix[x * cols + y])
        {
            if (str[str_index + 1] == '\0') return true;
            for (int i = 0; i < 4; ++i)
            {
                new_x = x + dir[i].first;
                new_y = y + dir[i].second;
                if (isvalid(new_x, new_y) && !visited[new_x][new_y])
                {
                    visited[new_x][new_y] = true;
                    if (DFS(new_x, new_y, str_index + 1, visited))
                        return true;
                    visited[new_x][new_y] = false;
                }
            }
        }
        return false;
    }
private:
    int rows;
    int cols;
    char *matrix;
    char *str;
};

int main(void)
{
    Solution sss;
    cout << sss.hasPath("abcesfcsadee", 3, 4, "abcd");
    return 0;
}

可以看到,DFS的代碼和BFS的代碼十分類似。說一下區別:1. DFS采用遞歸來實現; 2. 訪問表在進入函數之間置位,而在退出函數之后需要復位。原因是因為DFS在訪問的時候有一個回溯的過程,如果子節點沒有得到需要的解,那么就需要回溯的父節點,并擴展父節點的其他子節點來搜索解,因此原來被子節點訪問過的位置現在應該撤銷訪問。

總結

BFS和DFS是兩種十分重要的搜索算法,BFS適合查找最優解,DFS適合查找是否存在解(或者說能找到任意一個可行解)。

 

來自:http://yosef-gao.github.io/2016/08/06/bfs-and-dfs/

 

 本文由用戶 mevincent 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!