深度優先遍歷(DFS)和廣度優先遍歷(BFS)


1 圖的兩種存儲方式

1.1 鄰接矩陣(Adjacency Matrix)

1.1.1 原理

用一維數組存儲圖中頂點信息;用二維數組(矩陣)存儲圖中的邊和弧的信息。對於無向圖來說,如果頂點i與頂點j之間有邊,就將A[i][j]和A[j][i]標記為1;對於有向圖來說,如果頂點i和頂點j之間,有一條箭頭從頂點i指向頂點j的邊,就將A[i][j]標記為1,有箭頭從頂點j指向頂點i的邊,就將A[j][i]標記為1。對於有權圖,數組中存儲相應權重。

鄰接矩陣可表示為:

vertex[4] = {v0, v1, v2, v3};

edge[4][4] = { {0, 1, 1, 0}, 

                      {1, 0, 0, 1},

                      {1, 0, 0, 1},

                      {0, 1, 1, 0} };

1.1.2 優缺點

1.1.2.1 優點

1)基於數組,存儲方式簡單、直接,獲取頂點關系時非常高效;

2)計算方便,鄰接矩陣的方式存儲圖,可以將很多圖的運算轉換成矩陣之間的運算。

1.1.2.2 缺點

浪費存儲空間。對於無向圖來說,如果A[i][j]為1,那么A[j][i]也為1,實際上,我們只需要存儲一個就可以了;如果我們存儲的是稀疏圖(sparse matrix),也就是頂點很多,但每個頂點的邊不多,那就更加浪費空間了。

1.1.3 適用場景

1)要求較高速的計算速率;2)運行內存充足;3)圖的頂點個數n值較小;4)有向圖且為稠密圖;

1.1.4 鄰接矩陣存儲圖的代碼實現

#ifndef GRAPH_H
#define GRAPH_H

#define MAX_VERTEX (10)

//鄰接矩陣圖
class AdjacencyMatrixGraph {
private:
    int m_nVertex[MAX_VERTEX];    //頂點數組
    int m_nEdge[MAX_VERTEX][MAX_VERTEX];    //鄰接矩陣
    int m_nCurrentVertex;    //當前圖中頂點個數
    int m_nCurrentEdge;    //當前圖中邊的個數
    bool visited[MAX_VERTEX];    //訪問數組

public:   
    void CreatGraph();    //創建鄰接矩陣圖
    void DisplayGraph();    //以鄰接矩陣形式顯示圖
    void MatrixDFS();    //深度優先遍歷
    void MDFS(int i);    //深度優先遍歷遞歸調用
    void MatrixBFS();    //廣度優先遍歷
};

#endif
/* 創建鄰接矩陣圖
 *     1----2
 *   /  \  / |\
 * 6     /\  | 3
 *   \  /   \|/
 *     5---- 4
 * 頂點 6 個、邊為 1-2/1-4/1-6/2-3/2-4/2-5/3-4/4-5/5-6共9條
 */
void AdjacencyMatrixGraph::CreatGraph() {
    //輸入頂點個數、邊數
    std::cout << "輸入頂點個數、邊數:" << std::endl;
    std::cin >> m_nCurrentVertex >> m_nCurrentEdge;
    
    //初始化頂點數組,數組值為下表加1
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        m_nVertex[i] = i + 1;
    }
    //初始化鄰接矩陣
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        for (int j = 0; j < m_nCurrentVertex; ++j) {
            m_nEdge[i][j] = 0;
        }
    }
    //初始化圖
    int m, n;
    for (int i = 0; i < m_nCurrentEdge; ++i) {
        std::cout << "輸入頂點m、鄰接點n" << std::endl;
        std::cin >> m >> n;
        m_nEdge[m - 1][n - 1] = m_nEdge[n - 1][m - 1] = 1;
    }
}

