【高階數據結構】第三彈---圖的存儲與遍歷詳解:鄰接表構建與鄰接矩陣的BFS/DFS實現

?個人主頁:?熬夜學編程的小林

💗系列專欄:?【C語言詳解】?【數據結構詳解】【C++詳解】【Linux系統編程】【高階數據結構】

目錄

1、圖的存儲結構

1.1、鄰接表

1.1.1、邊的結構

1.1.2、圖的基本結構

1.1.3、圖的創建

1.1.4、獲取頂點下標

1.1.5、添加邊

1.1.6、打印

1.1.7、測試一

1.1.8、測試二

2、圖的遍歷

2.1、圖的廣度優先遍歷

2.2、圖的深度優先遍歷?


1、圖的存儲結構

1.1、鄰接表

鄰接表使用數組表示頂點的集合,使用鏈表表示邊的關系

1. 無向圖鄰接表存儲?

注意無向圖中同一條邊在鄰接表中出現了兩次。如果想知道頂點vi的度,只需要知道頂點vi邊鏈表集合中結點的數目即可。?

2. 有向圖鄰接表存儲

注意:有向圖中每條邊在鄰接表中只出現一次,與頂點vi對應的鄰接表所含結點的個數,就是該頂點的出度,也稱出度表,要得到vi頂點的入度,必須檢測其他所有頂點對應的邊鏈表,看有多少邊頂點的dst取值是i。?

1.1.1、邊的結構

1、鄰接表使用鏈表來存儲邊之間的關系,因此成員需要next指針,除此之外還需要起始點(此處可以省略,因為使用目標點即可實現圖)目標點權值

2、此處還需要實現一個有參構造函數參數包括目標點下標和權值

3、權值需要使用類型模板參數

template<class W>
struct Edge
{// int _srci; // 起始點,入邊使用int _dsti;    // 目標點下標,出邊使用W _w;        // 權值Edge<W>* _next;Edge(size_t dsti, const W& w):_dsti(dsti),_w(w),_next(nullptr){}
};

1.1.2、圖的基本結構

1、使用鄰接表存儲圖的基本結構與使用鄰接矩陣存儲圖的基本結構類似,鄰接表同樣的使用模板實現,但是無需表示無窮大的非類型模板參數,因為此處為鏈表結構,添加邊的時候才需要new新的結點

2、成員變量包括頂點集合,頂點與下標的映射關系和領接表

template<class V, class W, bool Direction = false>
class Graph
{typedef Edge<W> Edge;
public:// ...
private:vector<V> _vertexs;     // 頂點集合map<V, int> _vIndexMap; // 頂點映射下標vector<Edge*> _tables;  // 鄰接表
};

1.1.3、圖的創建

圖的創建與鄰接矩陣一樣,使用手動添加邊的方式創建,即實現一個有參構造(參數包含定帶你集合以及頂點個數)

Graph(const V* a, size_t n)
{_vertexs.reserve(n);for (size_t i = 0; i < n; i++){_vertexs.push_back(a[i]);_vIndexMap[a[i]] = i;}_tables.resize(n, nullptr);
}

注意:此處的鄰接表只需開辟一層空間,因為當添加邊的時候,我們會new新的結點。

1.1.4、獲取頂點下標

頂點與下標的映射存儲在map中,獲取頂點對應的下標只需要在map中進行查找即可,找到則返回下標,沒找到則拋異常(為了編譯通過,返回-1)。

// 根據頂點獲取下標
size_t GetVertexIndex(const V& v)
{auto it = _vIndexMap.find(v);if (it != _vIndexMap.end()){return it->second;}else{throw invalid_argument("頂點不存在");return -1;}
}

1.1.5、添加邊

1、參數為起始頂點,結束頂點和權值,還需注意有向與無向圖

2、添加邊的思想先獲取頂點對應的下標,然后將新的結點頭插到鄰接表中,如果是無向圖則需雙向存儲

