From 33c797efebf2884e55285781a44b1bdce81ca7d8 Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Thu, 2 Mar 2023 18:57:33 +0800 Subject: [PATCH] feat: Add C++ code for the graph bfs and dfs (#401) * Add C++ code for the graph bfs and dfs * Add C++ code for the graph bfs and dfs --- .../chapter_graph/graph_adjacency_list.cpp | 93 +++++-------------- .../graph_adjacency_list_test.cpp | 49 ++++++++++ .../chapter_graph/graph_adjacency_matrix.cpp | 11 ++- codes/cpp/chapter_graph/graph_bfs.cpp | 59 ++++++++++++ codes/cpp/chapter_graph/graph_dfs.cpp | 55 +++++++++++ .../linkedlist_deque.cpp | 20 ++-- codes/cpp/include/Vertex.hpp | 34 +++++++ codes/cpp/include/include.hpp | 1 + .../chapter_graph/graph_adjacency_list.java | 3 +- docs/chapter_stack_and_queue/deque.md | 2 +- docs/index.md | 2 - 11 files changed, 241 insertions(+), 88 deletions(-) create mode 100644 codes/cpp/chapter_graph/graph_adjacency_list_test.cpp create mode 100644 codes/cpp/chapter_graph/graph_bfs.cpp create mode 100644 codes/cpp/chapter_graph/graph_dfs.cpp create mode 100644 codes/cpp/include/Vertex.hpp diff --git a/codes/cpp/chapter_graph/graph_adjacency_list.cpp b/codes/cpp/chapter_graph/graph_adjacency_list.cpp index 5a38b3925..3bef35d17 100644 --- a/codes/cpp/chapter_graph/graph_adjacency_list.cpp +++ b/codes/cpp/chapter_graph/graph_adjacency_list.cpp @@ -1,24 +1,27 @@ /** * File: graph_adjacency_list.cpp * Created Time: 2023-02-09 - * Author: what-is-me (whatisme@outlook.jp) + * Author: what-is-me (whatisme@outlook.jp), Krahets (krahets@163.com) */ #include "../include/include.hpp" -/* 顶点类 */ -struct Vertex { - int val; - Vertex(int val) : val(val) {} -}; - /* 基于邻接表实现的无向图类 */ class GraphAdjList { - // 邻接表,使用哈希表来代替链表,以提升删除边、删除顶点的效率 - // 请注意,adjList 中的元素是 Vertex 对象 - unordered_map> adjList; - public: + // 邻接表,key: 顶点,value:该顶点的所有邻接顶点 + unordered_map> adjList; + + /* 在 vector 中删除指定结点 */ + void remove(vector &vec, Vertex *vet) { + for (int i = 0; i < vec.size(); i++) { + if (vec[i] == vet) { + vec.erase(vec.begin() + i); + break; + } + } + } + /* 构造方法 */ GraphAdjList(const vector>& edges) { // 添加所有顶点和边 @@ -37,8 +40,8 @@ public: if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) throw invalid_argument("不存在顶点"); // 添加边 vet1 - vet2 - adjList[vet1].insert(vet2); - adjList[vet2].insert(vet1); + adjList[vet1].push_back(vet2); + adjList[vet2].push_back(vet1); } /* 删除边 */ @@ -46,15 +49,15 @@ public: if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) throw invalid_argument("不存在顶点"); // 删除边 vet1 - vet2 - adjList[vet1].erase(vet2); - adjList[vet2].erase(vet1); + remove(adjList[vet1], vet2); + remove(adjList[vet2], vet1); } /* 添加顶点 */ void addVertex(Vertex* vet) { if (adjList.count(vet)) return; // 在邻接表中添加一个新链表 - adjList[vet] = unordered_set(); + adjList[vet] = vector(); } /* 删除顶点 */ @@ -64,67 +67,19 @@ public: // 在邻接表中删除顶点 vet 对应的链表 adjList.erase(vet); // 遍历其它顶点的链表,删除所有包含 vet 的边 - for (auto& [key, set_] : adjList) { - set_.erase(vet); + for (auto& [key, vec] : adjList) { + remove(vec, vet); } } /* 打印邻接表 */ void print() { cout << "邻接表 =" << endl; - for (auto& [key, value] : adjList) { - vector tmp; - for (Vertex* vertex : value) - tmp.push_back(vertex->val); + for (auto& [key, vec] : adjList) { cout << key->val << ": "; - PrintUtil::printVector(tmp); + PrintUtil::printVector(vetsToVals(vec)); } } }; - -/* Driver Code */ -int main() { - /* 初始化无向图 */ - Vertex *v0 = new Vertex(1), - *v1 = new Vertex(3), - *v2 = new Vertex(2), - *v3 = new Vertex(5), - *v4 = new Vertex(4); - vector> edges = {{v0, v1}, {v1, v2}, {v2, v3}, {v0, v3}, {v2, v4}, {v3, v4}}; - GraphAdjList graph(edges); - cout << "\n初始化后,图为" << endl; - graph.print(); - - /* 添加边 */ - // 顶点 1, 2 即 v0, v2 - graph.addEdge(v0, v2); - cout << "\n添加边 1-2 后,图为" << endl; - graph.print(); - - /* 删除边 */ - // 顶点 1, 3 即 v0, v1 - graph.removeEdge(v0, v1); - cout << "\n删除边 1-3 后,图为" << endl; - graph.print(); - - /* 添加顶点 */ - Vertex* v5 = new Vertex(6); - graph.addVertex(v5); - cout << "\n添加顶点 6 后,图为" << endl; - graph.print(); - - /* 删除顶点 */ - // 顶点 3 即 v1 - graph.removeVertex(v1); - cout << "\n删除顶点 3 后,图为" << endl; - graph.print(); - - /* 释放内存 */ - delete v0; - delete v1; - delete v2; - delete v3; - delete v4; - delete v5; -} +// GraphAdjList 的测试样例在 graph_adjacency_list_test.cpp 文件中 diff --git a/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp b/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp new file mode 100644 index 000000000..a8f799b7f --- /dev/null +++ b/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp @@ -0,0 +1,49 @@ +/** + * File: graph_adjacency_list_test.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp), Krahets (krahets@163.com) + */ + +#include "./graph_adjacency_list.cpp" + +/* Driver Code */ +int main() { + /* 初始化无向图 */ + vector v = valsToVets(vector { 1, 3, 2, 5, 4 }); + vector> edges = { { v[0], v[1] }, { v[0], v[3] }, { v[1], v[2] }, + { v[2], v[3] }, { v[2], v[4] }, { v[3], v[4] } }; + GraphAdjList graph(edges); + cout << "\n初始化后,图为" << endl; + graph.print(); + + /* 添加边 */ + // 顶点 1, 2 即 v[0], v[2] + graph.addEdge(v[0], v[2]); + cout << "\n添加边 1-2 后,图为" << endl; + graph.print(); + + /* 删除边 */ + // 顶点 1, 3 即 v[0], v[1] + graph.removeEdge(v[0], v[1]); + cout << "\n删除边 1-3 后,图为" << endl; + graph.print(); + + /* 添加顶点 */ + Vertex* v5 = new Vertex(6); + graph.addVertex(v5); + cout << "\n添加顶点 6 后,图为" << endl; + graph.print(); + + /* 删除顶点 */ + // 顶点 3 即 v[1] + graph.removeVertex(v[1]); + cout << "\n删除顶点 3 后,图为" << endl; + graph.print(); + + // 释放内存 + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp b/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp index b188cdda6..0fc3b136a 100644 --- a/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp +++ b/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp @@ -8,8 +8,8 @@ /* 基于邻接矩阵实现的无向图类 */ class GraphAdjMat { - vector vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” - vector> adjMat; // 邻接矩阵,行列索引对应“顶点索引” + vector vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + vector> adjMat; // 邻接矩阵,行列索引对应“顶点索引” public: /* 构造方法 */ @@ -90,13 +90,12 @@ public: } }; - /* Driver Code */ int main() { /* 初始化无向图 */ // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 - vector vertices = {1, 3, 2, 5, 4}; - vector> edges = {{0, 1}, {0, 2}, {1, 2}, {2, 3}, {0, 3}, {2, 4}, {3, 4}}; + vector vertices = { 1, 3, 2, 5, 4 }; + vector> edges = { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 2, 3 }, { 2, 4 }, { 3, 4 } }; GraphAdjMat graph(vertices, edges); cout << "\n初始化后,图为" << endl; graph.print(); @@ -123,4 +122,6 @@ int main() { graph.removeVertex(1); cout << "\n删除顶点 3 后,图为" << endl; graph.print(); + + return 0; } diff --git a/codes/cpp/chapter_graph/graph_bfs.cpp b/codes/cpp/chapter_graph/graph_bfs.cpp new file mode 100644 index 000000000..4968ecff7 --- /dev/null +++ b/codes/cpp/chapter_graph/graph_bfs.cpp @@ -0,0 +1,59 @@ +/** + * File: graph_bfs.cpp + * Created Time: 2023-03-02 + * Author: Krahets (krahets@163.com) + */ + +#include "../include/include.hpp" +#include "./graph_adjacency_list.cpp" + +/* 广度优先遍历 BFS */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +vector graphBFS(GraphAdjList &graph, Vertex *startVet) { + // 顶点遍历序列 + vector res; + // 哈希表,用于记录已被访问过的顶点 + unordered_set visited = { startVet }; + // 队列用于实现 BFS + queue que; + que.push(startVet); + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (!que.empty()) { + Vertex *vet = que.front(); + que.pop(); // 队首顶点出队 + res.push_back(vet); // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + for (auto adjVet : graph.adjList[vet]) { + if (visited.count(adjVet)) + continue; // 跳过已被访问过的顶点 + que.push(adjVet); // 只入队未访问的顶点 + visited.emplace(adjVet); // 标记该顶点已被访问 + } + } + // 返回顶点遍历序列 + return res; +} + +/* Driver Code */ +int main() { + /* 初始化无向图 */ + vector v = valsToVets({ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); + vector> edges = { { v[0], v[1] }, { v[0], v[3] }, { v[1], v[2] }, { v[1], v[4] }, + { v[2], v[5] }, { v[3], v[4] }, { v[3], v[6] }, { v[4], v[5] }, + { v[4], v[7] }, { v[5], v[8] }, { v[6], v[7] }, { v[7], v[8] } }; + GraphAdjList graph(edges); + cout << "\n初始化后,图为\\n"; + graph.print(); + + /* 广度优先遍历 BFS */ + vector res = graphBFS(graph, v[0]); + cout << "\n广度优先遍历(BFS)顶点序列为" << endl; + PrintUtil::printVector(vetsToVals(res)); + + // 释放内存 + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/codes/cpp/chapter_graph/graph_dfs.cpp b/codes/cpp/chapter_graph/graph_dfs.cpp new file mode 100644 index 000000000..abde12b1f --- /dev/null +++ b/codes/cpp/chapter_graph/graph_dfs.cpp @@ -0,0 +1,55 @@ +/** + * File: graph_dfs.cpp + * Created Time: 2023-03-02 + * Author: Krahets (krahets@163.com) + */ + +#include "../include/include.hpp" +#include "./graph_adjacency_list.cpp" + +/* 深度优先遍历 DFS 辅助函数 */ +void dfs(GraphAdjList& graph, unordered_set& visited, vector& res, Vertex* vet) { + res.push_back(vet); // 记录访问顶点 + visited.emplace(vet); // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + for (Vertex* adjVet : graph.adjList[vet]) { + if (visited.count(adjVet)) + continue; // 跳过已被访问过的顶点 + // 递归访问邻接顶点 + dfs(graph, visited, res, adjVet); + } +} + +/* 深度优先遍历 DFS */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +vector graphDFS(GraphAdjList& graph, Vertex* startVet) { + // 顶点遍历序列 + vector res; + // 哈希表,用于记录已被访问过的顶点 + unordered_set visited; + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +int main() { + /* 初始化无向图 */ + vector v = valsToVets(vector { 0, 1, 2, 3, 4, 5, 6 }); + vector> edges = { { v[0], v[1] }, { v[0], v[3] }, { v[1], v[2] }, + { v[2], v[5] }, { v[4], v[5] }, { v[5], v[6] } }; + GraphAdjList graph(edges); + cout << "\n初始化后,图为" << endl; + graph.print(); + + /* 深度优先遍历 DFS */ + vector res = graphDFS(graph, v[0]); + cout << "\n深度优先遍历(DFS)顶点序列为" << endl; + PrintUtil::printVector(vetsToVals(res)); + + // 释放内存 + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp b/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp index 97dc653d1..fefbb7675 100644 --- a/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp +++ b/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp @@ -8,17 +8,17 @@ /* 双向链表结点 */ -struct DoubleListNode { +struct DoublyListNode { int val; // 结点值 - DoubleListNode *next; // 后继结点指针 - DoubleListNode *prev; // 前驱结点指针 - DoubleListNode(int val) : val(val), prev(nullptr), next(nullptr) {} + DoublyListNode *next; // 后继结点指针 + DoublyListNode *prev; // 前驱结点指针 + DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) {} }; /* 基于双向链表实现的双向队列 */ class LinkedListDeque { private: - DoubleListNode *front, *rear; // 头结点 front ,尾结点 rear + DoublyListNode *front, *rear; // 头结点 front ,尾结点 rear int queSize = 0; // 双向队列的长度 public: @@ -28,7 +28,7 @@ public: /* 析构方法 */ ~LinkedListDeque() { // 释放内存 - DoubleListNode *pre, *cur = front; + DoublyListNode *pre, *cur = front; while (cur != nullptr) { pre = cur; cur = cur->next; @@ -48,7 +48,7 @@ public: /* 入队操作 */ void push(int num, bool isFront) { - DoubleListNode *node = new DoubleListNode(num); + DoublyListNode *node = new DoublyListNode(num); // 若链表为空,则令 front, rear 都指向 node if (isEmpty()) front = rear = node; @@ -88,7 +88,7 @@ public: if (isFront) { val = front->val; // 暂存头结点值 // 删除头结点 - DoubleListNode *fNext = front->next; + DoublyListNode *fNext = front->next; if (fNext != nullptr) { fNext->prev = nullptr; front->next = nullptr; @@ -98,7 +98,7 @@ public: } else { val = rear->val; // 暂存尾结点值 // 删除尾结点 - DoubleListNode *rPrev = rear->prev; + DoublyListNode *rPrev = rear->prev; if (rPrev != nullptr) { rPrev->next = nullptr; rear->prev = nullptr; @@ -131,7 +131,7 @@ public: /* 返回数组用于打印 */ vector toVector() { - DoubleListNode *node = front; + DoublyListNode *node = front; vector res(size()); for (int i = 0; i < res.size(); i++) { res[i] = node->val; diff --git a/codes/cpp/include/Vertex.hpp b/codes/cpp/include/Vertex.hpp new file mode 100644 index 000000000..cfe359792 --- /dev/null +++ b/codes/cpp/include/Vertex.hpp @@ -0,0 +1,34 @@ +/** + * File: PrintUtil.hpp + * Created Time: 2023-03-02 + * Author: Krahets (krahets@163.com) + */ + +#pragma once + +#include +using namespace std; + +/* 顶点类 */ +struct Vertex { + int val; + Vertex(int x) : val(x) {} +}; + +/* 输入值列表 vals ,返回顶点列表 vets */ +vector valsToVets(vector vals) { + vector vets; + for (int val : vals) { + vets.push_back(new Vertex(val)); + } + return vets; +} + +/* 输入顶点列表 vets ,返回值列表 vals */ +vector vetsToVals(vector vets) { + vector vals; + for (Vertex *vet : vets) { + vals.push_back(vet->val); + } + return vals; +} diff --git a/codes/cpp/include/include.hpp b/codes/cpp/include/include.hpp index 03bf9b503..ae288474c 100644 --- a/codes/cpp/include/include.hpp +++ b/codes/cpp/include/include.hpp @@ -22,6 +22,7 @@ #include "ListNode.hpp" #include "TreeNode.hpp" +#include "Vertex.hpp" #include "PrintUtil.hpp" using namespace std; diff --git a/codes/java/chapter_graph/graph_adjacency_list.java b/codes/java/chapter_graph/graph_adjacency_list.java index ed290cca9..67fe95a27 100644 --- a/codes/java/chapter_graph/graph_adjacency_list.java +++ b/codes/java/chapter_graph/graph_adjacency_list.java @@ -84,7 +84,8 @@ public class graph_adjacency_list { public static void main(String[] args) { /* 初始化无向图 */ Vertex[] v = Vertex.valsToVets(new int[] { 1, 3, 2, 5, 4 }); - Vertex[][] edges = { { v[0], v[1] }, { v[0], v[3] }, { v[1], v[2] }, { v[2], v[3] }, { v[2], v[4] }, { v[3], v[4] } }; + Vertex[][] edges = { { v[0], v[1] }, { v[0], v[3] }, { v[1], v[2] }, + { v[2], v[3] }, { v[2], v[4] }, { v[3], v[4] } }; GraphAdjList graph = new GraphAdjList(edges); System.out.println("\n初始化后,图为"); graph.print(); diff --git a/docs/chapter_stack_and_queue/deque.md b/docs/chapter_stack_and_queue/deque.md index 81d43aba2..f454d6ae4 100644 --- a/docs/chapter_stack_and_queue/deque.md +++ b/docs/chapter_stack_and_queue/deque.md @@ -325,7 +325,7 @@ === "C++" ```cpp title="linkedlist_deque.cpp" - [class]{ListNode}-[func]{} + [class]{DoublyListNode}-[func]{} [class]{LinkedListDeque}-[func]{} ``` diff --git a/docs/index.md b/docs/index.md index 50b1edc09..cbaa025e5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -64,8 +64,6 @@ hide: 如果你也有上述烦恼,那么很幸运这本书找到了你。本书是我对于该问题给出的答案,虽然不一定正确,但至少代表一次积极的尝试。这本书虽然不足以让你直接拿到 Offer ,但会引导你探索数据结构与算法的“知识地图”,带你了解不同“地雷”的形状大小和分布位置,让你掌握各种“排雷方法”。有了这些本领,相信你可以更加得心应手地刷题与阅读文献,逐步搭建起完整的知识体系。 -书中的代码均配有可一键运行的源文件,托管在 [github.com/krahets/hello-algo](https://github.com/krahets/hello-algo) 仓库,建议前往下载。发行版 PDF 的更新周期较长,想看最新内容的小伙伴可以前往 [www.hello-algo.com](https://www.hello-algo.com/) 网页版。 -

作者简介

靳宇栋 (Krahets),大厂高级算法工程师,上海交通大学硕士。力扣(LeetCode)全网阅读量最高博主,其 LeetBook《图解算法数据结构》已被订阅 22 万本。