//以鄰接矩陣形式顯示圖
void AdjacencyMatrixGraph::DisplayGraph() {
    //顯示頂點
    std::cout << "頂點有:" << std::endl;
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        std::cout << m_nVertex[i] << " | ";
    }
    std::cout << std::endl;

    /*顯示鄰接矩陣
     *   1 2 3 4 5 6
     * 1 0 1 0 1 0 1
     * 2 1 0 1 1 1 0
     * 3 0 1 0 1 0 0
     * 4 1 1 1 0 1 0
     * 5 0 1 0 1 0 1
     * 6 1 0 0 0 1 0
     */
    std::cout << "  ";
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        std::cout << m_nVertex[i] << ' ';
    }
    std::cout << std::endl;
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        std::cout << i + 1 << ' ';
        for (int j = 0; j < m_nCurrentVertex; ++j) {
            std::cout << m_nEdge[i][j] << ' ';
        }
        std::cout << std::endl;
    }
}

 

1.2 鄰接表(Adjacency List)

1.2.1 原理

鄰接表類似於散列表的拉鏈表示法。每個頂點對應一條鏈表,鏈表中存儲的是與這個頂點相連接的其他頂點。以上圖為例,鄰接表可表示為:

每條鏈表的頭結點組成頂點的數組。

1.2.2 優缺點

1)優點:節省內存空間;2)缺點:鄰接表使用起來比較耗時,鏈表的存儲方式堆緩存不友好。

1.2.3 使用場景

1)有內存限制;2)對運行速度要求不高;3)頂點個數較大時;

1.2.4 鄰接表存儲圖的代碼實現

#ifndef GRAPH_H
#define GRAPH_H

//鄰接表圖
typedef struct AdjacencyNode {    //鄰接點元素
    int m_nAdjacencyNode;    //鄰接點值
    AdjacencyNode* m_pNextNode; 
}* AdjacencyNodePtr;

typedef struct VertexNode {    //頂點數組元素
    int m_nVertex;    //頂點值
    AdjacencyNodePtr m_pFirstAdjacencyNode;    //頂點的第一鄰接點
}* VertexNodePtr;

class AdjacencyListGraph {
private:
    VertexNodePtr m_pVertexArr;    //指向頂點數組
    int m_nCurrentVertex;
    int m_nCurrentEdge;
    bool* visited;    //指向訪問數組

public:
    void CreatGraph();    //創建鄰接表圖
    void DisplayGraph();    //以鄰接表形式顯示圖
    void ListDFS();    //鄰接表的深度優先遍歷
    void LDFS(int n);    //深度優先遍歷遞歸函數
    void ListBFS();    //鄰接表的廣度優先遍歷
};

#endif
/* 創建鄰接矩陣圖
 *     1----2
 *   /  \  / |\
 * 6     /\  | 3
 *   \  /   \|/
 *     5---- 4
 * 頂點 6 個、邊為 1-2/1-4/1-6/2-3/2-4/2-5/3-4/4-5/5-6共9條
 */
void AdjacencyListGraph::CreatGraph() {
    std::cout << "輸入頂點個數、邊數" << std::endl;
    std::cin >> m_nCurrentVertex >> m_nCurrentEdge;
    m_pVertexArr = new VertexNode[m_nCurrentVertex];
    
    //初始化頂點數組
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        m_pVertexArr[i].m_nVertex = i + 1;
        m_pVertexArr[i].m_pFirstAdjacencyNode = nullptr;
    }

    //初始化鄰接表
    for (int i = 0; i < m_nCurrentEdge; ++i) {
        int m, n;
        std::cout << "輸入邊(m, n)" << std::endl;
        std::cin >> m >> n;

        //頂點m的鄰接表
        AdjacencyNodePtr pNewAdjacencyNode = new AdjacencyNode;
        pNewAdjacencyNode->m_pNextNode = m_pVertexArr[m - 1].m_pFirstAdjacencyNode;
        pNewAdjacencyNode->m_nAdjacencyNode = n;
        m_pVertexArr[m - 1].m_pFirstAdjacencyNode = pNewAdjacencyNode;

        //頂點n的鄰接表
        pNewAdjacencyNode = new AdjacencyNode;
        pNewAdjacencyNode->m_pNextNode = m_pVertexArr[n - 1].m_pFirstAdjacencyNode;
        pNewAdjacencyNode->m_nAdjacencyNode = m;
        m_pVertexArr[n - 1].m_pFirstAdjacencyNode = pNewAdjacencyNode;
    }
}