void AddEdge(const V& src, const V& dst, const W& w)
{size_t srci = GetVertexIndex(src);size_t dsti = GetVertexIndex(dst);// 1 -> 2 Edge* eg = new Edge(dsti, w);// 將Edge頭插到鄰接表eg->_next = _tables[srci];_tables[srci] = eg;// 無向圖 2 -> 1if (Direction == false){Edge* eg = new Edge(srci, w);// 將Edge頭插到鄰接表eg->_next = _tables[dsti];_tables[dsti] = eg;}
}

1.1.6、打印

打印的方式可以根據自己需求打印,此處打印下標與頂點的映射關系和鄰接表為了讓打印更美觀,將鄰接表打印成? ?頂點[下標]->[頂點,下標,權值]...? 的形式

void Print()
{// 頂點 下標 -> 頂點值for (size_t i = 0; i < _vertexs.size(); i++){cout << "[" << i << "]->" << _vertexs[i] << endl;}cout << endl;// 鄰接表for (size_t i = 0; i < _tables.size(); i++){// 頂點[下標]->[頂點,下標,權值]...cout << _vertexs[i] << "[" << i << "]->";Edge* cur = _tables[i];while (cur){cout << "[" << _vertexs[cur->_dsti] << "," << cur->_dsti << "," << cur->_w << "]->";cur = cur->_next;}cout << "nullptr" << endl;}
}

1.1.7、測試一

頂點值為char類型。

void TestGraph1()
{Graph<char, int, true> g("0123", 4);g.AddEdge('0', '1', 1);g.AddEdge('0', '3', 4);g.AddEdge('1', '3', 2);g.AddEdge('1', '2', 9);g.AddEdge('2', '3', 8);g.AddEdge('2', '1', 5);g.AddEdge('2', '0', 3);g.AddEdge('3', '2', 6);g.Print();
}

構造函數?

添加邊

打印結果

1.1.8、測試二

頂點值為string類型。

測試代碼?

void TestGraph2()
{string a[] = { "張三", "李四", "王五", "趙六" };Graph<string, int, true> g1(a, 4);g1.AddEdge("張三", "李四", 100);g1.AddEdge("張三", "王五", 200);g1.AddEdge("王五", "趙六", 30);g1.Print();
}

打印結果?

2、圖的遍歷

注意:此處的遍歷操作都是基于鄰接矩陣進行遍歷的。

給定一個圖G和其中任意一個頂點v0,從v0出發,沿著圖中各邊訪問圖中的所有頂點,且每個頂點僅被遍歷一次"遍歷"即對結點進行某種操作的意思

2.1、圖的廣度優先遍歷

如何防止節點被重復遍歷?

我們可以創建一個標記容器成員類型為bool類型,訪問過則將值設置為true,默認為false。?

廣度優先遍歷思想:

與二叉樹的層序遍歷類似,需要借助隊列先將第一個值入隊列,出隊列的同時把鄰接頂點入隊,直到隊列為空則遍歷結束,具體步驟如下:

  • 1、獲取頂點對應的下標
  • 2、創建隊列和標記容器
  • 3、入隊
  • 4、隊列不為空則遍歷
    • 4.1、打印隊頭
    • 4.2、將front的鄰接頂點入隊
      • 有效邊且標志位為false則入隊

遍歷代碼?

// 根據頂點進行廣度優先遍歷,使用隊列+標記容器
void BFS(const V& src)
{// 1.獲取頂點對應的下標size_t srci = GetVertexIndex(src);// 2.創建隊列和標記容器queue<int> q;vector<bool> visited(_vertexs.size(),false); // 默認false可以訪問// 3.入隊q.push(srci);visited[srci] = true; // 不能訪問size_t n = _vertexs.size();// 4.隊列不為空則遍歷while (!q.empty()){// 打印隊頭int front = q.front();q.pop();cout << front << ":" << _vertexs[front] << endl;// 將front的鄰接頂點入隊for (size_t i = 0; i < n; i++){// 有效邊且標志位為false則入隊if (_matrix[front][i] != MAX_W && !visited[i]){q.push(i);visited[i] = true;}}}cout << endl;
}

