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
pull/402/head
Yudong Jin 2 years ago committed by GitHub
parent 4f941e3d99
commit 33c797efeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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<Vertex*, unordered_set<Vertex*>> adjList;
public:
// 邻接表key: 顶点value该顶点的所有邻接顶点
unordered_map<Vertex*, vector<Vertex*>> adjList;
/* 在 vector 中删除指定结点 */
void remove(vector<Vertex*> &vec, Vertex *vet) {
for (int i = 0; i < vec.size(); i++) {
if (vec[i] == vet) {
vec.erase(vec.begin() + i);
break;
}
}
}
/* 构造方法 */
GraphAdjList(const vector<vector<Vertex*>>& 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<Vertex*>();
adjList[vet] = vector<Vertex*>();
}
/* 删除顶点 */
@ -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<int> 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<vector<Vertex*>> 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 文件中

@ -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<Vertex*> v = valsToVets(vector<int> { 1, 3, 2, 5, 4 });
vector<vector<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(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;
}

@ -8,8 +8,8 @@
/* 基于邻接矩阵实现的无向图类 */
class GraphAdjMat {
vector<int> vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引”
vector<vector<int>> adjMat; // 邻接矩阵,行列索引对应“顶点索引”
vector<int> vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引”
vector<vector<int>> adjMat; // 邻接矩阵,行列索引对应“顶点索引”
public:
/* 构造方法 */
@ -90,13 +90,12 @@ public:
}
};
/* Driver Code */
int main() {
/* 初始化无向图 */
// 请注意edges 元素代表顶点索引,即对应 vertices 元素索引
vector<int> vertices = {1, 3, 2, 5, 4};
vector<vector<int>> edges = {{0, 1}, {0, 2}, {1, 2}, {2, 3}, {0, 3}, {2, 4}, {3, 4}};
vector<int> vertices = { 1, 3, 2, 5, 4 };
vector<vector<int>> 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;
}

@ -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<Vertex*> graphBFS(GraphAdjList &graph, Vertex *startVet) {
// 顶点遍历序列
vector<Vertex*> res;
// 哈希表,用于记录已被访问过的顶点
unordered_set<Vertex*> visited = { startVet };
// 队列用于实现 BFS
queue<Vertex*> 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<Vertex*> v = valsToVets({ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
vector<vector<Vertex*>> 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<Vertex*> res = graphBFS(graph, v[0]);
cout << "\n广度优先遍历BFS顶点序列为" << endl;
PrintUtil::printVector(vetsToVals(res));
// 释放内存
for (Vertex *vet : v) {
delete vet;
}
return 0;
}

@ -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<Vertex*>& visited, vector<Vertex*>& 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<Vertex*> graphDFS(GraphAdjList& graph, Vertex* startVet) {
// 顶点遍历序列
vector<Vertex*> res;
// 哈希表,用于记录已被访问过的顶点
unordered_set<Vertex*> visited;
dfs(graph, visited, res, startVet);
return res;
}
/* Driver Code */
int main() {
/* 初始化无向图 */
vector<Vertex*> v = valsToVets(vector<int> { 0, 1, 2, 3, 4, 5, 6 });
vector<vector<Vertex*>> 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<Vertex*> res = graphDFS(graph, v[0]);
cout << "\n深度优先遍历DFS顶点序列为" << endl;
PrintUtil::printVector(vetsToVals(res));
// 释放内存
for (Vertex *vet : v) {
delete vet;
}
return 0;
}

@ -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<int> toVector() {
DoubleListNode *node = front;
DoublyListNode *node = front;
vector<int> res(size());
for (int i = 0; i < res.size(); i++) {
res[i] = node->val;

@ -0,0 +1,34 @@
/**
* File: PrintUtil.hpp
* Created Time: 2023-03-02
* Author: Krahets (krahets@163.com)
*/
#pragma once
#include <vector>
using namespace std;
/* 顶点类 */
struct Vertex {
int val;
Vertex(int x) : val(x) {}
};
/* 输入值列表 vals ,返回顶点列表 vets */
vector<Vertex*> valsToVets(vector<int> vals) {
vector<Vertex*> vets;
for (int val : vals) {
vets.push_back(new Vertex(val));
}
return vets;
}
/* 输入顶点列表 vets ,返回值列表 vals */
vector<int> vetsToVals(vector<Vertex*> vets) {
vector<int> vals;
for (Vertex *vet : vets) {
vals.push_back(vet->val);
}
return vals;
}

@ -22,6 +22,7 @@
#include "ListNode.hpp"
#include "TreeNode.hpp"
#include "Vertex.hpp"
#include "PrintUtil.hpp"
using namespace std;

@ -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();

@ -325,7 +325,7 @@
=== "C++"
```cpp title="linkedlist_deque.cpp"
[class]{ListNode}-[func]{}
[class]{DoublyListNode}-[func]{}
[class]{LinkedListDeque}-[func]{}
```

@ -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/) 网页版。
<h3 align="left"> 作者简介 </h3>
靳宇栋 (Krahets)大厂高级算法工程师上海交通大学硕士。力扣LeetCode全网阅读量最高博主其 LeetBook《图解算法数据结构》已被订阅 22 万本。

Loading…
Cancel
Save