【知识点】图与图论入门

何为图论

见名知意,图论 (Graph Theory) 就是研究 图 (Graph) 的数学理论和方法。图是一种抽象的数据结构,由 节点 (Node) 和 连接这些节点的 边 (Edge) 组成。图论在计算机科学、网络分析、物流、社会网络分析等领域有广泛的应用。

如下,这就是一个图,可以看到这个图有 5 5 5 个顶点,分别编号为 { 0 , 1 , 2 , 3 , 4 } \{0, 1, 2, 3, 4\} {0,1,2,3,4}。同时这个图有 4 4 4 条边,例如,在顶点 2 2 2 和 顶点 4 4 4 之间存在着一条边。

image

图的基本概念

在详细讲解图论和有关图论算法之前,先来了解一下在图论中的一些基本表述和规范。

  1. 图 (Graph):图是一种由一组顶点和一组边组成的数据结构,记做 G = ( V , E ) G = (V, E) G=(V,E),其中 V V V 代表顶点集合, E E E​ 代表边集合。
  2. 顶点 (Vertex):顶点是图的基本单位,也称为节点。
  3. 边 (Edge):一条边是连接两个顶点的线段或弧。可以是无向的,也可以是有向的。一条边可以记做为 ( u , v ) (u, v) (u,v)。在无向图中,若存在一条 ( u , v ) (u, v) (u,v),表示可以从 u u u 点直接走到 v v v 点,反之亦然。但若在有向图中,存在一条边 ( u , v ) (u, v) (u,v),表示可以从 u u u 节点直接走向 v v v 节点,如果不存在一条边 v , u v, u v,u,那么 v v v 节点就没有办法直接走向 u u u 节点。
  4. 无向图 (Undirected Graph):图中的边没有方向,即 ( u , v ) (u, v) (u,v) ( v , u ) (v, u) (v,u) 是同一条边。
  5. 有向图 (Directed Graph/ Digraph):图中的边有方向,即 ( u , v ) (u, v) (u,v) ( v , u ) (v, u) (v,u)​ 不是同一条边。
  6. 简单图 (Simple Graph):表示含有重边(两个顶点之间的多条边)和自环(顶点到自身的边)的图。
  7. 多重图 (Multigraph):允许有重边和自环的图。
  8. 边权 (Weight of an Edge):一般表示经过这一条边的代价(代价一般是由命题人定义的)。

如下图,就是一个有向的简单图(通常来说,在有向图中边的方向用箭头来表示):

image

如下图,就是一个无向的多重图,其中存在两条边可以从顶点 5 5 5 到顶点 2 2 2

image

与此同时,为了方便起见,对于无向图的处理,我们只需要在两个顶点之间建立两个方向相反的无向边就可以表示一个无向图,具体如下:

image

图的表示方法

在计算机中,图可以通过许多方式来构建和表示。总的可以分成图的邻接矩阵和邻接表两种方法(关于链式前向星本文不过多展开叙述,有兴趣的可以自行查阅相关文档)。

图的邻接矩阵 (Adjacency Matrix)

若一个图中有 N N N 个顶点,那么我们就可以用一个 N × N N \times N N×N 的矩阵来表示这个图。我们一般定义,若矩阵的元素 A i , j ≠ − ∞ A_{i, j} \neq -\infty Ai,j= 表示从节点 i i i j j j 有一条有向边,其中边的权值为 A i , j A_{i, j} Ai,j​。

假设存在一个有 3 3 3 个顶点的图,并且有三条有向边 E = { ( 1 , 2 ) , ( 2 , 3 ) , ( 3 , 2 ) } E = \{(1, 2), (2, 3), (3, 2)\} E={(1,2),(2,3),(3,2)},那么就可以用邻接矩阵表示为:
G = [ 1 2 3 1 0 1 0 2 0 0 1 3 0 1 0 ] G = \begin{bmatrix} & \mathtt{1} & \mathtt{2} & \mathtt{3}\\ \mathtt{1} & 0 & 1 & 0 \\ \mathtt{2} & 0 & 0 & 1 \\ \mathtt{3} & 0 & 1 & 0 \end{bmatrix} G= 123100021013010
画成可视化的图就长这个样子:

