C++資料結構之實現鄰接表
本文例項為大家分享了C++資料結構之實現鄰接表的具體程式碼,供大家參考,具體內容如下
一、圖的鄰接表實現
1.實現了以頂點順序表、邊連結串列為儲存結構的鄰接表;
2.實現了圖的建立(有向/無向/圖/網)、邊的增刪操作、深度優先遞迴/非遞迴遍歷、廣度優先遍歷的演算法;
3.採用頂點物件列表、邊(弧)物件列表的方式,對圖的建立進行初始化;引用 "ObjArrayList.h"標頭檔案,標頭檔案可參看之前博文“資料結構之順序列表(支援物件元素)”程式碼;
4.深度優先遍歷分別採用遞迴/非遞迴演算法;非遞迴中用到的棧,引用"LinkStack.h"標頭檔案,標頭檔案可參看之前博文“資料結構之棧”程式碼;
5.廣度優先遍歷採用佇列方式實現;用到的佇列,引用 "LinkQueue.h"標頭檔案,標頭檔案可參看之前博文“資料結構之佇列”程式碼;
6.測試程式碼中以有向網的所有帶權邊作為邊的初始化資料,選擇圖型別(DG,UDG,DN,UDN)可建立成不同型別的圖。
7.優劣分析:
7.1.(優勢)鄰接表儲存結構,相比鄰接矩陣,消除了無鄰接關係頂點的邊儲存空間;
7.2.(優勢)鄰接表比鄰接矩陣更容易訪問某頂點的鄰接頂點;
7.3.(優勢)鄰接表比鄰接矩陣更易於統計邊總數,無需逐行逐列遍歷;
7.4.(劣勢)在確定兩頂點間是否有邊的搜尋過程中,鄰接表不如鄰接矩陣可直接定址快,反而需要頂點到邊鏈的遍歷;
7.5.(劣勢)鄰接矩陣在刪除頂點時,只需清除對應行列資料即可;而鄰接表在清除頂點指向的邊鏈後,還需遍歷整個邊表,清除所有鄰接於此頂點的邊結點;
7.6.(不足)鄰接表在統計有向圖出度時容易,只需遍歷依附此頂點的邊鏈;卻在統計其入度時,需要遍歷整個邊表,比較麻煩;可採用十字連結串列(鄰接表與逆鄰接表的結合)解決這個問題;
7.7.(不足)鄰接表在無向圖的儲存中,屬於行列對稱矩陣形式,因此會有一半重複的邊資料,故可採用鄰接多重表,只儲存一半邊,來優化儲存。
二、測試程式碼中的圖結構
深度優先遍歷序列(從 v1 頂點開始):
1.無向圖/網:v1-v2-v3-v5-v4-v6-v7
2.有向圖/網:v1-v2-v5-v3-v4-v6-v7
廣度優先遍歷序列(從 v2 頂點開始):
1.無向圖/網:v2-v1-v3-v5-v4-v6-v7
2.有向圖/網:v2-v5 後序無法遍歷
注:有向圖的遍歷 是遵循出度方向遍歷的,若無出度方向,則遍歷終止。
三、程式碼
//檔名:"GraphAdjList.h" #pragma once #ifndef GRAPHADJLISL_H_ #define GRAPHADJLISL_H_ #include <string> #include "ObjArrayList.h" using namespace std; /* . 圖(鄰接表實現) Graph Adjacency List . 相關術語: . 頂點 Vertex ; 邊 Arc ;權 Weight ; . 有向圖 Digraph ;無向圖 Undigraph ; . 有向網 Directed Network ;無向網 Undirected Network ; . 儲存結構: . 1.頂點表只能採用順序結構。(因為若採用鏈式結構,頂點結點定義與邊表結點定義就相互引用,無法定義) . 2.邊表採用連結串列結構。 */ class GraphAdjList { /* . 邊表(連結串列)結點 */ struct ArcNode { int adjVex; //鄰接頂點所在表中下標 int weight; //邊權重 ArcNode * next; //下一條邊 }; /* . 頂點表(順序表)結點 */ struct VNode { string name; //頂點名 ArcNode * first; //指向的第一個依附該頂點的頂點邊結點 }; public: /* . 圖 種類 */ enum GraphType { DG,//有向圖,預設 0 UDG,//無向圖,預設 1 DN,//有向網,預設 2 UDN //無向網,預設 3 }; /* . 邊(弧)資料,注:供外部初始化邊資料使用 */ struct ArcData { string Tail; //弧尾 string Head; //弧頭 int Weight; //權重 }; private: static const int _MAX_VERTEX_NUM = 10; //支援最大頂點數 VNode vexs[_MAX_VERTEX_NUM]; //頂點表 int vexs_visited[_MAX_VERTEX_NUM]; //頂點訪問標記陣列:0|未訪問 1|已訪問 int vexNum; //頂點數 int arcNum; //邊數 int type; //圖種類 void _CreateVexSet(ObjArrayList<string> * vexs); //建立頂點集合 void _CreateDG(ObjArrayList<ArcData> * arcsList); //建立有向圖 void _CreateUDG(ObjArrayList<ArcData> * arcsList); //建立無向圖 void _CreateDN(ObjArrayList<ArcData> * arcsList); //建立有向網 void _CreateUDN(ObjArrayList<ArcData> * arcsList); //建立無向網 int _Locate(string vertex); //定位頂點元素位置 void _InsertArc(int tail,int head,int weight); //插入邊(元操作,不分有向/無向) void _DeleteArc(int tail,int head); //刪除邊(元操作,不分有向/無向) void _DFS_R(int index); //深度優先遍歷 遞迴 void _DFS(int index); //深度優先遍歷 非遞迴 public: GraphAdjList(int type); //建構函式:初始化圖種類 ~GraphAdjList(); //解構函式 void Init(ObjArrayList<string> * vexs,ObjArrayList<ArcData> * arcsList); //初始化頂點、邊資料為 圖|網 void InsertArc(ArcData * arcData); //插入邊(含有向/無向操作) void DeleteArc(ArcData * arcData); //刪除邊(含有向/無向操作) void Display(); //顯示 圖|網 void Display_DFS_R(string *vertex); //從指定頂點開始,深度優先 遞迴 遍歷 void Display_DFS(string *vertex); //從指定頂點開始,深度優先 非遞迴 遍歷 void Display_BFS(string *vertex); //從指定頂點開始,廣度優先遍歷 };
//檔名:"GraphAdjList.cpp" #include "stdafx.h" #include <string> #include "ObjArrayList.h" #include "LinkQueue.h" #include "LinkStack.h" #include "GraphAdjList.h" using namespace std; GraphAdjList::GraphAdjList(int type) { /* . 建構函式:初始化圖型別 */ this->type = type; this->vexNum = 0; this->arcNum = 0; } GraphAdjList::~GraphAdjList() { /* . 解構函式:銷燬圖 */ } void GraphAdjList::Init(ObjArrayList<string> * vexs,ObjArrayList<ArcData> * arcsList) { /* . 初始化頂點、邊資料,並構建 圖|網 . 入參: . vexs: 頂點 列表 . arcsList: 邊資料 列表 */ //1.建立頂點集 _CreateVexSet(vexs); //2.根據圖型別,建立指定的圖 switch (this->type) { case DG: _CreateDG(arcsList); break; case UDG: _CreateUDG(arcsList); break; case DN: _CreateDN(arcsList); break; case UDN: _CreateUDN(arcsList); break; default: break; } } void GraphAdjList::_CreateVexSet(ObjArrayList<string> * vexs) { /* . 建立頂點集合 */ string vertex = ""; //頂點最大數校驗 if (vexs->Length() > this->_MAX_VERTEX_NUM) { return; } //遍歷頂點表,無重複插入頂點,並計數頂點數 for (int i = 0; i < vexs->Length(); i++) { vertex = *vexs->Get(i); if (_Locate(vertex) == -1) { this->vexs[this->vexNum].name = vertex; this->vexs[this->vexNum].first = NULL; this->vexNum++; } } } void GraphAdjList::_CreateDG(ObjArrayList<ArcData> * arcsList) { /* . 建立有向圖 . 鄰接矩陣為 非對稱邊 */ //初始化臨時 邊物件 ArcData * arcData = NULL; //初始化 Tail Head 頂點下標索引 int tail = 0,head = 0; //遍歷邊資料列表 for (int i = 0; i < arcsList->Length(); i++) { //按序獲取邊(弧) arcData = arcsList->Get(i); //定位(或設定)邊的兩端頂點位置 tail = _Locate(arcData->Tail); head = _Locate(arcData->Head); //插入邊 _InsertArc(tail,head,0); } } void GraphAdjList::_CreateUDG(ObjArrayList<ArcData> * arcsList) { /* . 建立無向圖 . 鄰接矩陣為 對稱邊 */ //初始化臨時 邊物件 ArcData * arcData = NULL; //初始化 Tail Head 頂點下標索引 int tail = 0,head = 0; //遍歷邊資料列表 for (int i = 0; i < arcsList->Length(); i++) { //按序獲取邊(弧) arcData = arcsList->Get(i); //定位(或設定)邊的兩端頂點位置 tail = _Locate(arcData->Tail); head = _Locate(arcData->Head); //插入對稱邊 _InsertArc(tail,0); _InsertArc(head,tail,0); } } void GraphAdjList::_CreateDN(ObjArrayList<ArcData> * arcsList) { /* . 建立有向網 . 鄰接矩陣為 非對稱矩陣 */ //初始化臨時 邊物件 ArcData * arcData = NULL; //初始化 Tail Head 頂點下標索引 int tail = 0,arcData->Weight); } } void GraphAdjList::_CreateUDN(ObjArrayList<ArcData> * arcsList) { /* . 建立無向網 . 鄰接矩陣為 對稱矩陣 */ //初始化臨時 邊物件 ArcData * arcData = NULL; //初始化 Tail Head 頂點下標索引 int tail = 0,arcData->Weight); _InsertArc(head,arcData->Weight); } } int GraphAdjList::_Locate(string vertex) { /* . 定位頂點元素位置 . 後期可改成【字典樹】,頂點數超過100個後定位頂點位置可更快 */ //遍歷定位頂點位置 for (int i = 0; i < this->_MAX_VERTEX_NUM; i++) { if (vertex == this->vexs[i].name) { return i; } } //cout << endl << "頂點[" << vertex << "]不存在。" << endl; return -1; } void GraphAdjList::_InsertArc(int tail,int weight) { /* . 插入邊(元操作,不分有向/無向) */ //邊結點指標:初始化為 弧尾 指向的第一個邊 ArcNode * p = this->vexs[tail].first; //初始化 前一邊結點的指標 ArcNode * q = NULL; //重複邊布林值 bool exist = false; //1.邊的重複性校驗 while (p != NULL) { //若已存在該邊,則標記為 存在 true if (p->adjVex == head) { exist = true; break; } //若不是該邊,繼續下一個邊校驗 q = p; p = p->next; } //2.1.如果邊存在,則跳過,不做插入 if (exist) return; //2.2.邊不存在時,建立邊 ArcNode * newArc = new ArcNode(); newArc->adjVex = head; newArc->weight = weight; newArc->next = NULL; //3.1.插入第一條邊 if (q == NULL) { this->vexs[tail].first = newArc; } //3.2.插入後序邊 else { q->next = newArc; } //4.邊 計數 this->arcNum++; } void GraphAdjList::InsertArc(ArcData * arcData) { /* . 插入邊(含有向/無向操作) */ //初始化 Tail Head 頂點下標索引 int tail = 0,head = 0; tail = _Locate(arcData->Tail); head = _Locate(arcData->Head); //根據圖型別,插入邊 switch (this->type) { case DG: _InsertArc(tail,0); break; case UDG: _InsertArc(tail,0); break; case DN: _InsertArc(tail,arcData->Weight); break; case UDN: _InsertArc(tail,arcData->Weight); break; default: break; } } void GraphAdjList::_DeleteArc(int tail,int head) { /* . 刪除邊(元操作,不分有向/無向) */ //邊結點指標:初始化為 弧尾 指向的第一個邊 ArcNode * p = this->vexs[tail].first; //初始化 前一邊結點的指標 ArcNode * q = NULL; //1.遍歷查詢邊 while (p != NULL) { //若存在該邊,則結束迴圈 if (p->adjVex == head) { break; } //若不是該邊,繼續下一個邊 q = p; p = p->next; } //2.1.邊不存在 if (p == NULL) { cout << endl << "邊[" << this->vexs[head].name << "->" << this->vexs[head].name << "]不存在。" << endl; return; } //2.2.邊存在,刪除邊 //2.2.1.若為第一條邊 if (q == NULL) { this->vexs[tail].first = p->next; } //2.2.2.非第一條邊 else { q->next = p->next; } //3.釋放 p delete p; } void GraphAdjList::DeleteArc(ArcData * arcData) { /* . 刪除邊(含有向/無向操作) */ //初始化 Tail Head 頂點下標索引 int tail = 0,head = 0; tail = _Locate(arcData->Tail); head = _Locate(arcData->Head); //根據圖型別,刪除邊 switch (this->type) { case DG: _DeleteArc(tail,head); break; case UDG: _DeleteArc(tail,head); _DeleteArc(head,tail); break; case DN: _DeleteArc(tail,head); break; case UDN: _DeleteArc(tail,tail); break; default: break; } } void GraphAdjList::Display() { /* . 顯示 圖|網 */ //初始化邊表結點指標 ArcNode * p = NULL; cout << endl << "鄰接表:" << endl; //遍歷頂點表 for (int i = 0; i < this->_MAX_VERTEX_NUM; i++) { //空頂點(在刪除頂點的操作後會出現此類情況) if (this->vexs[i].name == "") { continue; } //輸出頂點 cout << "[" << i << "]" << this->vexs[i].name << " "; //遍歷輸出邊頂點 p = this->vexs[i].first; while (p != NULL) { cout << "[" << p->adjVex << "," << p->weight << "] "; p = p->next; } cout << endl; } } void GraphAdjList::_DFS_R(int index) { /* . 深度優先遍歷 遞迴 */ //1.訪問頂點,並標記已訪問 cout << this->vexs[index].name << " "; this->vexs_visited[index] = 1; //2.遍歷訪問其相鄰頂點 ArcNode * p = this->vexs[index].first; int adjVex = 0; while (p != NULL) { adjVex = p->adjVex; //當頂點未被訪問過時,可訪問 if (this->vexs_visited[adjVex] != 1) { _DFS_R(adjVex); } p = p->next; } } void GraphAdjList::Display_DFS_R(string *vertex) { /* . 從指定頂點開始,深度優先 遞迴 遍歷 */ //1.判斷頂點是否存在 int index = _Locate(*vertex); if (index == -1) return; //2.初始化頂點訪問陣列 for (int i = 0; i < this->_MAX_VERTEX_NUM; i++) { this->vexs_visited[i] = 0; } //3.深度優先遍歷 遞迴 cout << "深度優先遍歷(遞迴):(從頂點" << *vertex << "開始)" << endl; _DFS_R(index); } void GraphAdjList::_DFS(int index) { /* . 深度優先遍歷 非遞迴 */ //1.訪問第一個結點,並標記為 已訪問 cout << this->vexs[index].name << " "; vexs_visited[index] = 1; //初始化 邊結點 棧 LinkStack<ArcNode> * s = new LinkStack<ArcNode>(); //初始化邊結點 指標 ArcNode * p = this->vexs[index].first; //2.尋找下一個(未訪問的)鄰接結點 while (!s->Empty() || p != NULL) { //2.1.未訪問過,則訪問 (縱向遍歷) if (vexs_visited[p->adjVex] != 1) { //訪問結點,標記為訪問,並將其入棧 cout << this->vexs[p->adjVex].name << " "; vexs_visited[p->adjVex] = 1; s->Push(p); //指標 p 移向 此結點的第一個鄰接結點 p = this->vexs[p->adjVex].first; } //2.2.已訪問過,移向下一個邊結點 (橫向遍歷) else p = p->next; //3.若無鄰接點,則返回上一結點層,並出棧邊結點 if (p == NULL) { p = s->Pop(); } } //釋放 棧 delete s; } void GraphAdjList::Display_DFS(string *vertex) { /* . 從指定頂點開始,深度優先 非遞迴 遍歷 */ //1.判斷頂點是否存在 int index = _Locate(*vertex); if (index == -1) return; //2.初始化頂點訪問陣列 for (int i = 0; i < this->_MAX_VERTEX_NUM; i++) { this->vexs_visited[i] = 0; } //3.深度優先遍歷 遞迴 cout << "深度優先遍歷(非遞迴):(從頂點" << *vertex << "開始)" << endl; _DFS(index); } void GraphAdjList::Display_BFS(string *vertex) { /* . 從指定頂點開始,廣度優先遍歷 */ //1.判斷頂點是否存在 int index = _Locate(*vertex); if (index == -1) return; //2.初始化頂點訪問陣列 for (int i = 0; i < this->_MAX_VERTEX_NUM; i++) { this->vexs_visited[i] = 0; } //3.廣度優先遍歷 cout << "廣度優先遍歷:(從頂點" << *vertex << "開始)" << endl; //3.1.初始化佇列 LinkQueue<int> * vexQ = new LinkQueue<int>(); //3.2.訪問開始頂點,並標記訪問、入隊 cout << this->vexs[index].name << " "; this->vexs_visited[index] = 1; vexQ->EnQueue(new int(index)); //3.3.出隊,並遍歷鄰接頂點(下一層次),訪問後入隊 ArcNode * p = NULL; int adjVex = 0; while (vexQ->GetHead() != NULL) { index = *vexQ->DeQueue(); p = this->vexs[index].first; //遍歷鄰接頂點 while (p != NULL) { adjVex = p->adjVex; //未訪問過的鄰接頂點 if (this->vexs_visited[adjVex] != 1) { //訪問頂點,並標記訪問、入隊 cout << this->vexs[adjVex].name << " "; this->vexs_visited[adjVex] = 1; vexQ->EnQueue(new int(adjVex)); } p = p->next; } } //4.釋放佇列 int * e; while (vexQ->GetHead() != NULL) { e = vexQ->DeQueue(); delete e; } delete vexQ; }
//檔名:"GraphAdjList_Test.cpp" #include "stdafx.h" #include <iostream> #include "GraphAdjList.h" #include "ObjArrayList.h" using namespace std; int main() { //初始化頂點資料 string * v1 = new string("v1"); string * v2 = new string("v2"); string * v3 = new string("v3"); string * v4 = new string("v4"); string * v5 = new string("v5"); string * v6 = new string("v6"); string * v7 = new string("v7"); ObjArrayList<string> * vexs = new ObjArrayList<string>(); vexs->Add(v1); vexs->Add(v2); vexs->Add(v3); vexs->Add(v4); vexs->Add(v5); vexs->Add(v6); vexs->Add(v7); //初始化邊(弧)資料 GraphAdjList::ArcData * arc1 = new GraphAdjList::ArcData{ "v1","v2",2 }; GraphAdjList::ArcData * arc2 = new GraphAdjList::ArcData{ "v1","v3",3 }; GraphAdjList::ArcData * arc3 = new GraphAdjList::ArcData{ "v1","v4",4 }; GraphAdjList::ArcData * arc4 = new GraphAdjList::ArcData{ "v3","v1",5 }; GraphAdjList::ArcData * arc5 = new GraphAdjList::ArcData{ "v3",6 }; GraphAdjList::ArcData * arc6 = new GraphAdjList::ArcData{ "v3","v5",7 }; GraphAdjList::ArcData * arc7 = new GraphAdjList::ArcData{ "v2",8 }; GraphAdjList::ArcData * arc8 = new GraphAdjList::ArcData{ "v4","v6",9 }; GraphAdjList::ArcData * arc9 = new GraphAdjList::ArcData{ "v4","v7",9 }; GraphAdjList::ArcData * arc10 = new GraphAdjList::ArcData{ "v6",9 }; ObjArrayList<GraphAdjList::ArcData> * arcsList = new ObjArrayList<GraphAdjList::ArcData>(); arcsList->Add(arc1); arcsList->Add(arc2); arcsList->Add(arc3); arcsList->Add(arc4); arcsList->Add(arc5); arcsList->Add(arc6); arcsList->Add(arc7); arcsList->Add(arc8); arcsList->Add(arc9); arcsList->Add(arc10); //測試1:無向圖 cout << endl << "無向圖初始化:" << endl; GraphAdjList * udg = new GraphAdjList(GraphAdjList::UDG); udg->Init(vexs,arcsList); udg->Display(); //1.1.深度優先遍歷 cout << endl << "無向圖深度優先遍歷序列:(遞迴)" << endl; udg->Display_DFS_R(v1); cout << endl << "無向圖深度優先遍歷序列:(非遞迴)" << endl; udg->Display_DFS(v1); //1.2.廣度優先遍歷 cout << endl << "無向圖廣度優先遍歷序列:" << endl; udg->Display_BFS(v2); //1.3.插入新邊 cout << endl << "無向圖新邊:" << endl; udg->InsertArc(new GraphAdjList::ArcData{ "v7",8 }); udg->Display(); //1.4.刪除邊 cout << endl << "無向圖刪除邊arc9:" << endl; udg->DeleteArc(arc9); udg->Display(); //測試2:有向圖 cout << endl << "有向圖:" << endl; GraphAdjList * dg = new GraphAdjList(GraphAdjList::DG); dg->Init(vexs,arcsList); dg->Display(); //2.1.深度優先遍歷 cout << endl << "有向圖深度優先遍歷序列:(遞迴)" << endl; dg->Display_DFS_R(v1); cout << endl << "有向圖深度優先遍歷序列:(非遞迴)" << endl; dg->Display_DFS(v1); //2.2.廣度優先遍歷 cout << endl << "有向圖廣度優先遍歷序列:" << endl; dg->Display_BFS(v2); //測試:無向網 cout << endl << "無向網:" << endl; GraphAdjList * udn = new GraphAdjList(GraphAdjList::UDN); udn->Init(vexs,arcsList); udn->Display(); //測試:有向網 cout << endl << "有向網:" << endl; GraphAdjList * dn = new GraphAdjList(GraphAdjList::DN); dn->Init(vexs,arcsList); dn->Display(); return 0; }
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。