c++實現圖的廣度優先搜索(BFS)和深度優先搜索(DFS)

1. 基本概念

  1. 圖分爲無向圖和有向圖。
  2. 與一個頂點相鄰接的頂點數叫做該頂點的度。在有向圖中,進入一個頂點的弧叫做該頂點的入度,從一個頂點發出的弧叫做該頂點的出度。
  3. 在無向圖中,若圖中任意一對頂點都是連通的,則稱此圖是連通圖。
  4. 在有向圖中,若任意一對頂點u和v間存在一條從u到v的路徑和從v到u的路徑,則稱此圖是強連通圖。
  5. 無向圖的一個極大連通子圖稱爲該圖的一個連通分量。
  6. 有向圖的一個極大強連通子圖稱爲該圖的一個強連通分量。
  7. 在圖的每條邊上加上一個數字作權,也稱代價,帶權的圖稱網。

關於圖的基本概念實在是太多了,大家可以找一本數據結構的書看看,這裏也推薦一篇博客可以看看圖(1)——圖的定義和基本概念

2. 圖的表示方式

圖基本上有二種表達方式,分別是鄰接表和鄰接矩陣。數據結構 學習筆記(七):圖(上):圖的表示方法(鄰接表,鄰接矩陣),遍歷(DFS,BFS)

這裏我們使用的是鄰接表方法。

3. 圖的存儲結構

class Graph {
 private:
     int V; // vertex num
     std::list<int>* adj;   // adjacency list
 public:
    Graph(int V);
    ~Graph();
    void addEdge(int v, int w);
};

Graph::Graph(int V) {
    this->V = V;
    adj = new std::list<int> [V];   // init adjacency list
}

Graph::~Graph() {
    delete [] adj;  // need [] attention!
    adj = nullptr;
}

void Graph::addEdge(int v, int w) {
    adj[v].push_back(w);
}

int main()
{
    int V(5);
   	Graph g(V);     // create graph
	std::set<int> edgeInput[V];
    edgeInput[0].insert({1, 2});
    edgeInput[1].insert({3, 4});
    for (int i = 0; i < V; ++i) {
        for (auto it = edgeInput[i].begin(); it != edgeInput[i].end(); ++it) {
            g.addEdge(i, *it);     // insert edge
        }
    }
 
    return 0;
}

這裏的話我們使用鄰接表的方法,爲了方便我們這裏使用STL::list結構,這樣話我們使用std::list<int>*就可以實現了整個鄰接表的描述,這個比使用二級指針表述鄰接表要方便一點。

我們使用以下的有向無環圖作爲我們的測試例子:
在這裏插入圖片描述

4. 深度優先搜索遍歷

首先訪問圖中某一起始頂點0,然後由0出發,訪問與v鄰接且未被訪問的任一頂點1,再訪問與1鄰接且未被訪問的任一頂點3,……重複上述過程。當不能再繼續向下訪問時,依次退回到最近被訪問的頂點,若它還有鄰接頂點未被訪問過,則從該點開始繼續上述搜索過程,直到圖中所有頂點均被訪問過爲止。

DFS本質是一種遞歸調用,我們可以直接使用遞歸也可以使用棧來代替遞歸的過程,同時我們注意需要使用一個visited數組來保存我們的元素是否已經被遍歷過了。或者我們也可以使用std::set進行代替。

void Graph::DFSUtil(int v, bool* visited) {
    visited[v] = true;
    std::cout << v << " ";
    for (auto it = adj[v].begin(); it != adj[v].end(); ++it) {
        if (!visited[*it]) {
            DFSUtil(*it, visited);
        }
    }
}

void Graph::DFS() {
    bool* visited = new bool[V];
    for (int i = 0; i < V; ++i) {
        visited[i] = false;
    }
    for (int j = 0; j < V; ++j) {
        if (!visited[j]) {
            DFSUtil(j, visited);
        }
    }
}

5. 廣度優先搜索遍歷

首先訪問起始頂點0,接着由0出發,依次訪問0的各個未訪問過的鄰接頂點1,2,…,,然後再依次訪問1,2,…,的所有未被訪問過的鄰接頂點;再從這些訪問過的頂點出發,再訪問它們所有未被訪問過的鄰接頂點……依次類推,直到圖中所有頂點都被訪問過爲止。

