1. 程式人生 > >資料結構(C++)——圖:基於鄰接矩陣實現的圖結構

資料結構(C++)——圖:基於鄰接矩陣實現的圖結構

抽象資料型別

操作介面:圖支援的操作介面分為邊和頂點兩類

Graph模板類

typedef enum { UNDISCOVERED, DISCOVERED, VISITED } VStatus; //頂點狀態
typedef enum { UNDETERMINED, TREE, CROSS, FORWARD, BACKWARD } EStatus; //邊狀態

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)) status(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;
    virtual Te remove(int, int) = 0; //刪除頂點v和u之間的邊e,返回該邊資訊
    virtual EStatus& status(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); //優先順序搜尋框架
};

鄰接矩陣

原理

鄰接矩陣(adjacency matrix)是圖ADT最基本的實現方式,使用方陣A[n][n]表示由n頂點構成的圖,其中每個單元,各自負責描述一對頂點之間可能存在的鄰接關係,故此得名。

對於無權圖,存在從頂點u到v的邊,當且僅當A[u][v] =1

a、b、c分別對應無向圖、有向圖、網路的鄰接矩陣例項

基於鄰接矩陣實現的圖結構

#include "../Vector/Vector.h" //引入向量
#include "../Graph/Graph.h" //引入圖ADT

template <typename Tv> struct Vertex { //頂點物件(為簡化起見,並未嚴格封裝) 
    Tv data; int inDegree, outDegree; VStatus status; //資料、出入度數、狀態
    int dTime, fTime; //時間標籤
    int parent; int priority; //在遍歷樹中的父節點、優先順序數
    Vertex(Tv const& d = (Tv) 0) :  //構造新頂點
        data(d), inDegree(0), outDegree(0), status(UNDISCOVERED),
        dTime(-1), fTime(-1), parent(-1), priority(INT_MAX) {} //暫不考慮權重溢位
};

template <typename Te> struct Edge { //邊物件(為簡化起見,並未嚴格封裝)
    Te data; int weight; EStatus status; //資料、權重、狀態
    Edge(Te const& d, int w) : data(d), weight(w), status(UNDETERMINED) {}      //構造新邊
};

template <typename Tv, typename Te> //頂點型別、邊型別
class GraphMatrix : public Graph<Tv, Te> { //基於向量,以鄰接矩陣形式實現的圖
private:
    Vector<Vertex<Tv>> V; //頂點集(向量)
    Vector<Vector<Edge<Te>*>> E; //邊集(鄰接矩陣)
public:
    GraphMatrix() { n = e = 0; } //構造
    ~GraphMatrix() { //析構
        for (int j = 0; j < n; j++)     //所有動態建立的
            for (int k = 0; k < n; k++) //邊記錄
                delete E[j][k]; //逐條清除
}
// 頂點的基本操作:查詢第i個頂點(0 <= i < n)
    virtual Tv& vertex(int i) { return V[i].data; } //資料
    virtual int inDegree(int i) { return V[i].inDegree; } //入度
    virtual int outDegree(int i) { return V[i].outDegree; } //出度
    virtual int firstNbr(int i) { return nextNbr(i, n); } //首個鄰接頂點
    virtual int nextNbr(int i, int j) //相對於頂點j的下一鄰接頂點
    { while ((-1 < j) && (!exists(i, --j))); return j; } //逆向線性試探(改用鄰接表可提高效率)
    virtual VStatus& status(int i) { return V[i].status; } //狀態
    virtual int& dTime(int i) { return V[i].dTime; } //時間標籤dTime
    virtual int& fTime(int i) { return V[i].fTime; } //時間標籤fTime
    virtual int& parent(int i) { return V[i].parent; } //在遍歷樹中的父親
    virtual int& priority(int i) { return V[i].priority; } //在遍歷樹中的優先順序數
// 頂點的動態操作
    virtual int insert(Tv const& vertex) {
        for (int j = 0; j < n; j++) E[j].insert(NULL); n++; //各頂點預留一條潛在的關聯聯邊
        E.insert(Vector<Edge<Te>*>(n, n, (Edge<Te>*) NULL)); //建立新頂點對應的邊向量
        return V.insert(Vertex<Tv>(vertex)); //頂點向量增加一個頂點
}
    virtual Tv remove(int i) { //刪除第i個頂點及其關聯邊(0 <= i < n)
        for (int j = 0; j < n; j++) //所有出邊
            if (exists(i, j)) { delete E[i][j]; V[j].inDegree--; } //逐條刪除
        E.remove(i); n--; //刪除第i行
        for (int j = 0; j < n; j++) //所有出邊
            if (exists(j, i)) { delete E[j].remove(i); V[j].outDegree--; } //逐條刪除
        Tv vBak = vertex(i); V.remove(i); //刪除頂點i
        return vBak; //返回被刪除頂點的資訊
}
// 邊的確認操作
    virtual bool exists(int i, int j) //邊(i, j)是否存在
    { return (0 <= i) && (i < n) && (0 <= j) && (j < n) && E[i][j] != NULL; }
// 邊的基本操作:查詢頂點i與j之間的聯邊(0 <= i, j < n且exists(i, j))
    virtual EStatus& status(int i, int j) { return E[i][j]->status; } //邊(i, j)的狀態
    virtual Te& edge(int i, int j) { return E[i][j]->data; } //邊(i, j)的資料
    virtual int& weight(int i, int j) { return E[i][j]->weight; } //邊(i, j)的權重
// 邊的動態操作
    virtual void insert(Te const& edge, int w, int i, int j) {  //插入權重為w的邊e = (i, j)
        if (exists(i, j)) return; //確保該邊尚不存在
        E[i][j] = new Edge<Te>(edge, w); //建立新邊
        e++; V[i].outDegree++; V[j].inDegree++; //更新邊計數與關聯頂點的度數
}
    virtual Te remove(int i, int j) { //刪除頂點i和j之間的聯邊(exists(i, j))
        Te eBak = edge(i, j); delete E[i][j]; E[i][j] = NULL; //備份後刪除邊記錄
        e--; V[i].outDegree--; V[j].inDegree--; //更新邊計數不關聯頂點的度數
        return eBak; //返回被刪除邊的資訊
    }
};

利用了Vector結構,在內部將所有頂點組織為一個向量V[]; 同時通過巢狀定義,將所有(潛在的)邊組織為一個二維向量E[][]——亦即鄰接矩陣。 每個頂點統一表示為Vertex物件,每條邊統一表示為Edge物件。 邊物件的屬性weight統一簡化為整型,既可用於表示無權圖,亦可表示帶權網路。

鄰接表

原理:

鄰接矩陣的空間效率之所以低,是因為其中大量單元所對應的邊,通常並未在圖中出現。將這裡的向量替換為列表,按照這一思路,的確可以匯出圖結構的另一種表示與實現形式。

以如圖6.6(a)所示的無向圖為例,只需將如圖(b)所示的鄰接矩陣,逐行地轉換為如圖(c)所示的一組列表,即可分別記錄各頂點的關聯邊(或等價地,鄰接頂點)。這些列表,也因此稱作鄰接表(adjacency list)。