圖論之有權圖「帶權圖的表示及相鄰節點迭代器」

ice_moss發表於2021-05-25

一、介紹

帶權圖(Weighted Graph):帶權圖就是指圖中的每一條邊都有對應的一個或一組值,通常情況下這個值的是數值。
如:在交通運輸網中,邊上的權值可能表示的是路程,也可能表示
的是運輸費用(顯然二者都是數字)。不過,邊上的權值也有可能
是其它東西,比如說是一個字串,甚至是一個更加複雜的資料包,
裡面集合了更多的資料。

圖論之有權圖「帶權圖的表示」

二、表示方法

  1. 鄰接表
    在無權圖中鄰接表只存放了相應頂點,而現在帶權圖既需要存放頂點又需要存放權值,所以在帶權圖中的需要一個Edge的類來表示有權圖的頂點和權值,表示方法如下:
    圖論之有權圖「帶權圖的表示」
    這裡再將權值的型別寫成指標型別

圖論之有權圖「帶權圖的表示」

  1. 鄰接矩陣
    在帶權中不再使用bool值,但是這裡為了統一介面,統一將鄰接矩陣的表示寫成一個Edge類
    圖論之有權圖「帶權圖的表示」
    這裡再將權值的型別寫成指標型別

圖論之有權圖「帶權圖的表示」

