深度和廣度優先搜索算法

在社交網絡中,有一個六度分割理論,具體是說,世界上任何互不相識的兩人,平均只須要六步就可以創建起聯繫。一個用戶的一度鏈接用戶就是他的好友,二度鏈接用戶就是他好友的好友,三度鏈接用戶就是他好友好友的好友。算法

給定一個用戶,如何找出這個用戶的全部三度(包括一度、二度和三度)好友關係呢?數組

1. 什麼是 「搜索」 算法

咱們知道,算法都是做用於某種具體的數據結構上的,而深度優先搜索算法和廣度優先搜索算法就是做用於圖這種數據結構的。網絡

圖上的搜索算法,就是從圖中的一個頂點出發,到另外一個頂點的路徑。圖有兩種存儲方法,鄰接矩陣和鄰接表,在這裏咱們用鄰接表來存儲圖,並以無向圖做爲例子,但這兩種算法也一樣均可以應用在有向圖中。數據結構

// 無向圖
class Graph {
private:
    int v;  // 頂點個數
    vector<vector <int> > adjacent_list; // 嵌套向量來表示鄰接表
    bool found; // 深度優先搜索算法中標誌變量

public:
    Graph(int n)
    {
        v = n;
        found = false;
        for (int i = 0; i < v; i++)
        {
            vector<int> temp;
            adjacent_list.push_back(temp);
        }
    }

    // 無向圖中一條邊的兩個頂點都要存儲
    void AddEdge(int s, int t) {
        adjacent_list[s].push_back(t);
        adjacent_list[t].push_back(s);
    }

    void BFS(int s, int t);
    void Print(int prev[], int s, int t);
    void RecursiveDFS(int prev[], int visited[], int cur, int t);
    void DFS(int s, int t);
};
複製代碼

2. 廣度優先搜索(BFS)

廣度優先搜索(Breadth-First-Search),通常簡稱爲 BFS。直觀地講,它其實就是一種地毯式層層推動的搜索策略,即先查找離起始頂點最近的,而後是次近的,依次往外搜索。函數

下面這段代碼的功能是搜索一條從頂點 s 到頂點 t 的一條最短的路徑。測試

void Graph::Print(int prev[], int s, int t)
{
    if (prev[t] != -1 && t != s)
    {
        Print(prev, s, prev[t]);
    }
    cout << t << ' ';
}

// 從 s 到 t 的廣度優先搜索
void Graph::BFS(int s, int t)
{
    if (s == t) return;

    int visited[v] = {0};
    int prev[v] = {0};
    queue<int> vertex;

    visited[s] = 1;
    vertex.push(s);
    for (int i = 0; i < v; i++) prev[i] = -1;

    while(!vertex.empty())
    {
        int cur = vertex.front();
        vertex.pop();
        for (unsigned int i = 0; i < adjacent_list[cur].size(); i++)
        {
            int temp = adjacent_list[cur][i];
            if (!visited[temp])
            {
                prev[temp] = cur;
                if (temp == t)
                {
                    Print(prev, s, t);
                    return;
                }
                vertex.push(temp);
                visited[temp] = 1;
            }
        }
    }
}
複製代碼

其中,有三個很是重要的輔助變量須要特別注意。ui

  • visited,布爾數組,記錄頂點是否已經被訪問過,訪問過則爲真,沒有訪問過則爲假,這裏用 0 和 1 表示。
  • vertex,記錄上一層的頂點,也即已經被訪問但其相連的頂點尚未被訪問的頂點。當一層的頂點搜索完成後,咱們還須要經過這一層的頂點來遍歷與其相連的下一層頂點,這裏咱們用隊列來記錄上一層的頂點。
  • prev,記錄搜索路徑,保存的是當前頂點是從哪一個頂點遍歷過來的,好比 prev[4] = 1,說明頂點 4 是經過頂點 1 而被訪問到的。

下面咱們來看一下廣度優先搜索的時間複雜度和空間複雜度。spa