廣度優先搜索是一種分層的查找過程,每向前走一步可能訪問一批頂點,不像深度優先搜索那樣有往回退的情況,因此它不是一個遞歸的算法。爲了實現逐層的訪問,算法必須藉助一個輔助隊列,以記錄正在訪問的頂點的下一層頂點。

void Graph::BFSUtil(int v, bool* visited) {
    std::queue<int> myqueue;
    visited[v] = true;
    myqueue.push(v);
    while (!myqueue.empty()) {
        v = myqueue.front();
        std::cout << v << " ";
        myqueue.pop();
        for (auto it = adj[v].begin(); it != adj[v].end(); ++it) {
            if (!visited[*it]) {
                visited[*it] = true;
                myqueue.push(*it);
            }
        }
    }
}

void Graph::BFS() {
    bool* visited = new bool[V];
    for (int i = 0; i < V; ++i) {
        visited[i] = false;
    }
    for (int j = 0; j < V; ++j) {
        if (!visited[j]) {
            BFSUtil(j, visited);
        }
    }
}

6. 完整代碼

#include <iostream>
#include <list>
#include <queue>
#include <set>

class Graph {
 private:
     int V; // vertex num
     std::list<int>* adj;   // adjacency list
     void BFSUtil(int v, bool* visited);
     void DFSUtil(int v, bool* visited);
 public:
    Graph(int V);
    ~Graph();
    void addEdge(int v, int w);
    void BFS();    // BFS
    void DFS();    // DFS
};

Graph::Graph(int V) {
    this->V = V;
    adj = new std::list<int> [V];   // init adjacency list
}

Graph::~Graph() {
    delete [] adj;  // need [] attention!
    adj = nullptr;
}

void Graph::addEdge(int v, int w) {
    adj[v].push_back(w);
}

void Graph::BFSUtil(int v, bool* visited) {
    std::queue<int> myqueue;
    visited[v] = true;
    myqueue.push(v);
    while (!myqueue.empty()) {
        v = myqueue.front();
        std::cout << v << " ";
        myqueue.pop();
        for (auto it = adj[v].begin(); it != adj[v].end(); ++it) {
            if (!visited[*it]) {
                visited[*it] = true;
                myqueue.push(*it);
            }
        }
    }
}

void Graph::BFS() {
    bool* visited = new bool[V];
    for (int i = 0; i < V; ++i) {
        visited[i] = false;
    }
    for (int j = 0; j < V; ++j) {
        if (!visited[j]) {
            BFSUtil(j, visited);
        }
    }
}

void Graph::DFSUtil(int v, bool* visited) {
    visited[v] = true;
    std::cout << v << " ";
    for (auto it = adj[v].begin(); it != adj[v].end(); ++it) {
        if (!visited[*it]) {
            DFSUtil(*it, visited);
        }
    }
}

void Graph::DFS() {
    bool* visited = new bool[V];
    for (int i = 0; i < V; ++i) {
        visited[i] = false;
    }
    for (int j = 0; j < V; ++j) {
        if (!visited[j]) {
            DFSUtil(j, visited);
        }
    }
}
int main()
{
    int V(5);
   	Graph g(V);     // create graph
	std::set<int> edgeInput[V];
    edgeInput[0].insert({1, 2});
    edgeInput[1].insert({3, 4});
    for (int i = 0; i < V; ++i) {
        for (auto it = edgeInput[i].begin(); it != edgeInput[i].end(); ++it) {
            g.addEdge(i, *it);     // insert edge
        }
    }
    std::cout << "DFS: " << std::endl;
    g.DFS();
    std::cout << "\nBFS: " << std::endl;
    g.BFS();
    return 0;
}

在這裏插入圖片描述

7. 參考文獻

  1. 《C++實現數據結構》:圖
  2. 廣度優先搜索的實現
  3. 深度優先搜索的實現

這裏尤其感謝參考文獻2和3,主要也是參考這個代碼的,尤其是那種圖的數據結構還是比較方便的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章