圖--(一、圖的描述與實現)--數據結構學習筆記

1. 圖簡介

1.1 圖的分類

圖分爲

  • 有向圖 (digraph): 即所有邊皆有向的圖
    在這裏插入圖片描述
  • 無向圖 (undigraph): 即所有邊皆無向的圖
    在這裏插入圖片描述
  • 混合圖(mixed graph): 即含有無向以及有向邊的圖
    在這裏插入圖片描述
    雖然圖分爲上面三種, 但是我們重點分析有向圖, 因爲所有的圖都可以由有向圖簡化得到

1.2 圖的描述

基於任意一個圖都可以由點集V 與邊集 E組成 , 我們可以對圖 G 做出如下描述

G=(V;E) G = (V;E)

  • V(vertex): V 代表圖的點集, 我們令n=Vn = |V| ,n代表頂點的個數
  • E(edge): E 代表圖的邊集, 我們令e=Ee = |E|, e代表邊的個數

1.2.1 邊的描述

我們使用 (u,v)(u,v)來描述一條邊

  • 若u,v的次序無所謂, 則稱爲 無向邊(undirected edge), 容易看出,所有邊均無方向即爲無向圖
  • 若u,v的次序需要考慮,則(u,v)稱爲有向邊(directed edge)
    • u爲邊(u,v)(u,v)的尾(tail)
    • v爲邊(u,v)(u,v)的頭(head)

1.2.2 路徑(path)/環路(circle)

路徑描述

所謂路徑, 就是我們所說的路徑, 它由有向邊構成, 所以我們可以做出如下的數學描述, 一條路徑可以由k個頂點的有序序列描述 , v0v_0 爲路徑的起點, vkv_k爲路徑的終點
π=<v0,v1,...,vk>π=k 路徑\pi = <v_0, v_1, ...,v_k>\\ 長度|\pi| = k

路徑分類
  • path:
    • simple path:即沒有重複節點的路徑, 即不存在 i j , 使得vi=vjv_i = v_j
    • circle(環路): 即v0=vkv_0 = v_k,起點與終點相同的路徑
      • simple circle: 即不包含重複節點的circle環路
    • DAG(有向無環圖): 有向但是不存在環路的圖 , 稱爲有向無環圖
    • Hamiltonian tour(哈密爾頓環路): 每個頂點經過且只經過一次的環路, 稱爲哈密爾頓環路
    • Eulerian tour(歐拉環路): 覆蓋了所有的邊,經過每一個邊一次且只經過一次的環路, 稱爲歐拉環路

2. 如何使用代碼描述圖

與一般的線性結構,樹型結構不同, 圖由邊和頂點組成, 那麼計算機中如何使用代碼描述圖呢?

2.1 鄰接矩陣和關聯矩陣

描述圖一般有兩種方式

  • 鄰接矩陣
  • 關聯矩陣

2.1.1 鄰接矩陣(adjacency matrix)

鄰接矩陣就是利用矩陣元素間關係來描述頂點關係的一種方式
行爲所有的頂點, 列也爲所有的頂點
(i,j)(i,j)位置的元素爲1,則表示第ii個頂點爲起點, 第j個頂點爲終點的邊

A B C D
A 1 1
B 1
C 1
D

例如上面的矩陣, 代表的圖如下
在這裏插入圖片描述

2.1.2 關聯矩陣(incidence matrix)

在關聯矩陣中,** 行代表頂點 , 列 代表邊** , 矩陣中元素的值表明某一頂點與某一邊是否有關聯

注意因爲一條邊最多與兩個節點關聯, 因此如果1表示有關, 0表示無關,關聯矩陣中 的列(邊)最多有兩個1,其他均爲0

在這裏插入圖片描述

2.1.3 鄰接矩陣的優點與缺點

使用鄰接矩陣來描述圖, 直觀, 有效, 我們可以很方便的描述各種圖(有向圖, 網絡等),他的空間複雜度爲O(n2)O(n^2), 空間利用率極其低下1n≈\frac{1}{n}

對於平面圖(邊與邊不相交)而言, 實際上我們的邊數都會遠小於頂點數, 邊數與頂點數由以下公式制約