//已鄰接表顯示圖
void AdjacencyListGraph::DisplayGraph() {
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        //顯示樣例:1->2->4->6
        std::cout << m_pVertexArr[i].m_nVertex << "->";
        AdjacencyNodePtr pWorkNode = m_pVertexArr[i].m_pFirstAdjacencyNode;
        while (pWorkNode) {
            std::cout << pWorkNode->m_nAdjacencyNode;
            pWorkNode = pWorkNode->m_pNextNode;
            if (pWorkNode)    std::cout << "->";
        }
        std::cout << std::endl;
    }
}

 

2 深度優先遍歷(DFS)

2.1 原理

深度優先遍歷(depth first search)圖的方式類似於先序遍歷二叉樹。從圖中的某個頂點出發,訪問次頂點,然后從該頂點的未被訪問的鄰接點出發深度優先遍歷,直到圖中所有和該頂點相通的點都被訪問到。

2.2 實現及關鍵點

2.2.1 關鍵點

1)由原理可知,深度優先遍歷的實現依賴於遞歸思想,因此特別注意遞歸公式和遞歸終止條件;

2)由於二叉樹的層次關系,先序遍歷二叉樹時是層層遞進的,不會重復訪問已訪問過的節點;但是圖並沒有層次關系,所以要有標記—訪問數組,記錄已訪問過的頂點,防止頂點的重復訪問。

2.2.2 實現

//Adjacency Matrix
//深度優先遍歷
void AdjacencyMatrixGraph::MDFS(int i) {
    visited[i] = true;
    std::cout << m_nVertex[i] << " | ";    //打印路徑
    for (int j = 0; j < m_nCurrentVertex; ++j) {
        if (!visited[j] && m_nEdge[i][j]) {  //鄰接點未被訪問且存在邊
            MDFS(j);
        }
    }
}
void AdjacencyMatrixGraph::MatrixDFS() {
    //設置訪問數組,訪問過的頂點設置為true
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        visited[i] = false;
    }

    //從其中一個頂點開始遍歷圖
    MDFS(0);
    std::cout << std::endl;
}



//Adjacency List
void AdjacencyListGraph::LDFS(int n) {
    visited[n] = true;    //當前遍歷頂點置true,表示已訪問過
    std::cout << m_pVertexArr[n].m_nVertex << " | ";

    //遍歷當前頂點各個鄰接點
    AdjacencyNodePtr pWorkNode = m_pVertexArr[n].m_pFirstAdjacencyNode;
    int vertex;
    while (pWorkNode) {
        vertex = pWorkNode->m_nAdjacencyNode;    //鄰接點
        //如果頂點還未被訪問
        if (!visited[vertex - 1]) {
            LDFS(vertex - 1);
        }
        else {
            pWorkNode = pWorkNode->m_pNextNode;
        }
    }
}
void AdjacencyListGraph::ListDFS() {
    //初始化訪問數組
    visited = new bool[m_nCurrentVertex];
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        visited[i] = false;
    }
    //任選一個頂點開始遍歷
    LDFS(0);
    std::cout << std::endl;
}

2.3 復雜度分析

2.3.1 鄰接矩陣存儲方式

1)時間復雜度

遍歷每個頂點時,都要判斷其余頂點是否與該頂點相鄰,所以要遍歷整個鄰接矩陣中的所有元素,故時間復雜度為O(V2),V為頂點個數。

2)空間復雜度

遍歷過程中需要訪問數組來標記頂點是否被訪問,訪問數組大小等於頂點個數V,所以空間復雜度為O(V)。