最壞狀況下,終止頂點 t 距離起始頂點 s 很遠,須要遍歷完整個圖才能找到。這時候,每一個頂點都要進出一遍隊列,每條邊也都會被訪問一次。因此,廣度優先搜索的時間複雜度爲 O(V+E),V 爲頂點個數,E 爲邊的條數。針對一個全部頂點都是聯通的圖,E 確定要大於 V-1,因此時間複雜度能夠簡寫爲 O(V)。code

空間複雜度主要是三個變量所佔用的額外空間,和頂點個數成正相關,爲 O(V)。cdn

3. 深度優先搜索(DFS)

深度優先搜索(Depth-First-Search),簡稱 DFS,最直觀的例子就是走迷宮。

假設你站在迷宮的某個分岔路口,你想找到出口。你隨意選擇一個岔路口來走,走着走着發現走不通的時候就原路返回到上一個分岔路口,再選擇另外一條路繼續走,直到找到出口,這種走法就是深度優先搜索的策略。

上圖中,咱們但願找到一條從 s 到 t 的路徑,其中實線表示向前遍歷,虛線表示回退。能夠看到,深度優先搜索到的並非從 s 到 t 的最短路徑。

實際上,深度優先搜索用的是一種比較著名的思想——回溯思想,這種思想很是適合用遞歸來實現。深度優先搜索的代碼裏面有幾個和廣度優先搜索同樣的部分 visited、prev 和 Print() 函數,它們的做用也都是同樣的。此外,還有一個特殊的 found 變量,標記是否找到終止頂點,找到以後咱們就能夠中止遞歸不用再繼續查找了。

void Graph::RecursiveDFS(int prev[], int visited[], int cur, int t)
{
    if (found) return;

    if (cur == t)
    {
        found = true;
        return;
    }

    for (unsigned int i = 0; i < adjacent_list[cur].size(); i++)
    {
        int temp = adjacent_list[cur][i];
        if (!visited[temp])
        {
            prev[temp] = cur;
            visited[temp] = 1;
            RecursiveDFS(prev, visited, temp, t);
        }
    }
    return;
}

// 從 s 到 t 的深度優先搜索
void Graph::DFS(int s, int t)
{
    if (s == t) return;

    int visited[v] = {0};
    int prev[v] = {0};

    visited[s] = 1;
    for (int i = 0; i < v; i++) prev[i] = -1;
    RecursiveDFS(prev, visited, s, t);

    Print(prev, s, t);
}
複製代碼

在深度優先搜索算法中,每條邊最多會被訪問兩次,一次是遍歷,一次是回退。因此,深度優先搜索的時間複雜度爲 O(E)。

visited、prev 數組的大小爲頂點個數,而遞歸函數調用棧的最大深度不會超過頂點的個數,因此深度優先搜索的空間複雜度爲 O(V)。

測試代碼以下,對應圖爲上面廣度優先搜索算法中的例圖。

int main () {
    Graph g1(8);
    g1.AddEdge(0, 1);
    g1.AddEdge(0, 3);
    g1.AddEdge(1, 4);
    g1.AddEdge(1, 2);
    g1.AddEdge(3, 4);
    g1.AddEdge(4, 5);
    g1.AddEdge(4, 6);
    g1.AddEdge(2, 5);
    g1.AddEdge(5, 7);
    g1.AddEdge(6, 7);
    //g1.BFS(3, 7);
    g1.DFS(3, 2);

    return 0;
}
複製代碼

4. 查找三度好友?

查找用戶的三度好友,也就是距離用戶 3 條邊之內的用戶。也就是說,在廣度優先算法中,咱們只須要向外查找 3 層便可,能夠經過一個數組記錄當前頂點與起始頂點的距離來實現。在深度優先算法中,咱們只須要控制最多隻從起始頂點遞歸 3 次便可,能夠經過一個變量記錄遞歸深度來實現。

參考資料-極客時間專欄《數據結構與算法之美》

獲取更多精彩,請關注「seniusen」!

相關文章
相關標籤/搜索