圖的儲存結構(鄰接矩陣與鄰接表)及其C++實現
一、圖的定義
圖是由頂點的有窮非空集合和頂點之間邊的集合組成,通常表示為:
G=(V,E)
其中:G表示一個圖,V是圖G中頂點的集合,E是圖G中頂點之間邊的集合。
注:
線上性表中,元素個數可以為零,稱為空表;
在樹中,結點個數可以為零,稱為空樹;
在圖中,頂點個數不能為零,但可以沒有邊。
二、圖的基本術語
略。
三、圖的遍歷
圖的遍歷是在從圖中某一頂點出發,對圖中所有頂點訪問一次且僅訪問一次。
圖的遍歷操作要解決的關鍵問題:
① 在圖中,如何選取遍歷的起始頂點?
解決方案:從編號小的頂點開始 。
線上性表中,資料元素在表中的編號就是元素在序列中的位置,因而其編號是唯一的; 在樹中,將結點按層序編號,由於樹具有層次性,因而其層序編號也是唯一的; 在圖中,任何兩個頂點之間都可能存在邊,頂點是沒有確定的先後次序的,所以,頂點的編號不唯一。 為了定義操作的方便,將圖中的頂點按任意順序排列起來,比如,按頂點的儲存順序。
② 從某個起點始可能到達不了所有其它頂點,怎麼辦?
解決方案:多次呼叫從某頂點出發遍歷圖的演算法。
③ 因圖中可能存在迴路,某些頂點可能會被重複訪問,那麼如何避免遍歷不會因迴路而陷入死迴圈。
解決方案:附設訪問標誌陣列visited[n] 。
④ 在圖中,一個頂點可以和其它多個頂點相連,當這樣的頂點訪問過後,如何選取下一個要訪問的頂點?
解決方案:深度優先遍歷和廣度優先遍歷。
1、深度優先遍歷
基本思想 :
⑴ 訪問頂點v;
⑵ 從v的未被訪問的鄰接點中選取一個頂點w,從w出發進行深度優先遍歷;
⑶ 重複上述兩步,直至圖中所有和v有路徑相通的頂點都被訪問到。
2、廣度優先遍歷
基本思想:
⑴ 訪問頂點v;
⑵ 依次訪問v的各個未被訪問的鄰接點v1, v2, …, vk;
⑶ 分別從v1,v2,…,vk出發依次訪問它們未被訪問的鄰接點,並使“先被訪問頂點的鄰接點”先於“後被訪問頂點的鄰接點”被訪問。直至圖中所有與頂點v有路徑相通的頂點都被訪問到。
四、圖的儲存結構
是否可以採用順序儲存結構儲存圖?
圖的特點:頂點之間的關係是m:n,即任何兩個頂點之間都可能存在關係(邊),無法通過儲存位置表示這種任意的邏輯關係,所以,圖無法採用順序儲存結構。
如何儲存圖?
考慮圖的定義,圖是由頂點和邊組成的,分別考慮如何儲存頂點、如何儲存邊。
①鄰接矩陣(陣列表示法)
基本思想:用一個一維陣列儲存圖中頂點的資訊,用一個二維陣列(稱為鄰接矩陣)儲存圖中各頂點之間的鄰接關係。
假設圖G=(V,E)有n個頂點,則鄰接矩陣是一個n×n的方陣,定義為:
②鄰接表
鄰接表儲存的基本思想:對於圖的每個頂點vi,將所有鄰接於vi的頂點鏈成一個單鏈表,稱為頂點vi的邊表(對於有向圖則稱為出邊表),所有邊表的頭指標和儲存頂點資訊的一維陣列構成了頂點表。
鄰接表有兩種結點結構:頂點表結點和邊表結點.。
頂點表 邊表
其中:vertex:資料域,存放頂點資訊。 firstedge:指標域,指向邊表中第一個結點。 adjvex:鄰接點域,邊的終點在頂點表中的下標。 next:指標域,指向邊表中的下一個結點。
定義鄰接表的結點:
// 邊表頂點 struct ArcNode { int adjvex; ArcNode *next; }; // 頂點表 template <class T> struct VertexNode { T vertex; ArcNode *firstedge; };
五、C++程式碼實現
Ⅰ、鄰接矩陣
// queue.h #pragma once #include <iostream> const int queueSize = 100; template<class T> class queue { public: T data[queueSize]; int front, rear; }; // graph.h #pragma once #include<iostream> #include"queue.h" // 基於鄰接矩陣儲存結構的圖的類實現 const int MaxSize = 10; int visited[MaxSize] = { 0 };// 頂點是否被訪問的標記 template<class T> class MGraph { public: MGraph(T a[], int n, int e);// 建構函式建立具有N個定點e條邊的圖 ~MGraph(){}// 解構函式 void DFSTraaverse(int v);// 深度優先遍歷圖 void BFSTraverse(int v);// 廣度優先遍歷圖 private: T vertex[MaxSize];// 存放圖中頂點的陣列 int arc[MaxSize][MaxSize];// 存放圖中邊的陣列 int vertexNum, arcNum;// 圖中頂點數和邊數 }; template<class T> inline MGraph<T>::MGraph(T a[], int n, int e) { vertexNum = n; arcNum = e; for (int i = 0; i < vertexNum; i++) // 頂點初始化 vertex[i] = a[i]; for (int i = 0; i < vertexNum; i++) // 鄰接矩陣初始化 for (int j = 0; j < vertexNum; j++) arc[i][j] = 0; for (int k = 0; k < arcNum; k++) { int i, j; std::cin >> i >> j; // 輸入邊依附的頂點的編號 arc[i][j] = 1; // 置有邊標記 arc[j][i] = 1; } } template<class T> inline void MGraph<T>::DFSTraaverse(int v) { cout << vertex[v]<<" "; visited[v] = 1; for (int j = 0; j < vertexNum; j++) { if (arc[v][j] == 1 && visited[j] == 0) DFSTraaverse(j); } } template<class T> inline void MGraph<T>::BFSTraverse(int v) { int visited[MaxSize] = { 0 };// 頂點是否被訪問的標記 queue<T> Q; Q.front = Q.rear = -1; // 初始化佇列 cout << vertex[v]<<" "; visited[v] = 1; Q.data[++Q.rear] = v; // 被訪問頂點入隊 while (Q.front != Q.rear) { v = Q.data[++Q.front]; // 對頭元素出隊 for (int j = 0; j < vertexNum; j++) { if (arc[v][j] == 1 && visited[j] == 0) { std::cout << vertex[j]<<" "; visited[j] = 1; Q.data[++Q.rear] = j; // 鄰接點入隊 } } } } // main.cpp #include"graph.h" using namespace std; int main() { int arry[] = { 1,2,3,4,5,6 }; MGraph<int> graph(arry, 6, 9); graph.BFSTraverse(1); cout << endl; graph.DFSTraaverse(1); system("pause"); return 0; }
Ⅱ、鄰接表
// queue.h #pragma once #include <iostream> const int queueSize = 100; template<class T> class queue { public: T data[queueSize]; int front, rear; }; // graph.h #pragma once #include<iostream> #include"queue.h" // 定義邊表結點 struct ArcNode { int adjvex;// 鄰接點域 ArcNode* next; }; // 定義頂點表結點 struct VertexNode { int vertex; ArcNode* firstedge; }; // 基於鄰接表儲存結構的圖的類實現 const int MaxSize = 10; int visited[MaxSize] = { 0 };// 頂點是否被訪問的標記 //typedef VertexNode AdjList[MaxSize]; //鄰接表 template<class T> class ALGraph { public: ALGraph(T a[], int n, int e);// 建構函式建立具有N個定點e條邊的圖 ~ALGraph() {}// 解構函式 void DFSTraaverse(int v);// 深度優先遍歷圖 void BFSTraverse(int v);// 廣度優先遍歷圖 private: VertexNode adjlist[MaxSize];// 存放頂點的陣列 int vertexNum, arcNum;// 圖中頂點數和邊數 }; template<class T> ALGraph<T>::ALGraph(T a[], int n, int e) { vertexNum = n; arcNum = e; for (int i = 0; i <vertexNum; i++) { adjlist[i].vertex = a[i]; adjlist[i].firstedge = NULL; } for (int k = 0; k < arcNum; k++) { int i, j; std::cin >> i >> j; ArcNode* s = new ArcNode; s->adjvex = j; s->next = adjlist[i].firstedge; adjlist[i].firstedge = s; } } template<class T> inline void ALGraph<T>::DFSTraaverse(int v) { std::cout << adjlist[v].vertex; visited[v] = 1; ArcNode* p = adjlist[v].firstedge; while (p != NULL) { int j = p->adjvex; if (visited[j] == 0) DFSTraaverse(j); p = p->next; } } template<class T> inline void ALGraph<T>::BFSTraverse(int v) { int visited[MaxSize] = { 0 };// 頂點是否被訪問的標記 queue<T> Q; Q.front = Q.rear = -1; // 初始化佇列 std::cout << adjlist[v].vertex; visited[v] = 1; Q.data[++Q.rear] = v;// 被訪問頂點入隊 while (Q.front != Q.rear) { v = Q.data[++Q.front]; // 對頭元素出隊 ArcNode* p = adjlist[v].firstedge; while (p != NULL) { int j = p->adjvex; if (visited[j] == 0) { std::cout << adjlist[j].vertex; visited[j] = 1; Q.data[++Q.rear] = j; } p = p->next; } } } // main.cpp #include"graph.h" using namespace std; int main() { int arry[] = { 1,2,3,4,5 }; ALGraph<int> graph(arry, 5, 7); graph.BFSTraverse(3); cout << endl; graph.DFSTraaverse(3); system("pause"); return 0; }