图海寻径——图相关算法的奇幻探索之旅

一、图的表示

1. 邻接矩阵 (Adjacency Matrix)

#include <iostream>
#include <vector>
#include <queue>
#include <limits>using namespace std;class GraphMatrix {
private:int numVertices;vector<vector<int>> adjMatrix;const static int INFINITY = numeric_limits<int>::max();public:GraphMatrix(int vertices) : numVertices(vertices), adjMatrix(vertices, vector<int>(vertices, INFINITY)) {}void addEdge(int u, int v, int weight) {if (u >= 0 && u < numVertices && v >= 0 && v < numVertices) {adjMatrix[u][v] = weight;adjMatrix[v][u] = weight; // 如果是无向图,则需要双向设置}}void BFS(int startVertex);void DFSUtil(int vertex, vector<bool>& visited);void DFS();
};void GraphMatrix::BFS(int startVertex) {vector<bool> visited(numVertices, false);queue<int> q;visited[startVertex] = true;q.push(startVertex);while (!q.empty()) {int current = q.front();cout << "Visited " << current << endl;q.pop();for (int i = 0; i < numVertices; ++i) {if (adjMatrix[current][i] != INFINITY && !visited[i]) {visited[i] = true;q.push(i);}}}
}void GraphMatrix::DFSUtil(int vertex, vector<bool>& visited) {visited[vertex] = true;cout << "Visited " << vertex << endl;for (int i = 0; i < numVertices; ++i) {if (adjMatrix[vertex][i] != INFINITY && !visited[i])DFSUtil(i, visited);}
}void GraphMatrix::DFS() {vector<bool> visited(numVertices, false);for (int i = 0; i < numVertices; ++i)if (!visited[i])DFSUtil(i, visited);
}

2. 邻接表 (Adjacency List)

#include <iostream>
#include <vector>
#include <list>
#include <queue>using namespace std;class GraphList {
private:int numVertices;vector<list<pair<int, int>>> adjLists;public:GraphList(int vertices) : numVertices(vertices), adjLists(vertices) {}void addEdge(int u, int v, int weight) {adjLists[u].push_back(make_pair(v, weight));adjLists[v].push_back(make_pair(u, weight)); // 如果是无向图,则需要双向添加}void BFS(int startVertex);void DFSUtil(int vertex, vector<bool>& visited);void DFS();
};void GraphList::BFS(int startVertex) {vector<bool> visited(numVertices, false);queue<int> q;visited[startVertex] = true;q.push(startVertex);while (!q.empty()) {int current = q.front();cout << "Visited " << current << endl;q.pop();for (auto& edge : adjLists[current]) {int neighbor = edge.first;if (!visited[neighbor]) {visited[neighbor] = true;q.push(neighbor);}}}
}void GraphList::DFSUtil(int vertex, vector<bool>& visited) {visited[vertex] = true;cout << "Visited " << vertex << endl;for (auto& edge : adjLists[vertex]) {int neighbor = edge.first;if (!visited[neighbor])DFSUtil(neighbor, visited);}
}void GraphList::DFS() {vector<bool> visited(numVertices, false);for (int i = 0; i < numVertices; ++i)if (!visited[i])DFSUtil(i, visited);
}

二、图的遍历算法

(一)深度优先搜索(DFS)

  1. 核心原理
    • 以深度优先为策略,从起始顶点出发,沿着一条路径持续深入探索,直至无法继续或达成特定条件(如找到目标顶点)。随后回溯到前一步,继续探寻其他未访问路径,直至遍历完起始顶点所在连通分量的所有顶点。若图中存在未访问顶点,则选取其一作为新起始点,重复上述流程,直至整个图的所有顶点均被访问。
  2. 实现方式
    • 递归实现:在递归函数中,首先标记当前顶点已访问,接着遍历其邻接顶点,对未访问的邻接顶点递归调用 DFS 函数。以下是使用邻接表存储图的递归 DFS 代码:
class GraphDFS {
private:vector<bool> visited;void DFSUtil(Graph& graph, int v) {visited[v] = true;cout << v << " ";for (const Edge& edge : graph.adjList[v]) {int neighbor = edge.to;if (!visited[neighbor]) {DFSUtil(graph, neighbor);}}}public:void DFS(Graph& graph) {visited.resize(graph.numVertices, false);for (int i = 0; i < graph.numVertices; ++i) {if (!visited[i]) {DFSUtil(graph, i);}}}
};
  1. 应用场景
    • 图的连通性检测:执行一次 DFS 遍历,若所有顶点均能被访问到,则图是连通的;反之则不连通。
    • 连通分量求解:多次调用 DFS,每次从一个未访问顶点启动,可获取图的各个连通分量。
    • 迷宫探索:将迷宫建模为图,迷宫中的格子作为顶点,相邻格子间的通道作为边,DFS 可用于找寻从迷宫入口到出口的路径。

(二)广度优先搜索(BFS)

  1. 核心原理
    • 从给定起始顶点开始,先访问该顶点,接着依次访问其所有未访问的邻接顶点,然后再访问这些邻接顶点的未访问邻接顶点,依此类推,按照层次顺序逐层向外拓展,直至遍历完图中的所有顶点。
  2. 实现方式
    • 借助队列实现。首先将起始顶点入队并标记为已访问,然后循环取出队首顶点,访问其未访问邻接顶点并将这些邻接顶点入队,直至队列为空。以下是使用邻接表存储图的 BFS 示例代码:
class GraphBFS {
public:void BFS(Graph& graph, int start) {vector<bool> visited(graph.numVertices, false);queue<int> q;visited[start] = true;q.push(start);while (!q.empty()) {int current = q.front();q.pop();cout << current << " ";for (const Edge& edge : graph.adjList[current]) {int neighbor = edge.to;if (!visited[neighbor]) {visited[neighbor] = true;q.push(neighbor);}}}}
};
  1. 应用场景
    • 无权图最短路径计算:在 BFS 遍历过程中,从起始顶点到其他顶点的路径长度即为它们在队列中被访问的层数,可用于求解无权图中两点间的最短路径长度及路径。
    • 网络爬虫页面遍历:网络爬虫从起始页面出发,按照广度优先顺序遍历链接页面,能够优先抓取与起始页面距离较近的页面,确保信息获取的全面性和层次性。

三、最小生成树算法

(一)普里姆算法(Prim)

  1. 核心原理
    • 从图中任选一个顶点作为起始点,构建初始的最小生成树集合。随后不断从剩余顶点中挑选与当前最小生成树集合相连且权值最小的边所对应的顶点,将其纳入最小生成树集合,持续该过程直至所有顶点均被加入。
  2. 实现细节
    • 维护两个顶点集合,已在最小生成树中的顶点集合U和未在其中的顶点集合V - U。使用数组key记录每个顶点到当前最小生成树的最小权值边的权值,初始除起始顶点外均设为无穷大,同时用数组parent记录每个顶点在最小生成树中的父顶点。以下是使用邻接表存储图的 Prim 算法代码:
class PrimMST {
public:int prim(Graph& graph, int start) {vector<bool> inMST(graph.numVertices, false);vector<int> key(graph.numVertices, INT_MAX);vector<int> parent(graph.numVertices, -1);key[start] = 0;for (int count = 0; count < graph.numVertices - 1; ++count) {int minKey = INT_MAX, minIndex = -1;for (int v = 0; v < graph.numVertices; ++v) {if (!inMST[v] && key[v] < minKey) {minKey = key[v];minIndex = v;}}inMST[minIndex] = true;for (const Edge& edge : graph.adjList[minIndex]) {int neighbor = edge.to;int weight = 1;  // 如果是带权图,这里改成对应边的权值if (!inMST[neighbor] && weight < key[neighbor]) {key[neighbor] = weight;parent[neighbor] = minIndex;}}}int sumWeight = 0;for (int i = 1; i < graph.numVertices; ++i) {sumWeight += key[i];  // 累加最小生成树边的权值(这里简单示例,实际带权图边权计算更复杂)}return sumWeight;}
};
  1. 时间复杂度分析
    • 若采用邻接矩阵存储图,时间复杂度为(O(n^2)),其中(n)为顶点数。若运用堆优化,时间复杂度可降至(O((n + m)log n)),其中(m)为边数。

(二)克鲁斯卡尔算法(Kruskal)

  1. 核心原理
    • 首先将图中所有边按照权值从小到大排序,然后依次考察每条边。若选取某条边不会与已选边构成回路,则将其纳入最小生成树,直至选取了(n - 1)条边((n)为顶点数)。
  2. 实现细节
    • 需要借助并查集数据结构判断边加入后是否形成回路。并查集用于维护顶点的连通性信息,初始每个顶点各自属于独立集合,加入边时判断边的两个顶点是否在同一集合,若不在则合并两个集合。
#include <iostream>
#include <vector>
#include <algorithm>using namespace std;// 边的数据结构
struct Edge {int src, dest, weight;
};// 比较函数,用于对边按照权重排序
bool compare(const Edge& a, const Edge& b) {return a.weight < b.weight;
}// 并查集 (Union-Find) 的类
class UnionFind {
private:vector<int> parent, rank;public:UnionFind(int size) : parent(size), rank(size, 0) {for (int i = 0; i < size; ++i)parent[i] = i;}// 查找操作,带有路径压缩int find(int x) {if (parent[x] != x)parent[x] = find(parent[x]); // 路径压缩return parent[x];}// 合并操作,带有按秩合并void unionSets(int x, int y) {int rootX = find(x);int rootY = find(y);if (rootX != rootY) {if (rank[rootX] > rank[rootY])parent[rootY] = rootX;else if (rank[rootX] < rank[rootY])parent[rootX] = rootY;else {parent[rootY] = rootX;rank[rootX]++;}}}
};// Kruskal 算法实现
void kruskalMST(vector<Edge>& edges, int V) {sort(edges.begin(), edges.end(), compare); // 按权重升序排序边UnionFind uf(V);vector<Edge> mst;for (auto& edge : edges) {int rootSrc = uf.find(edge.src);int rootDest = uf.find(edge.dest);if (rootSrc != rootDest) { // 如果不形成环,则加入最小生成树mst.push_back(edge);uf.unionSets(rootSrc, rootDest);}}cout << "Edges in the MST:" << endl;for (auto& e : mst)cout << e.src << " -- " << e.dest << " == " << e.weight << endl;
}int main() {int V = 4; // 顶点数vector<Edge> edges = {{0, 1, 10},{0, 2, 6},{0, 3, 5},{1, 3, 15},{2, 3, 4}};kruskalMST(edges, V);return 0;
}
  1. 时间复杂度分析
    • 时间复杂度主要取决于边的排序操作,通常为(O(mlog m)),其中(m)为边数。在稀疏图中,由于边数相对较少,克鲁斯卡尔算法往往比普里姆算法更具效率。

四、最短路径算法

(一)迪杰斯特拉算法(Dijkstra)

  1. 核心原理
    • 适用于带权有向图(权值非负)。从源点出发,逐步确定到其他顶点的最短路径。维护一个已确定最短路径的顶点集合S,对于不在S中的顶点v,记录从源点到v的当前最短路径长度d[v]。每次从不在S中的顶点里选取d[v]最小的顶点u加入S,并更新与u相邻顶点的d[v]值。
  2. 实现细节
    • 初始化时,源点的d值设为(0),其余顶点设为无穷大。然后持续重复选取最小d值顶点并更新相邻顶点d值的操作,直至所有顶点均被加入S。以下是使用邻接表存储图的 Dijkstra 算法代码:
class DijkstraSP {
public:vector<int> dijkstra(Graph& graph, int source) {vector<int> dist(graph.numVertices, INT_MAX);vector<bool> visited(graph.numVertices, false);dist[source] = 0;for (int count = 0; count < graph.numVertices - 1; ++count) {int minDist = INT_MAX, minIndex = -1;for (int v = 0; v < graph.numVertices; ++v) {if (!visited[v] && dist[v] < minDist) {minDist = dist[v];minIndex = v;}}visited[minIndex] = true;for (const Edge& edge : graph.adjList[minIndex]) {int neighbor = edge.to;int weight = 1;  // 如果是带权图,这里改成对应边的权值if (!visited[neighbor] && dist[minIndex] + weight < dist[neighbor]) {dist[neighbor] = dist[minIndex] + weight;}}}return dist;}
};
  1. 时间复杂度分析
    • 若采用邻接矩阵存储图,时间复杂度为(O(n^2)),其中(n)为顶点数。若使用堆优化,时间复杂度可降为(O((n + m)log n)),其中m为边数。

(二)弗洛伊德算法(Floyd)

  1. 核心原理
    • 可计算出图中任意两个顶点之间的最短路径。基于动态规划思想,对于任意两个顶点(i)和(j),考虑是否经过中间顶点(k)来更新(i)到(j)的最短路径长度。
  2. 实现细节
    • 使用二维数组dist存储顶点之间的最短路径长度。初始时,dist[i][j]为图中(i)到(j)的直接边权值(若有边),否则为无穷大。然后通过三层循环,依次以每个顶点k作为中间顶点更新dist[i][j]的值。以下是使用邻接矩阵存储图的 Floyd 算法示例代码:
class FloydSP {
public:vector<vector<int>> floyd(GraphMatrix& graph) {int n = graph.numVertices;vector<vector<int>> dist = graph.adjMatrix;for (int k = 0; k < n; ++k) {for (int i = 0; i < n; ++i) {for (int j = 0; j < n; ++j) {if (dist[i][k]!= INT_MAX && dist[k][j]!= INT_MAX &&dist[i][k] + dist[k][j] < dist[i][j]) {dist[i][j] = dist[i][k] + dist[k][j];}}}}return dist;}
};
  1. 时间复杂度分析
    • 时间复杂度为(O(n^3)),其中(n)为顶点数。由于时间复杂度较高,适用于顶点数较少的图或者对所有顶点对最短路径都有需求的场景。

五、拓扑排序算法

  1. 核心原理
    • 针对有向无环图(DAG),拓扑排序旨在将图中所有顶点排成一个线性序列,使得对于图中的任意一条有向边((u, v)),在序列中(u)均排在(v)之前。
  2. 实现方式(基于入度表)
    • 首先统计每个顶点的入度,将入度为(0)的顶点入队。然后循环取出队首顶点,将其输出到拓扑序列中,并将其所有邻接点的入度减(1),若某个邻接点入度变为(0),则将其入队,直至队列为空。若最终输出的顶点数小于图中顶点总数,则说明图中有环,不存在拓扑排序。以下是拓扑排序的代码:
#include <iostream>
#include <vector>
#include <queue>
using namespace std;// 边结构体,用于邻接表存储边的信息
struct Edge {int to;  // 边指向的顶点Edge(int t) : to(t) {}
};// 图结构体,使用邻接表存储
class Graph {
public:int numVertices;  // 顶点数量vector<vector<Edge>> adjList;  // 邻接表Graph(int n) : numVertices(n), adjList(n) {}// 添加边(有向图示例)void addEdge(int from, int to) {adjList[from].push_back(Edge(to));}
};class TopologicalSort {
public:vector<int> topologicalSort(Graph& graph) {int n = graph.numVertices;// 用于记录每个顶点的入度vector<int> inDegree(n, 0);// 计算每个顶点的入度for (int i = 0; i < n; i++) {for (const Edge& edge : graph.adjList[i]) {inDegree[edge.to]++;}}queue<int> zeroDegreeQueue;// 将入度为0的顶点入队for (int i = 0; i < n; i++) {if (inDegree[i] == 0) {zeroDegreeQueue.push(i);}}vector<int> topologicalOrder;// 进行拓扑排序while (!zeroDegreeQueue.empty()) {int current = zeroDegreeQueue.front();zeroDegreeQueue.pop();topologicalOrder.push_back(current);// 更新相邻顶点的入度,并将入度变为0的顶点入队for (const Edge& edge : graph.adjList[current]) {inDegree[edge.to]--;if (inDegree[edge.to] == 0) {zeroDegreeQueue.push(edge.to);}}}// 若排序后的顶点数小于图中顶点总数,则说明图中有环,不存在拓扑排序if (topologicalOrder.size() < n) {cout << "图中存在环,无法进行拓扑排序" << endl;return {};}return topologicalOrder;}
};int main() {Graph graph(6);graph.addEdge(5, 2);graph.addEdge(5, 0);graph.addEdge(4, 0);graph.addEdge(4, 1);graph.addEdge(2, 3);graph.addEdge(3, 1);TopologicalSort ts;vector<int> result = ts.topologicalSort(graph);if (!result.empty()) {cout << "拓扑排序结果为: ";for (int vertex : result) {cout << vertex << " ";}cout << endl;}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/63544.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker单机网络:解锁本地开发环境的无限潜能

作者简介&#xff1a;我是团团儿&#xff0c;是一名专注于云计算领域的专业创作者&#xff0c;感谢大家的关注 座右铭&#xff1a; 云端筑梦&#xff0c;数据为翼&#xff0c;探索无限可能&#xff0c;引领云计算新纪元 个人主页&#xff1a;团儿.-CSDN博客 目录 前言&#…

【前端】深入解析 JavaScript 中的 instanceof 运算符与 number 数据类型 和 Number 对象 区别辨析

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 前端 文章目录 &#x1f4af;前言&#x1f4af;理论基础&#xff1a;instanceof 运算符的设计初衷与核心功能基础定义与应用示例解析代码分解 &#x1f4af;typeof 与 instanceof&#xff1a;两种类型检测方法的语义与…

UI自动化测试框架:PO模式+数据驱动

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1. PO 设计模式简介 什么是 PO 模式&#xff1f; PO&#xff08;PageObject&#xff09;设计模式将某个页面的所有元素对象定位和对元素对象的操作封装成一个 Pa…

在ensp中ACL路由控制实验

一、实验目的 掌握ACL路由控制管理 二、实验要求 要求&#xff1a; 配置路由策略&#xff0c;左右两边不公开区域对方不可达&#xff0c;其他区域可以互相ping通 设备&#xff1a; 1、三台路由器 2、四台交换机 3、四台电脑 4、四台服务器 使用ensp搭建实验环境,如图所…

AlohaKit:一组.NET MAUI绘制的开源控件

前言 今天大姚给大家分享一组.NET MAUI绘制的开源、免费&#xff08;MIT License&#xff09;UI控件库&#xff1a;AlohaKit。 MAUI介绍 .NET MAUI是一个开源、免费&#xff08;MIT License&#xff09;的跨平台框架&#xff08;支持Android、iOS、macOS 和 Windows多平台运…

SpringBoot【一】零基础入门 springboot 及 idea 搭建

一、前言 springboot是什么&#xff1f; Spring Boot是由Pivotal团队提供的全新框架&#xff0c;其设计目的是用来简化新Spring应用的初始搭建以及开发过程。 该框架使用了特定的方式来进行配置&#xff0c;从而使开发人员不再需要定义样板化的配置。用我的话来理解&#xff0…

Grule前端表单post后端执行grule引擎规则

Grule前端表单post后端执行grule引擎规则 编写前端表单和后端接口 编写test.go执行grule引擎规则 示例都是 go test 执行的测试代码&#xff0c;所以将里面的测试代码去除 由于之前 NumberExponentExample_test.go 已经验证可运行, 所以将 err 的异常处理去除 package mai…

文献补充材料怎么查找下载

最近很多同学求助问补充文献怎么查找下载&#xff0c;补充文献一般会在文献的详情页&#xff0c;参考文献的上面。需要注意以下这些词汇&#xff1a;Supplementary data、Supplementary material、Appendix、Supplementary Information、Appendix A. Supplementary data、suppl…

上传ssh公钥到目标服务器

创建密钥 ssh-keygen -t rsa -b 4096 -C "xxxx.xx"上传 sudo ssh-copy-id -i /Users/xx/.ssh/id_rsa.pub root127.0.0.1

工作bug,keil5编译器,理解int 类型函数返回值问题,详解!!!

编写不易&#xff0c;禁止搬运&#xff0c;仅供学习&#xff0c;感谢理解 问题现象 下面是一个在keil5里面写的一个&#xff0c;int类型的返回值函数&#xff0c;这个函数里面&#xff0c;只有if else if else这三个判断条件语句&#xff0c;正常来说任何情况下&#xff0c;…

PHP语法学习(第七天)-循环语句,魔术常量

老套路了&#xff0c;朋友们&#xff0c;先回忆昨天讲的内容PHP语法学习(第六天)主要讲了PHP中的if…else语句、关联数组以及数组排序。 想要学习更多PHP语法相关内容点击“PHP专栏&#xff01;” 下列代码都是在PHP在线测试运行环境中得到的&#xff01;&#xff01; 还记得电…

ue5 motion matching

ue5.5 gameanimationsample 先看动画蓝图 核心两个node 第一个是根据数据选择当前的pose 第二个是缓存一段历史记录&#xff0c;为第一个node选择的时候提供数据。 在animinstance的update方法中 每帧都更新这个函数&#xff0c;每帧更新trajectory的数据 看看第一个node的…

【推导过程】常用共轭先验分布

文章目录 相关教程相关文献常用共轭先验分布预备知识贝叶斯统计后验分布的计算 正态均值(方差已知)的共轭先验分布是正态分布二项分布中的成功概率 θ 的共轭先验分布是贝塔分布正态均值(方差已知)的共轭先验分布是倒伽玛分布 作者&#xff1a;小猪快跑 基础数学&计算数学&…

消息队列(MQ):系统解耦与异步通信的利器

在现代分布式系统架构中&#xff0c;消息队列&#xff08;Message Queue&#xff0c;简称 MQ&#xff09;扮演着极为重要的角色。它作为一种中间件&#xff0c;能够有效地解决系统之间的耦合性问题&#xff0c;并实现高效的异步通信&#xff0c;极大地提升了系统的整体性能、可…

YOLO系列发展历程:从YOLOv1到YOLO11,目标检测技术的革新与突破

文章目录 前言一、YOLOv1&#xff1a;单阶段目标检测的开端二、YOLOv2&#xff1a;更精准的实时检测三、YOLOv3&#xff1a;阶梯特征融合四、YOLOv4&#xff1a;性能和速度的新平衡五、YOLOv5&#xff1a;易用性和扩展性的加强六、YOLOv6&#xff1a;工业部署的利器七、YOLOv7&…

ConcurrentLinkedQueue<>实现生产者-消费者问题理解和简易demo

1.ConcurrentLinkedQueue<> ConcurrentLinkedQueue 是 Java 中的一个线程安全的无界队列实现。它基于无锁&#xff08;lock-free&#xff09;的算法&#xff0c;采用了一个高效的、非阻塞的、可伸缩并发控制机制。这使得在高并发场景下能够实现较高的吞吐量。 无界性质…

【单元测试】单元测试介绍

1 单元测试基础 1.单元测试&#xff1a;单元测试又称模块测试&#xff0c;属于白盒测试&#xff0c;是最小单位的测试。模块分为程序模块和功能模块。功能模块指实现了一个完整功能的模块&#xff08;单元&#xff09;&#xff0c;一个完整的程序单元具备输入、加工和输出三个…

React废弃componentWillMount和componentWillReceiveProps这两个生命周期方法

React废弃componentWillMount和componentWillReceiveProps这两个生命周期方法的原因主要涉及到React的内部机制变更、性能优化以及未来特性的支持。以下是对这两个问题的详细解答&#xff1a; 废弃componentWillMount的原因 异步渲染的引入&#xff1a; React 16开始引入了异步…

RabbitMQ 实现分组消费满足服务器集群部署

实现思路 使用扇出交换机&#xff08;Fanout Exchange&#xff09;&#xff1a;扇出交换机会将消息广播到所有绑定的队列&#xff0c;确保每个消费者组都能接收到相同的消息。为每个消费者组创建独立的队列&#xff1a;每个消费者组拥有自己的队列&#xff0c;所有属于该组的消…

开发 UEFI 驱动

服务型驱动的特点&#xff1a; 1&#xff09;在 Image 的入口函数中执行安装&#xff1b; 2&#xff09;服务型驱动不需要驱动特定硬件&#xff0c;可以安装到任意控制器上&#xff1b; 3&#xff09;没有提供卸载函数。 一个设备 / 总线驱动程序在安装时首先要找到对应的硬件…