測試代碼

void TestBDFS()
{string a[] = { "張三", "李四", "王五", "趙六", "周七" };Graph<string, int> g1(a, sizeof(a) / sizeof(string));g1.AddEdge("張三", "李四", 100);g1.AddEdge("張三", "王五", 200);g1.AddEdge("王五", "趙六", 30);g1.AddEdge("王五", "周七", 30);g1.Print();g1.BFS("張三");
}

結構及打印結果

遍歷結果

上面確實實現了廣度優先遍歷,但是能不能像二叉樹的層序遍歷一樣,打印每一層呢?

可以的。

與二叉樹打印每一層類型,我們可以創建一個記錄隊列元素個數的變量,一次打印隊列元素個數個,打印完一層之后更新這個值

一層層打印?

// 廣度優先遍歷(一層層打印)
void BFS(const V& src)
{// 1.獲取頂點對應的下標size_t srci = GetVertexIndex(src);// 2.創建隊列和標記容器queue<int> q;vector<bool> visited(_vertexs.size(), false); // 默認false可以訪問// 3.入隊q.push(srci);visited[srci] = true; // 不能訪問int levelSize = 1;size_t n = _vertexs.size();// 4.隊列不為空則遍歷while (!q.empty()){// 一層層打印,一次打印隊列個數個for (int i = 0; i < levelSize; i++){// 打印隊頭int front = q.front();q.pop();cout << front << ":" << _vertexs[front] << endl;// 將front的鄰接頂點入隊for (size_t i = 0; i < n; i++){// 有效邊且標志位為false則入隊if (_matrix[front][i] != MAX_W && !visited[i]){q.push(i);visited[i] = true;}}}cout << endl;levelSize = q.size();}
}

遍歷結果?

2.2、圖的深度優先遍歷?

深度優先遍歷思想:?

與二叉樹的前序遍歷類似,但是此處需要使用標記容器,因此需要實現一個子函數具體步驟如下:

  • 1、獲取頂點對應的下標
  • 2、創建標記容器
  • 3、調用子函數

子函數思想:

  • 1、打印該位置的值并將該位置的標記位設置為true(不能再訪問)
  • 2、找一個該位置相鄰的且沒有訪問過的點深度遍歷

代碼實現?

void _DFS(size_t srci, vector<bool>& visited)
{// 下標:頂點值cout << srci << ":" << _vertexs[srci] << endl;visited[srci] = true; // 不能再訪問// 找一個scri相鄰的且沒有訪問過的點,深度遍歷for (size_t i = 0; i < _vertexs.size(); i++){if (_matrix[srci][i] != MAX_W && !visited[i]){_DFS(i, visited);}}
}
void DFS(const V& src)
{size_t srci = GetVertexIndex(src);vector<bool> visited(_vertexs.size(), false);_DFS(srci, visited);
}

運行結果

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/76854.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/76854.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/76854.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

OpenCV的詳細介紹與安裝(一)

1.OpenCV概述 OpenCV是一個開源的計算機視覺和機器學習軟件庫&#xff0c; 它輕量級而且高效——由一系列 C 函數和少量 C 類構成&#xff0c;它支持多種編程語言&#xff08;如C、Python、Java&#xff09;&#xff0c;并可在Windows、Linux、macOS、Android和iOS等平臺上運行…

STM32F103_HAL庫+寄存器學習筆記15 - 梳理CAN發送失敗時,涉及哪些寄存器

