數據結構實驗(五)
圖
-
圖的鄰接表封裝
-
“ALGraphs.hpp”
// Test7-1:圖的鄰接表封裝 #include <iostream> using namespace std; // 弧結點 struct ArcNode { int adjvex; ArcNode* nextArc; int info; // 弧自帶的信息 ArcNode(int adj, int w) { adjvex = adj; info = w; nextArc = NULL; } void release() { if (nextArc) { nextArc->release(); delete nextArc; } } void output() { cout << " -> Adj=" << adjvex + 1 << ",W=" << info; } }; // 頂點結點 struct VexNode { char data; //結點自帶的信息 ArcNode* firstArc; VexNode(char data = ' ') { this->data = data; firstArc = NULL; } ~VexNode() { //TODO:析構函數釋放鏈表 if (firstArc) firstArc->release(); } void output() { cout << "頂點 " << data; ArcNode* p = firstArc; while (p) { p->output(); p = p->nextArc; } cout << endl; } void addArc(int adj, int w) //向頂點添加弧 { ArcNode* p = new ArcNode(adj, w); p->nextArc = firstArc; firstArc = p; } }; // 鄰接表方式存儲的圖 struct ALGraph { VexNode* vertices; // 頂點順序表 int vexNum; // 頂點的數量 int arcNum; // 邊集的數量 int kind; // 圖的種類,0-無向圖,1-有向圖 ALGraph(const char* names, int k = 1) // 傳入一個字符串,根據其中字符爲每個結點命名 { kind = k; vexNum = strlen(names); arcNum = 0; vertices = new VexNode[vexNum]; for (int i = 0; i < vexNum; i++) vertices[i].data = names[i]; } ~ALGraph() { //TODO:析構函數釋放結點表 //if (vertices != NULL) // delete[]vertices; } void output() // 輸出圖 { for (int i = 0; i < vexNum; i++) vertices[i].output(); } void addArc(int tail, int head, int w = 0) { vertices[tail].addArc(head, w); if (kind == 0) vertices[head].addArc(tail, w); arcNum++; } };
-
“ALGraphs.cpp”
#include <iostream> using namespace std; #include "./ALGraph.hpp" int main() { // 測試圖的創建和輸出 ALGraph g("ABCD"); g.addArc(0, 2, 12); g.addArc(0, 1, 33); g.addArc(2, 3, 18); g.addArc(3, 0, 42); g.output(); return 0; }
-
運行截圖:
-
-
圖的遍歷 (Traversal Algorithm: DFS Traverse and BFS Traverse)
-
“Traversal of Graphs.cpp”
// Test7-2:圖的遍歷 // Traversal Algorithm: DFS Traverse and BFS Traverse #include <iostream> using namespace std; #include "../ALGraph/ALGraph.hpp" #include "../Queue/Queue.cpp" #define MAX_VERTEX_NUM 8 bool visited[MAX_VERTEX_NUM]; // 初始化一個標記數組,如果爲true代表該點已遍歷,否則,未遍歷 void visit(ALGraph& G, int v) { visited[v] = true; cout << "v" << G.vertices[v].data << "->"; } void DFS(ALGraph& G, int v) // Depth First Search { // TODO:深度優先搜索遞歸函數 visit(G, v); ArcNode* p = G.vertices[v].firstArc; if (p) { v = p->adjvex; if (!visited[v]) DFS(G, v); else p = p->nextArc; } else return; } void DFSTraverse(ALGraph& G, int i = 0) { // TODO:深度優先搜索主控函數 memset(visited, false, sizeof(visited)); // 初始化visited數組 while (i + 1 < G.vexNum) // 當每個結點都遍歷後,退出循環 { if (!visited[i]) // 如果該點未被遍歷過,執行深度優先搜索遞歸函數 DFS(G, i); i++; // 繼續遍歷 } cout << endl; } void BFS(ALGraph& G, int v) // Breadth First Search { // TODO:廣度優先搜索一個連通分支下的遍歷函數 Queue<int> queue; // 創建隊列 queue.enqueue(v); // 入隊起始頂點 visited[v] = true; // 修改visited[v]爲true,避免再次入隊,導致的多次遍歷 while (!queue.isEmpty()) // 當隊列爲空時,完成遍歷 { int j = queue.dequeue(); cout << "v" << G.vertices[j].data << "->"; ArcNode* p = G.vertices[j].firstArc; while (p) { if (!visited[p->adjvex]) { queue.enqueue(p->adjvex); visited[p->adjvex] = true; } p = p->nextArc; } } } void BFSTraverse(ALGraph& G, int i = 0) { // TODO:廣度優先搜索主控函數 memset(visited, false, sizeof(visited)); // 初始化visited數組 while (i + 1 < G.vexNum) // 當每個結點都遍歷後,退出循環 { if (!visited[i]) // 如果該點未被遍歷過,執行廣度優先搜索一個連通分支下的遍歷函數 BFS(G, i); i++; // 繼續遍歷 } cout << endl; } int main() { // 測試用例 ALGraph G("12345678", 0); // 8個結點的無向圖 G.addArc(0, 2); G.addArc(0, 1); G.addArc(1, 4); G.addArc(1, 3); G.addArc(2, 6); G.addArc(2, 5); G.addArc(3, 7); G.addArc(4, 7); cout << "* " << "Traversal Algorithm of Graphs" << " *" << endl; cout << endl; cout << "Depth First Search:" << endl; DFSTraverse(G); // 測試深度優先搜索 cout << endl; cout << "Breadth First Search:" << endl; BFSTraverse(G); // 測試廣度優先搜索 return 0; }
-
運行截圖:
-
-
圖的最小生成樹 (Minimum Cost Spanning Tree Algorithm)
-
“MST Algorithm.hpp”
// Test7-3:圖的最小生成樹 (Minimum Cost Spanning Tree Algorithm) // MST Algorithm: Prim and Kruskal #include <iostream> using namespace std; #include "../ALGraph/ALGraph.hpp" #define MAX_VERTEX_NUM 6 // 邊 struct Edges { int head =0; int tail = 0; int weight = 0; bool flag = false; Edges() {} // 默認構造函數,用於邊集數組的初始化,不能捨去這句話 void setEdges(int head, int tail, int weight) { this->head = head; this->tail = tail; this->weight = weight; } void output() { cout << "V" << head + 1 << "->" << "V" << tail + 1 << ":" << weight << endl; } }; // 通過鄰接表生成邊集 Edges* EdgeSet(ALGraph& G) { Edges* edges; // 邊集數組 edges = new Edges[G.arcNum]; // 依據邊的數量創建邊集數組 int j = 0; // 用於鎖定當前初始化的邊的索引 // 通過遍歷圖的鄰接鏈表來爲Edges初始化 for (int i = 0; i < G.vexNum; i++) { ArcNode* p = G.vertices[i].firstArc; while (p) { if (i < p->adjvex) // 對於無向圖而言,只取出前驅小於後繼的邊 { edges[j].setEdges(i, p->adjvex, p->info); j++; } p = p->nextArc; } } return edges; } // Prim算法的實現 void Prim(ALGraph& G, int index = 0) { int adjvex[MAX_VERTEX_NUM]; // 前驅表 memset(adjvex, 0, sizeof(adjvex)); // 初始化前驅表 int lowcost[MAX_VERTEX_NUM]; // 最小權表 for (int i = 0; i < MAX_VERTEX_NUM; i++) // 初始化最小權表 lowcost[i] = INT_MAX; bool U[MAX_VERTEX_NUM]; // 已選頂點的集合,U[v]=true,表示頂點v在U集合中 memset(U, 0, sizeof(U)); // 初始化已選頂點集合,爲空集 //Edges* edges; // 邊集數組 //edges = EdgeSet(G); // 獲取邊集 // 利用輔助數組adjvex, lowcost實現Prim算法 while (!U[index]) // 當每一個頂點都參與最小生成的生成則退出循環 { adjvex[index] = -1; // 初始化,標識該點已經添加至最小生成樹 lowcost[index] = 0; // 初始化,標識該邊已經添加至最小生成樹 U[index] = 1; // 初始化,設置頂點的標誌,作爲循環條件控制循環 int minweight = INT_MAX; ArcNode* node = G.vertices[index].firstArc; // 利用鄰接表來查詢邊,首選 while (node) { if (node->info < lowcost[node->adjvex]) // 權值若小於lowcost更新lowcost { // 標記其起點 adjvex[node->adjvex] = index; // 更新前驅 lowcost[node->adjvex] = node->info; // 更新最小權 } node = node->nextArc; // 訪問下一個結點 } /* // 利用邊集的數據結構來查詢邊,較慢 for (int i = 0; i < G.arcNum; i++) { // 尋找MST的邊集 if (edges[i].head == index) { if (edges[i].weight < lowcost[edges[i].tail]) // 權值若小於lowcost更新lowcost { // 標記其起點 adjvex[edges[i].tail] = index; // 更新前驅 lowcost[edges[i].tail] = edges[i].weight; // 更新最小權 } } } */ // 找到最小權值邊 int i = 0; for (int j = 0; j < G.vexNum; j++) { if (lowcost[j] < minweight && lowcost[j] != 0) // 從剩餘的邊中,找到最小值 { minweight = lowcost[j]; i = j; // 記錄該邊的索引值,便於輸出顯示 } } if (lowcost[i] != 0) // 這句話的出現是爲了過濾掉最小生成樹中最後一個頂點即lowcost數組全爲0的情況 cout << "V" << adjvex[i] + 1 << "->" << "V" << i + 1 << ":" << lowcost[i] << endl; index = i; } } // 對圖中的邊集進行排序 Edges* Sort(ALGraph& G) { Edges* edges; // 邊集數組 //Edges* sort; // 依據權重排序後的邊集數組 edges = EdgeSet(G); // 獲取邊集 //sort = new Edges[G.arcNum]; // 初始化排序邊集 // 採用冒泡排序,減小空間複雜度,優解 for (int i = 0; i < G.arcNum; i++) { for (int j = 0; j < G.arcNum - 1; j++) { if (edges[j].weight > edges[j + 1].weight) { Edges temp; temp = edges[j]; edges[j] = edges[j + 1]; edges[j + 1] = temp; } } } /* // 開闢新的地址空間進行排序,增大了空間複雜度,劣解 for (int i = 0; i < G.arcNum; i++) { int minIndex = 0; int minWeight = 999; for (int j = 0; j < G.arcNum; j++) { if (!edges[j].flag) // 如果沒參與過比較進入 { if (edges[j].weight < minWeight) { minIndex = j; minWeight = edges[j].weight; } } } sort[i] = edges[minIndex]; edges[minIndex].flag = true; // 將被選中的邊淘汰 } */ return edges; } /* // 比較函數,作爲qsort函數的參數,告訴qsort排序的依據 int compare(const void* arg1, const void* arg2) { Edges* edge1 = (Edges*)arg1; Edges* edge2 = (Edges*)arg2; return edge1->weight - edge2->weight; } */ // Kruskal算法的實現 void Kruskal(ALGraph& G) { int union_set[MAX_VERTEX_NUM]; for (int v = 0; v < G.vexNum; v++) // 創建連通分量輔助數組用於感知連通分支 union_set[v] = v; // 利用並查集實現Kruskal算法 Edges* edges = Sort(G); // 遍歷鄰接表圖並進行排序 /* // 利用系統函數qsort進行排序 Edges* edges; edges = EdgeSet(G); qsort((void*)edges, G.arcNum, sizeof(edges), compare); */ // 取出當前符合要求的權值最小邊並輸出 for (int i = 0; i < G.arcNum; i++) { int v1 = edges[i].head; // 當前邊的起點 int v2 = edges[i].tail; // 當前邊的終點 // 一定要提前存好union_set[v2],因爲要確保修改並查集時,判斷條件保持不變 int us1 = union_set[v1]; // 標記v1所在的連通分支 int us2 = union_set[v2]; // 標記v2所在的連通分支 if (us1 != us2) { edges[i].output(); for (int j = 0; j < G.vexNum; j++) // 合併連通分支,保證每一個連通分支下的頂點標記相同 { if (union_set[j] == us2) union_set[j] = us1; // 更新連通分量 } } else continue; } }
-
“MST Algorithm.cpp”
#include <iostream> using namespace std; #include "./MST Algorithm.hpp" int main() { // 測試用例 ALGraph G("123456", 0); // 6個結點的無向圖 G.addArc(0, 1, 6); G.addArc(0, 2, 1); G.addArc(0, 3, 5); G.addArc(1, 2, 5); G.addArc(1, 4, 3); G.addArc(2, 3, 5); G.addArc(2, 4, 6); G.addArc(2, 5, 4); G.addArc(3, 5, 2); G.addArc(4, 5, 6); G.output(); // 輸出圖的Adjacency LinkList cout << endl; cout << "利用Prim算法選擇的邊:" << endl; Prim(G); // 測試Prim算法 cout << endl; cout << "利用Kruskal算法選擇的邊:" << endl; Kruskal(G); // 測試Kruskal算法 return 0; }
-
運行截圖:
-