在這裏插入圖片描述
---------------------------------1750-大數學家歐拉

3. 圖類定義

3.1 頂點模板類

頂點應該至少有如下屬性

  1. 頂點有三種狀態
  • UNDISCOVERED
  • DISCOVERED
  • VISITED
  1. 入度與出度:
  2. 時間標籤:
  3. 在遍歷樹中的父節點(int)
  4. 在遍歷樹中的優先級(最短通路, 極短跨邊等)(int)
    代碼如下
typedef enum { UNDISCOVERED, DISCOVERED, VISITED } VStatus; //頂點狀態

template <typename Tv> struct Vertex{
    Tv data;
    int inDegree, outDegree;
    VStatus status;
    int dTime, fTime;
    int parent;
    int priority;
    Vertex(Tv const & d):
        data(d), inDegree(0),outDegree(0), status(UNDISCOVERED),
        dTime(-1), fTime(-1), parent(-1),
        priority(INT_MAX){}
};

3.2 邊模板類

邊模板類應該具有如下屬性

  1. 邊的狀態
  • UNDETERMINED
  • TREE
  • CROSS
  • FORWARD
  • BACKWARD
  1. 數據
  2. 權重
    代碼如下
typedef enum { UNDETERMINED, TREE, CROSS, FORWARD, BACKWARD }EStatus; //邊在遍歷樹中所屬的類型

template <typename Te> struct Edge{

    Te data;
    int weiget;
    EStatus status;
    Edge(Te const & d, int w):
        data(d), weiget(w), status(UNDETERMINED){}
};

3.3 圖模板類

typedef enum { UNDISCOVERED, DISCOVERED, VISITED } VStatus; //頂點狀態
typedef enum { UNDETERMINED, TREE, CROSS, FORWARD, BACKWARD } EType; //邊在遍歷樹中所屬的類型

template <typename Tv, typename Te> //頂點類型、邊類型
class Graph { //圖Graph模板類
private:
   void reset() { //所有頂點、邊的輔助信息復位
      for ( int i = 0; i < n; i++ ) { //所有頂點的
         status ( i ) = UNDISCOVERED; dTime ( i ) = fTime ( i ) = -1; //狀態,時間標籤
         parent ( i ) = -1; priority ( i ) = INT_MAX; //(在遍歷樹中的)父節點,優先級數
         for ( int j = 0; j < n; j++ ) //所有邊的
            if ( exists ( i, j ) ) type ( i, j ) = UNDETERMINED; //類型
      }
   }
   void BFS ( int, int& ); //(連通域)廣度優先搜索算法
   void DFS ( int, int& ); //(連通域)深度優先搜索算法
   void BCC ( int, int&, Stack<int>& ); //(連通域)基於DFS的雙連通分量分解算法
   bool TSort ( int, int&, Stack<Tv>* ); //(連通域)基於DFS的拓撲排序算法
   template <typename PU> void PFS ( int, PU ); //(連通域)優先級搜索框架
public:
// 頂點
   int n; //頂點總數
   virtual int insert ( Tv const& ) = 0; //插入頂點,返回編號
   virtual Tv remove ( int ) = 0; //刪除頂點及其關聯邊,返回該頂點信息
   virtual Tv& vertex ( int ) = 0; //頂點v的數據(該頂點的確存在)
   virtual int inDegree ( int ) = 0; //頂點v的入度(該頂點的確存在)
   virtual int outDegree ( int ) = 0; //頂點v的出度(該頂點的確存在)
   virtual int firstNbr ( int ) = 0; //頂點v的首個鄰接頂點
   virtual int nextNbr ( int, int ) = 0; //頂點v的(相對於頂點j的)下一鄰接頂點
   virtual VStatus& status ( int ) = 0; //頂點v的狀態
   virtual int& dTime ( int ) = 0; //頂點v的時間標籤dTime
   virtual int& fTime ( int ) = 0; //頂點v的時間標籤fTime
   virtual int& parent ( int ) = 0; //頂點v在遍歷樹中的父親
   virtual int& priority ( int ) = 0; //頂點v在遍歷樹中的優先級數
// 邊:這裏約定,無向邊均統一轉化爲方向互逆的一對有向邊,從而將無向圖視作有向圖的特例
   int e; //邊總數
   virtual bool exists ( int, int ) = 0; //邊(v, u)是否存在
   virtual void insert ( Te const&, int, int, int ) = 0; //在頂點v和u之間插入權重爲w的邊e
   virtual Te remove ( int, int ) = 0; //刪除頂點v和u之間的邊e,返回該邊信息
   virtual EType & type ( int, int ) = 0; //邊(v, u)的類型
   virtual Te& edge ( int, int ) = 0; //邊(v, u)的數據(該邊的確存在)
   virtual int& weight ( int, int ) = 0; //邊(v, u)的權重
// 算法
   void bfs ( int ); //廣度優先搜索算法
   void dfs ( int ); //深度優先搜索算法
   void bcc ( int ); //基於DFS的雙連通分量分解算法
   Stack<Tv>* tSort ( int ); //基於DFS的拓撲排序算法
   void prim ( int ); //最小支撐樹Prim算法
   void dijkstra ( int ); //最短路徑Dijkstra算法
   template <typename PU> void pfs ( int, PU ); //優先級搜索框架
};