導言 《STM32F103_LL庫寄存器學習筆記14 - CAN發送完成中斷》上一章節完成CAN發送完成中斷&#xff0c;在梳理二級發送緩存之前&#xff0c;先梳理怎樣監控CAN發送失敗。 如上所示&#xff1a; 當我關掉CAN分析儀的CAN通道1&#xff0c;CAN錯誤狀態寄存器CAN_ESR的TEC&#x…

Linux——Shell編程之循環語句(筆記)

For循環語句 1、for語句的結構與邏輯&#xff1a; 使用for循環語句時&#xff0c;我們需要指定一個變量以及取值列表&#xff0c;針對每個不同的取值重復執行相同的命令序列,直到變量使用完退出循環。結構如下&#xff1a; for 變量 in 取值列表do命令序列done 對于for語句的…

【權限】v-hasPermi=“[‘monitor:job:add‘]“ 這個屬性是怎么控制能不能看到這個按鈕

背景&#xff1a;對于前臺中通過指令對于操作按鈕的控制是怎么實現的&#xff1a; <el-col :span"1.5"><el-buttontype"primary"plainicon"Plus"click"handleAdd"v-hasPermi"[system:role:add]">新增</el-bu…

ISIS路由引入

?基本概念與作用? ISIS&#xff08;Intermediate System to Intermediate System&#xff09;協議的路由引入&#xff08;Route Import&#xff09;功能用于將其他路由協議&#xff08;如OSPF、BGP&#xff09;或靜態/直連路由引入ISIS域&#xff0c;實現跨協議的路由信息共…

CentOS7更換國內YUM源和Docker簡單應用

配置國內阿里云鏡像源 ## 更新鏡像源 # 1.備份 cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak# 2.替換鏡像源文件 curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo# 3.生成緩存 yum clean all yum m…

常見的 14 個 HTTP 狀態碼詳解

文章目錄 一、2xx 成功1、200 OK2、204 No Content3、206 Partial Content 二、3xx 重定向1、301 Moved Permanently2、302 Found3、303 See Other注意4、Not Modified5、307 Temporary Redirect 三、4xx 客戶端錯誤1、400 Bad Request2、401 Unauthorized3、403 Forbidden4、4…

RAG(檢索增強生成)學習路徑全解析:從入門到精通

引言 檢索增強生成&#xff08;Retrieval Augmented Generation&#xff0c;簡稱RAG&#xff09;是一種結合了信息檢索技術與語言生成模型的人工智能技術。它通過從外部知識庫中檢索相關信息&#xff0c;然后將其作為上下文輸入到大語言模型&#xff08;LLM&#xff09;中&…

OpenAI為搶跑AI,安全底線成犧牲品?

幾年前&#xff0c;如果你問任何一個AI從業者&#xff0c;安全測試需要多長時間&#xff0c;他們可能會淡定地告訴你&#xff1a;“至少幾個月吧&#xff0c;畢竟這玩意兒可能改變世界&#xff0c;也可能毀了它。”而現在&#xff0c;OpenAI用實際行動給出了一個新答案——幾天…

解決在linux下運行rust/tauri項目出現窗口有內容,但是渲染出來成純黑問題

起因 最近折騰了一下rust/tauri程序開發&#xff0c;據說這玩意性能非常牛皮就玩了一下&#xff0c;但是我運行打包一直出現一個奇怪問題&#xff0c;窗口能正常打開&#xff0c;但是是純黑的什么內容都沒有&#xff0c;鼠標移上去又發現指針會變換&#xff08;看起來是內容又…

高并發內存池(定長內存池基礎)

定長內存池的設計 定長內存池定長內存池的原理講解代碼實現定義對象New對象的主要邏輯delete對象的主要邏輯完整代碼 定長內存池 為什么我們要設計這個定長內存池呢&#xff1f;首先malloc是c標準庫中向堆申請空間的接口&#xff0c;變相的說malloc是普遍性&#xff0c;而我們…

【VUE3】練習項目——大事件后臺管理