image

在 C++ 中,我们可以简单地用一个二维数组来表示:

// 定义一个矩阵。
int map[50][50];// 将所有的边初始化为负无穷大。
for (int i=1; i<=50; i++)for (int j=1; j<=50; j++)map[i][j] = -0x7f7f7f7f;// 建边,其中所有的边权为1。
map[1][2] = map[2][3] = map[3][2] = 1;

图的邻接表 (Adjacency List)

邻接表本质上就是用链表表示图。数组的每个元素表示一个顶点,元素的值是一个链表,链表中存储该顶点的所有邻接顶点。假设存在一个有 4 4 4 个顶点的图,并且有四条有向边 E = { ( 1 , 2 ) , ( 2 , 3 ) , ( 3 , 2 ) , ( 3 , 4 ) } E = \{(1, 2), (2, 3), (3, 2), (3, 4)\} E={(1,2),(2,3),(3,2),(3,4)},那么就可以用邻接表表示为:

image

画成可视化的图就长这个样子:

image

在 C++ 中,我们可以使用 STL模板库 中的 vector 来实现:

#include <vector>
vector<int> G[50];  // 建图。
G[1].push_back(2);
G[2].push_back(3);
G[3].push_back(2);
G[3].push_back(4);

一般情况下,推荐使用邻接表的方式来存图,因为使用邻接矩阵比较浪费空间。在顶点数量非常多但边非常少的图中, N 2 N^2 N2 的时空复杂度会导致 MLE 或 TLE 等问题。

图的各种性质

  1. 度数 (Degree):一个顶点的度是连接该顶点的边的数量。在有向图中,有 入度 (Indegree)出度 (Outdegree) 之分(具体例子见后文)。
  2. 路径 (Path):从一个顶点到另一个顶点的顶点序列,路径上的边没有重复。
  3. 回路 (Cycle):起点和终点相同的路径。
  4. 连通图 (Connected Graph):任意两个顶点之间都有路径相连的无向图。
  5. 强连通图 (Strongly Connected Graph):任意两个顶点之间都有路径相连的有向图。

对于下面这个无向图不连通图,顶点 1 1 1 的度数为 1 1 1;顶点 2 2 2 的度数为 2 2 2;顶点 3 3 3 的度数为 1 1 1;顶点 4 4 4 的度数为 0 0 0。同时,由于 4 4 4 号顶点没有度数,所以该顶点没有办法到达任何一个其他的顶点,所以这个图是一个不连通图:

image

如下图,就是一个有向不强连通图。其中,顶点 1 1 1 的入度为 0 0 0,出度为 2 2 2;顶点 2 2 2 的入度为 1 1 1,出度也为 1 1 1;顶点 3 3 3 的入度为 2 2 2,但出度为 0 0 0。由于顶点 1 1 1 和顶点 2 2 2 可以走到顶点 3 3 3,但顶点 3 3 3 没有办法走到顶点 1 1 1 或顶点 2 2 2,因此下面的图不是一个强连通图:

image

对于下图来说, 1 → 2 → 3 → 4 1\to 2\to 3\to 4 1234 是一条从顶点 1 1 1 到顶点 4 4 4的路径。 2 → 3 → 4 → 2 → 3 2\to 3\to 4 \to 2\to 3 23423 就不是一个路径,因为相同的边 ( 2 , 3 ) (2, 3) (2,3) 被多次走到了。 1 → 2 → 3 → 1 1\to 2\to 3\to 1 1231 就是一个回路,因为这个路径的起点和终点相同:

image

图的遍历

图通常采用 深度优先搜索/ 广度优先搜索 这两个算法来遍历。其中深度优先算法是最常见的遍历算法。