3.4 鄰接矩陣類模板

鄰接矩陣至少應該由兩個集合:

  1. 頂點集: 由頂點集合組成
  2. 邊集: 由鄰接矩陣所表示的邊, 爲二維向量

template <typename Tv, typename Te>
class GraphMatrix: public Graph<Tv,Te>
{
private:
    vector<Vertex<Tv>> V; //頂點集
    vector< vector<   Edge<Te >*> >E; // 由鄰接矩陣表示的邊集

public:
    int n ;
    int e;
    GraphMatrix(){n = e = 0;}
    ~GraphMatrix(){
        for(int j = 0; j < n; j++)
            for(int k = 0 ; k< n ; k++)
                delete  E[j][k];
    }
};

4. 圖類實現

4.1 頂點操作

4.1.1 鄰居枚舉

鄰居枚舉中也利用到了哨兵的思想, 由於頂點總數爲 n , 雖然在二維鄰接矩陣中最大索引爲n-1, 我們不妨定義n爲一個哨兵, 來助於我們進行訪問與遍歷

template <typename Tv, typename Te>
int GraphMatrix<Tv,Te>::nextNbr(int i , int j){
    while( (-1<j) && !exists(i,--j) );
    return j;
}

注意虛函數要實現必須在子類中聲明

4.1.2 頂點插入

頂點插入我們需要做出下面三個步驟

  1. 更新鄰接矩陣的列數
  2. 更新鄰接矩陣的行數
  3. 創建並且插入節點, 可以由下面表示的圖表示
    在這裏插入圖片描述
template <typename Tv, typename Te>
int GraphMatrix<Tv,Te>::insert(Tv const & j){
    for(int j = 0; j <n ; j++) E[j].insert(NULL); n++;
    E.insert(vector< Edge<Te>*> ( n, n ,NULL));

    return V.insert(Vertex<Tv>(vertex()));
}

4.2 邊操作

4.2.1 判斷邊是否存在

template <typename Tv, typename Te>
bool GraphMatrix<Tv, Te>::exists(int i , int j){
    return (0<=i && i<n) && (0<=j && j<n) && \
            E[i][j] !=NULL;
}

4.2.2 插入/刪除一條邊

我們需要做出如下操作

  • 如果已經有邊則直接返回 , 不操作
  • 如果沒有邊, 則新建邊插入, 並且更新對應頂點的出度與入度, 以及圖的總數
template <typename Tv, typename Te>
void GraphMatrix<Tv, Te>::insert(Te const & edge, int w, int i ,int j){
    if(exists(i,j)) return ;
    E[i][j] = new Edge<Te>(edge, w);
    e++;
    this->V[i].outDegree++;
    this->V[i].inDegree++;
}

刪除只需執行相反的操作

template <typename Tv, typename Te>
Te GraphMatrix<Tv, Te>::remove(int i ,int j){
    Te eBak = edge(i,j);
    delete  E[i][j]; E[i][j] = NULL; //刪除邊

    e--;
    this->V[i].outDegree--;
    this->V[i].inDegree--;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章