三、帶權圖的實現

  1. 首先需要編寫一個Edge的類,用來表示帶全圖

    #include <iostream>
    #include <cassert>
    using namespace std;
    // 邊
    //使用模板型別Weight
    template<typename Weight>
    class Edge{
    private:
     //一條邊對應兩個端點
     int a,b; // 邊的兩個端點
    Weight weight; // 邊的權值
    public:
     // 建構函式
    Edge(int a, int b, Weight weight){
         this->a = a;
         this->b = b;
         this->weight = weight;
    }
     // 空的建構函式, 所有的成員變數都取預設值
    Edge(){}
    //解構函式
    ~Edge(){}

    成員函式

    int v(){
       return a;    // 返回第一個頂點
     }     
    int w(){
       return b;    // 返回第二個頂點
    }      
    Weight wt(){      // 返回權值
       return weight;
     }   
    
     // 給定一個頂點, 返回另一個頂點
     //other(int x)在圖的演算法中常用
    int other(int x){
       assert( x == a || x == b );
       return x == a ? b : a;
    }

    在圖中,我們會經常對物件進行運算子操作,所以在這裡需要使用運算子的過載,如下:

    // 輸出邊的資訊
    friend ostream& operator<<(ostream &os, const Edge &e){
           os<<e.a<<"-"<<e.b<<": "<<e.weight;
           return os;
    }
    // 邊的大小比較, 是對邊的權值的大小比較
    bool operator<(Edge<Weight>& e){
           return weight < e.wt();
    }
     bool operator<=(Edge<Weight>& e){
           return weight <= e.wt();
    }
     bool operator>(Edge<Weight>& e){
           return weight > e.wt();
    }
     bool operator>=(Edge<Weight>& e){
           return weight >= e.wt();
    }
     bool operator==(Edge<Weight>& e){
           return weight == e.wt();
     }
    };
  2. 稀疏圖(鄰接表)的實現
    在帶權圖中,我們需要將Edge的類的標頭檔案引入

    #include <iostream>
    #include <vector>
    #include <cassert>
    #include "Edge.h"

    需要將原來的資料型別int型別改為:<Edge<Weight>*>型別,如下:
    vector<vector<Edge<Weight> *> > g;

    
    using namespace std;
    // 稀疏圖 - 鄰接表
    template<typename Weight>
    class SparseGraph{
    private:
    int n, m; // 節點數和邊數
    bool directed; // 是否為有向圖
    vector<vector<Edge<Weight> *> > g; // 圖的具體資料
    public:
    // 建構函式
    //傳入節點個數和是否為有向圖
    SparseGraph( int n , bool directed){
       assert(n >= 0);
       this->n = n;
       this->m = 0; // 初始化沒有任何邊
       this->directed = directed;
      // g初始化為n個空的vector, 表示每一個g[i]都為空, 即沒有任和邊
       g = vector<vector<Edge<Weight> *> >(n, vector<Edge<Weight> *>());
    }
    
    // 解構函式
    ~SparseGraph(){
       for( int i = 0 ; i < n ; i ++ )
           for( int j = 0 ; j < g[i].size() ; j ++ )
               delete g[i][j];
    }
    

    成員函式

    int V(){ return n;} // 返回節點個數
    int E(){ return m;} // 返回邊的個數
    
    // 向圖中新增一個邊, 權值為weight
    void addEdge( int v, int w , Weight weight){
         assert( v >= 0 && v < n );
         assert( w >= 0 && w < n );
    
         // 注意, 由於在鄰接表的情況, 查詢是否有重邊需要遍歷整個連結串列
         // 我們的程式允許重邊的出現
         //需要開空間,Edge<Weight>型別,包括:兩個節點和權值
         g[v].push_back(new Edge<Weight>(v, w, weight));
         if( v != w && !directed )
               g[w].push_back(new Edge<Weight>(w, v, weight));
         m ++;
    }
    // 驗證圖中是否有從v到w的邊
    bool hasEdge( int v , int w ){
         assert( v >= 0 && v < n );
         assert( w >= 0 && w < n );
         for( int i = 0 ; i < g[v].size() ; i ++ ){
                if( g[v][i]->other(v) == w ){
                    return true;
                }
                return false;
         }
    }
    
    // 顯示圖的資訊
    void show(){
    
        for( int i = 0 ; i < n ; i ++ ){
            cout<<"vertex "<<i<<":\t";
            for( int j = 0 ; j < g[i].size() ; j ++ )
                  cout<<"( to:"<<g[i][j]->w()<<",wt:"<<g[i][j]->wt()<<")\t";
            cout<<endl;
        }
    }
    • 下面來看看稀疏圖的迭代器
      其實和前面一樣, 將原來的int型別改為Edge<Weight>*

      // 鄰邊迭代器, 傳入一個圖和一個頂點,
      // 迭代在這個圖中和這個頂點向連的所有邊  
      class adjIterator{
      private:
       SparseGraph &G; // 圖G的引用
       int v;
       int index;
      public:
       // 建構函式
       //傳入圖和一個節點
       adjIterator(SparseGraph &graph, int v): G(graph){
            this->v = v;
            this->index = 0;
      }
       //解構函式
       ~adjIterator(){}
      
       // 返回圖G中與頂點v相連線的第一個邊
      Edge<Weight>* begin(){
            index = 0;
            if( G.g[v].size() )
                 return G.g[v][index];
      // 若沒有頂點和v相連線, 則返回NULL
            return NULL;
      }
      // 返回圖G中與頂點v相連線的下一個邊
      Edge<Weight>* next(){
           index += 1;
           if( index < G.g[v].size() )
               return G.g[v][index];
            return NULL;
      }
      
       // 檢視是否已經迭代完了圖G中與頂點v相連線的所有頂點
       bool end(){
             return index >= G.g[v].size();
        }
       };
      };
  3. 稠密圖—鄰接矩陣的實現
    同樣需要將Edge類的標頭檔案引入

    #include <iostream>
    #include <vector>
    #include <cassert>
    #include "Edge.h"
    using namespace std;

    編寫一個類
    同理,需要將原來的bool型別改為Edge<Weight> *

    // 稠密圖 - 鄰接矩陣
    template <typename Weight>
    class DenseGraph{
    private:
       int n, m; // 節點數和邊數
       bool directed; // 是否為有向圖
       vector<vector<Edge<Weight> *>> g; // 圖的具體資料
    public:
     // 建構函式
    DenseGraph( int n , bool directed){
         assert( n >= 0 );
         this->n = n;
         this->m = 0;
         this->directed = directed;
        // g初始化為n*n的矩陣, 每一個g[i][j]指向一個邊的資訊, 初始化為NULL
          g = vector<vector<Edge<Weight> *>>(n, vector<Edge<Weight> *>(n, NULL));
    }
    
     // 解構函式
    ~DenseGraph(){
    
         for( int i = 0 ; i < n ; i ++ )
             for( int j = 0 ; j < n ; j ++ )
                 if( g[i][j] != NULL )
                     delete g[i][j];
    }

    成員函式:

    int V(){ return n;} // 返回節點個數
    int E(){ return m;} // 返回邊的個數
    // 向圖中新增一個邊, 權值為weight
    void addEdge( int v, int w , Weight weight ){
          assert( v >= 0 && v < n );
          assert( w >= 0 && w < n );
    
      // 如果從v到w已經有邊, 刪除這條邊
          if( hasEdge( v , w  ) ){
                delete g[v][w];
                if( v != w && !directed ){
                    delete g[w][v];
                }
                m --;
          }
    
         g[v][w] = new Edge<Weight>(v, w, weight);
         if( v != w && !directed ){
                g[w][v] = new Edge<Weight>(w, v, weight);
         }
         m ++;
    }
    // 驗證圖中是否有從v到w的邊
    bool hasEdge( int v , int w ){
        assert( v >= 0 && v < n );
        assert( w >= 0 && w < n );
        return g[v][w] != NULL;
    }
    // 顯示圖的資訊
    void show(){
        for( int i = 0 ; i < n ; i ++ ){
           for( int j = 0 ; j < n ; j ++ ){
               if( g[i][j] ){
                   cout<<g[i][j]->wt()<<"\t";
               }
               else 
                   cout<<"NULL\t";
           cout<<endl;
           }
      }
    }
  • 稠密圖迭代器的實現
    在迭代器中只需要把原來的bool型別改為Edge<Weight>*
// 鄰邊迭代器, 傳入一個圖和一個頂點,
// 迭代在這個圖中和這個頂點向連的所有邊
class adjIterator{
private:
    DenseGraph &G; // 圖G的引用
    int v;
    int index;

public:
    // 建構函式
  adjIterator(DenseGraph &graph, int v): G(graph){
        this->v = v;
        this->index = -1; // 索引從-1開始, 因為每次遍歷都需要呼叫一次next()
  }

    ~adjIterator(){}

// 返回圖G中與頂點v相連線的第一個邊
  Edge<Weight>* begin(){
            // 索引從-1開始, 因為每次遍歷都需要呼叫一次next()
  index = -1;
 return next();
  }

        // 返回圖G中與頂點v相連線的下一個邊
  Edge<Weight>* next(){
        // 從當前index開始向後搜尋, 直到找到一個g[v][index]為true
       for( index += 1 ; index < G.V() ; index ++ ){
              if( G.g[v][index] )
                    return G.g[v][index];
              // 若沒有頂點和v相連線, 則返回NULL
              return NULL;
      }
  }

        // 檢視是否已經迭代完了圖G中與頂點v相連線的所有邊
  bool end(){
            return index >= G.V();
    }
  };
};
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章