对于一个用 邻接矩阵 保存的图,其深度优先搜索遍历的 C++ 代码如下:

int vis[105], map[105][105];void dfs(int node){if (vis[node]) return ;vis[node] = 1;cout << node << endl;for (int i=1; i<=n; i++)if (map[node][i] != -0x7f7f7f7f)dfs(i);return ;
}// 函数调用:dfs(1); 表示从1号顶点开始遍历。

对于一个用 邻接表 保存的图,其深度优先搜索遍历的 C++ 代码如下:

#include <vector>
vector<int> G[105];
int vis[105];void dfs(int node){if (vis[node]) return ;vis[node] = 1;cout << node << endl;for (int to : G[node])dfs(to);return ;
}// 函数调用:dfs(1); 表示从1号顶点开始遍历。

广度优先搜索的方式也类似:

#include <queue>
vector<int> G[105];
int vis[105];void bfs(int node){queue<int> que;que.push(node);while(!que.empty()){int t = que.front();cout << t << endl;que.pop();for (int to : G[node]){if (!vis[to]) {vis[to] = 1;que.push(to);}}}return ;
}// 函数调用:bfs(1); 表示从1号顶点开始遍历。

对于判断无向图的连通性,我们只需要从任意一个点开始跑一遍深搜或者广搜就行了。如果所有顶点的 vis 都被标记了,则证明图是联通的,否则图就是不连通的。

例题讲解

P3916 图的遍历

模板题目,从每一个顶点开始用深搜遍历一遍就可以了。但从每一个点考虑能走到的最大点比较麻烦,一个更优的解决办法是反向建边,从最大的点开始遍历,这样子就可以一次性计算出多个结果。