2.3.2 鄰接表存儲方式

1)時間復雜度

鄰接表的存儲方式會先訪問頂點,然后再判斷鏈表中的每個鄰接點(邊),每條會訪問2次,所以時間復雜度為O(V+2E),V為頂點個數、E為邊的個數。

2)空間復雜度

同鄰接矩陣,空間復雜度為O(V)。

 

3 廣度優先遍歷(BFS)

3.1 原理

廣度優先遍歷(breadth first search)可以類比為二叉樹的層序遍歷;先查找離起始頂點最近的,然后是次進的,依次往外搜索。

3.2 實現及關鍵點

3.2.1 關鍵點

1)借助隊列將當前遍歷頂點的鄰接點存儲起來,使得搜索路徑有層次;

2)同深度優先搜索中,需要借助訪問數組標記已訪問頂點;

2)頂點元素入隊時要將訪問數組相關頂點標記為已訪問,出隊時執行會造成頂點的重復訪問。

//Adjacency Matrix
void AdjacencyMatrixGraph::MatrixBFS() {
    //初始化訪問數組
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        visited[i] = false;
    }

    std::queue<int> vertexQueue;    //存放已遍歷頂點的鄰接點
    int frontVertex = 0;    //隊列中首元素
    visited[0] = true;
    vertexQueue.push(0);    //將頂點1放入隊列中
    while (!vertexQueue.empty()) {
        frontVertex = vertexQueue.front();
        //打印首元素
        std::cout << frontVertex + 1 << " | ";
        //將鄰接點加入隊列
        for (int i = 0; i < m_nCurrentVertex; ++i) {
            if (!visited[i] && m_nEdge[frontVertex][i]) {
                visited[i] = true;    //注意!訪問數組先置位再將頂點下標放入隊列,否則會造成重復
                vertexQueue.push(i);
            }
        }
        //首元素出列
        vertexQueue.pop();
    }
    std::cout << std::endl;
}



//Adjacency List
void AdjacencyListGraph::ListBFS() {
    //初始化訪問數組
    visited = new bool[m_nCurrentVertex];
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        visited[i] = false;
    }

    std::queue<int> vertexQueue;    //存放已訪問頂點的鄰接點
    int vertex = m_pVertexArr[0].m_nVertex;
    vertexQueue.push(vertex);    //將遍歷起始點放入隊列
    visited[vertex - 1] = true;
    std::cout << vertex << " | ";
    while (!vertexQueue.empty()) {
        vertex = vertexQueue.front();
        vertexQueue.pop();    
        AdjacencyNodePtr pWorkNode = m_pVertexArr[vertex - 1].m_pFirstAdjacencyNode;
        while (pWorkNode) {
            vertex = pWorkNode->m_nAdjacencyNode;
            if (!visited[vertex - 1]) {
                vertexQueue.push(vertex);
                visited[vertex - 1] = true;
                std::cout << vertex << " | ";
            }
            pWorkNode = pWorkNode->m_pNextNode;
        }
    }
    std::cout << std::endl;
}

2.4 復雜度分析

2.4.1 鄰接矩陣存儲方式

1)時間復雜度

遍歷過程中,每個頂點出隊時,都要判斷與該頂點相鄰的所有頂點是否已訪問,因為遍歷整個圖,所以所有頂點都要有入隊、出隊操作,所以時間復雜度為O(V2)。

2)空間復雜度

遍歷操作用到訪問數組與隊列,隊列長度不超過頂點個數,所以空間復雜為O(V)。

2.4.2 鄰接表存儲方式

1)時間復雜度

同樣的,每個頂點出隊時,都要遍歷其鄰接點鏈表(邊),所以時間復雜度為O(V+2E)。

2)空間復雜度

同鄰接矩陣存儲方式,空間復雜度為O(V)。

 

該篇博客是自己的學習博客,水平有限,如果有哪里理解不對的地方,希望大家可以指正!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM