圖論系列之「廣度優先遍歷及無權圖的最短路徑(ShortPath)」

ice_moss發表於2021-05-24

一、介紹

  1. 廣度優先遍歷
    廣度優先遍歷從某個頂點 v 出發,首先訪問這個結點,並將其標記為已訪問過,然後順序訪問結點v的所有未被訪問的鄰接點 {vi,..,vj} ,並將其標記為已訪問過,然後將 {vi,…,vj} 中的每一個節點重複節點v的訪問方法,直到所有結點都被訪問完為止。分為三個步驟:
  • 使用一個輔助佇列,首先將第一個節點v放入佇列,並標記也被訪問,然後檢測佇列是否為空
  • 如果佇列不為空時,將佇列的第一個元素取出,並將與該節點相連且未被訪問的節點加入佇列,並將這些節點進行標記
  • 當佇列為空時,就完成了圖的廣度優先遍歷
  1. 無權圖的最短路徑
    無權圖的最短路勁,是基於圖的廣度優先遍歷而來的,是指圖中兩節點間最短的路徑,透過ord[i]陣列用來記錄上一個節點到i節點的路徑

二、遍歷過程

下面直接來看看具體的例子:
對下圖進行廣度優先遍歷
圖論系列之「廣度優先遍歷」

從節點0開始
將0放入佇列中,並對節點0進行標記
圖論系列之「廣度優先遍歷」

當佇列不為空時,就從佇列中拿出首元素,即節點0;然後將和節點0兩連並未被訪問過的節點依次放入佇列中,並進行標記,如下圖:
圖論系列之「廣度優先遍歷」

同理,此時佇列不為空,就拿出佇列中的首元素節點1,此時沒有和節點1相連且未被訪問過的節點,就沒有節點進入佇列,然後進入下一輪的遍歷。
圖論系列之「廣度優先遍歷」

佇列不為空,取出佇列首元素節點2,同理(參考節點1的出佇列),進入下一輪遍歷
圖論系列之「廣度優先遍歷」

佇列不為空,取出佇列首元素節點5,並將和5相連且未被訪問的節點放入佇列,並標記,如下圖:
圖論系列之「廣度優先遍歷」

佇列不為空,拿出佇列首元素節點6,此時節點6相連的且未被訪問過的節點就為空了,進入下一輪遍歷
圖論系列之「廣度優先遍歷」

同理,遍歷節點3:
圖論系列之「廣度優先遍歷」

同理,遍歷節點4;此時佇列為空,遍歷已就完成了

圖論系列之「廣度優先遍歷」

三、程式碼實現

編寫一個類,成員變數及其初始化:

#include <iostream>
#include <queue>
#include <stack>
#include <vector>
#include <cassert>

using namespace std;

//尋找無權圖的最短路徑
template <typename Graph>   //封裝為統一介面
class ShortestPath{

private:
    Graph &G; //圖是引用
    int s; //起始點
    bool *visited; //記錄dfs的過程中節點是否被標記
    int *from; //記錄路徑,from[i]表示查詢的路徑上i的上一個節點
    int *ord; //記錄路徑中節點的次序,ord[i]表示i節點在路徑中的次序

public:
    //建構函式
  ShortestPath(Graph &graph, int s):G(graph){

       //演算法初始化
       assert( s >= 0 && s < graph.V() );
       this->s = s;
       visited = new bool[graph.v()];
       from = new int[graph.v()];
       ord = new int[graph.v()];
       for(int i = 0; i < graph.v(); i++){
            visited[i] = false;
            from[i] = -1;
            ord[i] = -1;
  }

廣度優先遍歷演算法:

// 無向圖最短路徑演算法, 從s開始廣度優先遍歷整張圖
  queue<int> q;    //q為輔助佇列

  q.push( s );
  visited[s] = true;
  ord[s] = 0;
  while( !q.empty() ){
        //將佇列中的首元素賦值給v
        int v = q.front();
        q.pop(); //將第一個元素取出佇列

        typename Graph::adjIterator adj(G, v);
        for( int i = adj.begin(); !adj.end(); i = adj.next() ){
                if( !visited[i] ){    //判斷節點是否被訪問過
                    q.push(i);
                    visited[i] = true;
                    from[i] = v;
                    ord[i]  = ord[v] + 1;      //記錄最短路徑
                }
         }
    }
}

解構函式及其成員函式

//解構函式
  ~ShortestPath(){
        delete[] visited;
        delete[] from;
        delete[] ord;
  }
    // 查詢從s點到w點是否有路徑
  bool hasPath(int w) {
        assert(w >= 0 && w < G.V());
        return visited[w];
  }

   // 查詢從s點到w點的路徑, 存放在vec中
  void path(int w,vector<int> vec){
        assert(w >= 0 && w < G.V());
        stack<int> s;
        // 透過from陣列逆向查詢到從s到w的路徑, 存放到棧中
        int p = w;
        while(p != -1){
            s.push(p);
            p = from[p];
        }

        // 從棧中依次取出元素, 獲得順序的從s到w的路徑
       vec.clear();
       while( !s.empty() ){
                vec.push_back( s.top());
                s.pop();
       }
    }

    // 列印出從s點到w點的路徑
  void showPath(int w){

        assert( w >= 0 && w < G.V() );

        vector<int> vec;
        path(w, vec);
        for( int i = 0 ; i < vec.size() ; i ++ ){
                cout<<vec[i];
              if( i == vec.size()-1 )
                    cout<<endl;
              else  cout<<" -> ";
       }
    }

    // 檢視從s點到w點的最短路徑長度
  int length(int w){
        assert( w >= 0 && w < G.V() );
        return ord[w];
  }
};





本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章