#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;const int N = 10005;
int n, m, ans, vis[N];
vector<int> G[N];void dfs(int node, int d){if (vis[node]) return ;vis[node] = d;ans = max(node, ans);for (int to : G[node]) dfs(to, d);return ;
}int main(){cin >> n >> m;for (int i=0, u, v; i<m; i++){cin >> u >> v;G[v].push_back(u);  // 反向建边。}for (int i=n; i>=1; i--) dfs(i, i);for (int i=1; i<=n; i++) cout << vis[i] << ' ';return 0;
}

P5318 【深基18.例3】查找文献

也是一道模板题目,正常遍历即可。

#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
const int MAXN = 100005;int n, m;
int vis1[MAXN], vis2[MAXN];
queue<int> que;
vector<int> G[MAXN];void dfs(int node, int current){vis1[node] = 1;cout << node << ' ';if (current == n) return ;for (int i=0; i<G[node].size(); i++){if (vis1[G[node][i]]) continue;dfs(G[node][i], current+1);}return ;
}void dfs(int node){vis2[node] = 1;que.push(node);while(que.size()){int t = que.front();cout << t << " ";for (int i=0; i<G[t].size(); i++){if (vis2[G[t][i]]) continue;vis2[G[t][i]] = 1;que.push(G[t][i]);}que.pop();}return ;
}int main(){cin >> n >> m;for (int i=0; i<m; i++){int t1, t2;cin >> t1 >> t2;G[t1].push_back(t2);}for (int i=1; i<=n; i++) sort(G[i].begin(), G[i].end());dfs(1, 0), cout << endl, dfs(1);return 0;
}

番外 - 图的常见算法

更多关于图论的算法,请持续关注后续更新。

  1. 深度优先搜索 (DFS):适用于遍历图和检测图中的回路。
  2. 广度优先搜索 (BFS):适用于寻找最短路径(无权图)。
  3. Dijkstra 算法:适用于加权图中寻找单源最短路径。
  4. Bellman-Ford 算法:适用于有负权边的图中寻找单源最短路径。
  5. Floyd-Warshall 算法:适用于寻找所有顶点对之间的最短路径。
  6. Kruskal 算法:用于求解最小生成树 (MST - Minimum Spanning Tree)。
  7. Prim 算法:另一种求解最小生成树的方法。
  8. 拓扑排序 (Topological Sorting):适用于有向无环图 (DAG),用于任务调度等应用。
  9. Tarjan 算法:用于求解图中的强连通分量、割点、桥。

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

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

相关文章

【书生大模型实战营】Linux 基础知识-L0G1000

前言&#xff1a;书生大模型实战营是上海人工智能实验室开展的大模型系列实践活动&#xff0c;提供免费算力平台&#xff0c;学员通过闯关式任务&#xff0c;可获得免费算力和存储&#xff0c;助力项目实践。本期是第4期&#xff0c;时间从十一月份开始&#xff0c;持续到十二月…

【软件安全】软件安全设计规范,软件系统安全设计制度(Word原件)

1.1安全建设原则 1.2 安全管理体系 1.3 安全管理规范 1.4 数据安全保障措施 1.4.1 数据库安全保障 1.4.2 操作系统安全保障 1.4.3 病毒防治 1.5安全保障措施 1.5.1实名认证保障 1.5.2 接口安全保障 1.5.3 加密传输保障 1.5.4终端安全保障 软件全面文档清单涵盖以下核心内容&a…

你是如何找bug的?bug分析的正确打开方式

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 Bug严重级别(Severity&#xff0c;Bug级别)&#xff1a;是指因缺陷引起的故障对软件产品的影响程度&#xff0c;由测试人员指定。 A-Crash&#xff1a;造成系统或…

WGCNA分析流程及内容解读

文章标题&#xff1a;通过 WGCNA 和机器学习识别和验证与免疫和氧化应激相关的糖尿病肾病诊 发表年限&#xff1a;2023年 期刊&#xff1a;Frontiers in Immunology 影响因子&#xff1a;5.7 研究背景&#xff1a; 糖尿病肾病&#xff08;DN&#xff09;以蛋白尿、高血压和…

【MySQL 进阶之路】锁详解

MySQL 锁详解 1. 锁的基本概念 锁在数据库中是用来保证数据一致性和防止并发冲突的一种机制。MySQL 中的锁可以分为不同的类型和粒度&#xff0c;每种锁都有特定的使用场景和特点。了解锁的类型、作用以及如何避免锁带来的问题是提升数据库性能和避免数据冲突的关键。 2. 锁…

qsort函数用法,以及模拟实现

函数定义 基于快速排列的一个函数&#xff0c;它的四个参数的含义&#xff1a; 1、void* base&#xff0c;是排序开始的地址 2、size_t num 想要排列的个数 3、size_t size 排列对象&#xff0c;一个占多少空间&#xff08;字节&#xff09; 4、int (*comper)(void*,voi…

CentOS7虚拟机 网络适配器 NAT模式和桥接模式区别

一、环境介绍 宿主机&#xff1a;Windows电脑 虚拟机&#xff1a;VMware下的CentOS7 局域网&#xff1a;路由器下的各真实主机组成的网络 内部局域网&#xff1a;宿主机构建的一个内部网路 二、NAT和桥接网络链接模式区别 NAT模式&#xff1a;相当于宿主机构建一个内部局域网&a…

全面解析DApp开发中的智能合约设计

在DApp的开发过程中&#xff0c;智能合约的设计起到了至关重要的作用。智能合约是运行在区块链上的程序&#xff0c;负责处理和执行DApp中的逻辑、交易和数据存储。下面我们将深入探讨智能合约的设计原则、挑战和优化方法&#xff0c;帮助开发者掌握如何设计高效、安全的智能合…

锐捷Web认证

文章目录 Web认证二代 Web 认证配置 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Datacom专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年12月6日11点40分 Web认证 Portal 认证、Web认证 Web认证的介绍 Web 认证使用浏览器进行身份验…

KernelShark在ubuntu24.04.01的编译

KernelShark在ubuntu24.04.01的编译 写在前面具体过程装ubuntu24.04.01安装depends下载代码如何编译cmake 输出make 输出 如何安装 初步启动Add the User to the perf Group 简单的使用trace-cmd抓包 来看我的文章&#xff0c;必有所得。 平凡中&#xff0c;总有我帮您踩过的坑…

解决Unity编辑器Inspector视图中文注释乱码

1.问题介绍 新创建一个脚本&#xff0c;用VS打开编辑&#xff0c;增加一行中文注释保存&#xff0c;在Unity中找到该脚本并选中&#xff0c;Inspector视图中预览的显示内容&#xff0c;该中文注释显示为乱码&#xff0c;如下图所示&#xff1a; 2.图示解决步骤 按上述步骤操作…

【AIGC半月报】AIGC大模型启元:2024.12(上)

【AIGC半月报】AIGC大模型启元&#xff1a;2024.12&#xff08;上&#xff09; &#xff08;1&#xff09;OpenAI-12日发布会&#xff08;持续更新中........&#xff09;Day01-12.06&#xff1a;o1满血版上线&#xff08;已发布&#xff09;Day02-12.07&#xff1a;强化微调&a…

字节码

字节码 1、字节码文件概述 1.1、字节码文件是跨平台的吗&#xff1f; ​ Java 虚拟机不和包括 Java 在内的任何语言绑定&#xff0c;它只与“Class 文件”这种特定的二进制文件格式所关联。 ​ 无论使用何种语言进行软件开发&#xff0c;只要能将源文件编译为正确的Class文件…

排查bug的通用思路

⭐️前言⭐️ APP点击某个按钮没有反应/PC端执行某个操作后&#xff0c;响应较慢&#xff0c;通用的问题排查方法: 从多个角度来排查问题 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f349;博主将持续更新学习记录收获&#xff0c;友友们有任何问题可以在评…

AI 建站:Durable

网址&#xff1a;https://app.durable.co 步骤 1) 登录 2&#xff09;点击创建新业务 3&#xff09;填写信息后&#xff0c;点击创建 4&#xff09;进入业务 5&#xff09;生成网站 6&#xff09;生成完成后不满意的话可以自己调整 7&#xff09;点击保存 8&#xff09;发布 …

部署kafka并通过python操作

目录 一、安装JDK1.81、检查服务器是否已安装JDK2、若已安装JDK&#xff0c;进行卸载3、更新yum源4、搜索JDK1.8安装包5、安装JDK1.86、查看是否安装成功7、配置环境变量 二、安装Kafka1、下载并解压kafka部署包至/usr/local/目录2、修改server.properties3、修改/etc/profile4…

【Linux】 进程池 一主多从 管道通信

目录 1.代码介绍 2.channel 类 3.进程池类编写 4.主函数及其他 5. 源码 1.代码介绍 本文代码采用一主多从式&#xff08;一个主进程&#xff08;master&#xff09;多个子进程&#xff08;worker&#xff09;&#xff09;通过管道进行通信&#xff0c;实现主进程分发任务&…

Ubuntu环境安装RabbitMQ

1.安装Erlang RabbitMq需要Erlang语⾔的⽀持&#xff0c;在安装rabbitMq之前需要安装erlang # 更新软件包 sudo apt-get update # 安装 erlang sudo apt-get install erlang 查看erlang版本 : erl 退出命令:halt(). 2. 安装RabbitMQ # 更新软件包 sudo apt-get update # 安装 …

力扣--199.二叉树的右视图

题目 给定一个二叉树的 根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 提示: 二叉树的节点个数的范围是 [0,100] -100 < Node.val < 100 代码 class Solution { public List rightSideV…

Ubuntu Server 22.04.5 LTS重启后IP被重置问题

Ubuntu Server 22.04.5 LTS重启后IP被重置问题 最近在使用Ubuntu Server 22.04做项目开发测试时发现每次重启和关机后&#xff0c;所设置的静态IP地址都会回复到安装系统时所设置的ip Ubuntu Server 22.04 官网下载地址&#xff1a;Ubuntu官方下载地址 对虚拟机下安装Ubuntu感…