資料結構(C++)——圖:基於鄰接矩陣實現的圖結構
阿新 • • 發佈:2019-02-02
抽象資料型別
操作介面:圖支援的操作介面分為邊和頂點兩類
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)。