目錄 0 前言 1 準備工作 1.1 安裝pnpm 1.2 創建vue項目 1.3 Eslint & Prettier的配置 1.4 husky 提交代碼檢查 1.5 目錄調整 1.6 VueRouter4 1.6.1 基礎配置 1.6.2 路由跳轉 1.7 引入 Element Plus 組件庫 1.8 Pinia 1.8.1 優化 1.9 封裝請求工具 1.9.1 安…

WebSocket與MQTT

在物聯網&#xff08;IoT&#xff09;領域&#xff0c;?WebSocket和MQTT確實都可以實現實時通信&#xff0c;但它們的核心設計目標、適用場景和角色存在顯著差異。以下是兩者的對比分析&#xff1a; ?1. 協議設計初衷? ?WebSocket? ?目標?&#xff1a;提供瀏覽器與服務器…

Mysql為什么有時候會選錯索引

案例 正常情況 有一個表t ( id, a , b )&#xff0c;id是主鍵索引&#xff0c;a是Normal索引。 正常情況下&#xff0c;針對a進行查詢&#xff0c;可以走索引a 并且查詢的數量和預估掃描行數是差不多的&#xff0c;都是10001行 奇怪的現象 隨著時間的變化&#xff0c;后…

[250414] ArcoLinux 項目宣布逐步結束

目錄 ArcoLinux 項目宣布逐步結束 ArcoLinux 項目宣布逐步結束 備受歡迎的 Arch Linux 發行版 ArcoLinux 近日宣布&#xff0c;其項目將逐步結束。ArcoLinux 以其作為 Linux 教育平臺和提供多種安裝選項&#xff08;從完整桌面環境到最小化基礎安裝&#xff09;而聞名。 核心…

opencv人臉性別年齡檢測

一、引言 在計算機視覺領域&#xff0c;人臉分析是一個熱門且應用廣泛的研究方向。其中&#xff0c;人臉性別年齡檢測能夠自動識別圖像或視頻流中人臉的性別和年齡信息&#xff0c;具有諸多實際應用場景&#xff0c;如市場調研、安防監控、用戶個性化體驗等。OpenCV 作為一個強…

【NLP】 22. NLP 現代教程:Transformer的訓練與應用全景解讀

&#x1f9e0; NLP 現代教程&#xff1a;Transformer的訓練與應用全景解讀 一、Transformer的使用方式&#xff08;Training and Use&#xff09; 如何使用Transformer模型&#xff1f; Transformer 模型最初的使用方式有兩種主要方向&#xff1a; 類似 RNN 編碼-解碼器的架…

Spring Boot 集成 RocketMQ 全流程指南:從依賴引入到消息收發

前言 在分布式系統中&#xff0c;消息中間件是解耦服務、實現異步通信的核心組件。RocketMQ 作為阿里巴巴開源的高性能分布式消息中間件&#xff0c;憑借其高吞吐、低延遲、高可靠等特性&#xff0c;成為企業級應用的首選。而 Spring Boot 通過其“約定優于配置”的設計理念&a…

HTTPS實現安全的關鍵方法及技術細節

HTTPS&#xff08;HyperText Transfer Protocol Secure&#xff09;通過多種技術手段實現數據傳輸的安全性&#xff0c;其核心機制基于SSL/TLS協議&#xff0c;并結合數字證書、加密算法等技術。 SSL&#xff1a;Secure Sockets Layer&#xff0c;安全套接字層 TLS&#xff1a;…

Java【多線程】(8)CAS與JUC組件

目錄 1.前言 2.正文 2.1CAS概念 2.2CAS兩種用途 2.2.1實現原子類 2.2.2實現自旋鎖 2.3缺陷&#xff1a;ABA問題 2.4JUC組件 2.4.1Callable接口 2.4.2ReentrantLock&#xff08;與synchronized對比&#xff09; 2.4.3Semaphore信號量 2.4.4CountDownLatch 3.小結 1…