【知识点】图与图论入门

何为图论

见名知意,图论 (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.rhkb.cn/news/486660.html

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

相关文章

泷羽sec-burp(4)burp常见用法 以及 漏洞测试理论 学习笔记

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…

Linux上传代码的步骤与注意事项

最近因为工作需要&#xff0c;要上传代码到 DPDK 上&#xff0c;代码已经上传成功&#xff0c;记录一下过程&#xff0c;给大家提供一个参考。我这次需要上传的是pmd&#xff0c;即poll mode driver。 1 Coding Style 要上传代码&#xff0c;第一件事就是需要知道Coding Styl…

vllm0.5.0的v1/completions各参数说明

一、调用示例 curl -X POST \http://ip:8001/v1/completions \-H accept: application/json \-H Content-Type: application/json \-d {"model": "qwen-api","prompt": ["讲个中文笑话"],"best_of": 1,"n": 1,&qu…

Java项目实战II基于微信小程序的作品集展示(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着移动互联网技术的飞速…

物联网入门-Arduino的下载与配置教程(以ESP32为例)-2024

教程介绍 本次教程主要讲述如何下载与配置Arduino&#xff0c;以及开发版对应驱动的下载安装 原文链接&#xff1a;物联网入门-Arduino的下载与配置教程(以ESP32为例)-2024 步骤概述 1&#xff1a;下载Arduino 2&#xff1a;安装Arduino 3&#xff1a;下载安装驱动 4&am…

13.在 Vue 3 中使用OpenLayers加载鹰眼控件示例教程

在 WebGIS 开发中&#xff0c;鹰眼控件 是一个常用的功能&#xff0c;它可以为用户提供当前地图位置的概览&#xff0c;帮助更好地定位和导航。在本文中&#xff0c;我们将基于 Vue 3 的 Composition API 和 OpenLayers&#xff0c;创建一个简单的鹰眼控件示例。 效果预览 在最…

Flink如何基于数据版本使用最新离线数据

业务场景 假设批量有一张商户表&#xff0c;表字段中有商户名称和商户分类两个字段。 批量需要将最新的商户名称和分类的映射关系推到hbase供实时使用。 原实现方案 a.原方案内容 为解决批量晚批问题&#xff0c;批量推送hbase表时一份数据产生两类rowkey&#xff1a;T-1和…

从GCC源码分析C语言编译原理——源码表层分析(脚本篇)

目录 一、目录结构 二、有意思的小功能 三、install脚本 脚本变量和设置 程序名称变量 模式和命令 参数解析 主要逻辑 四、主要功能脚本 ------------------------------------------------------------------------------------------------------------------------…

Latex转word(docx)或者说PDF转word 一个相对靠谱的方式

0. 前言 投文章过程中总会有各种各样的要求&#xff0c;其中提供word格式的手稿往往是令我头疼的一件事。尤其在多公式的文章中&#xff0c;其中公式转换是一个头疼的地方&#xff0c;还有很多图表&#xff0c;格式等等&#xff0c;想想就让人头疼欲裂。实践中摸索出一条相对靠…

挑战用React封装100个组件【010】

Hello&#xff0c;大家好&#xff0c;今天我挑战的组件是这样的&#xff01; 今天这个组件是一个打卡成功&#xff0c;或者获得徽章后的组件。点击按钮后&#xff0c;会弹出礼花。项目中的勋章是我通过AI生成的&#xff0c;还是很厉害的哈&#xff01;稍微抠图直接使用。最后面…

企业实践|广州新华学院携手泰迪智能科技开展大数据开发企业实践圆满结束

12月3日&#xff0c;新华学院健康学院携手广东泰迪智能科技股份有限公司联合开展大数据开发企业实践活动圆满结束&#xff0c;健康学院专业老师陈键聪及来自信息资源管理专业2023级24名学生参与此次活动结业仪式。泰迪智能科技董事长张良均、校企合作经理吴桂锋、钟秋平出席。 …

设计模式的艺术读书笔记

设计模式的艺术 面向对象设计原则概述单一职责原则开闭原则里氏代换原则依赖倒转原则接口隔离原则合成复用原则迪米特法则 创建的艺术创建型模式单例模式饿汉式单例与懒汉式单例的讨论通过静态内部类实现的更好办法 简单工厂模式 面向对象设计原则概述 单一职责原则 单一职责…

深度和法线纹理

屏幕后期处理效果的基本原理就是当游戏画面渲染完毕后通过获取到该画面的信息进行额外的效果处理 之前的边缘检测、高斯模糊、Bloom、运动模糊等效果都是基于获取当前屏幕图像中的像素信息进行后期处理的 如果仅仅根据像素信息来进行一些效果处理&#xff0c;存在以下问题&…

WPF编写工业相机镜头选型程序

该程序满足面阵和线阵的要求。 前端代码 <Window x:Class"相机镜头选型.MainWindow" Loaded"Window_Loaded"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml…

ORCA:基于持续批处理的LLM推理性能优化技术详解

大语言模型(LLMs)推理过程中的批处理优化面临显著挑战&#xff0c;这主要源于其推理过程的迭代特性。核心问题在于批处理中的各个请求完成时间存在差异&#xff0c;这导致资源释放和新请求整合的复杂性显著提高&#xff0c;特别是在处理不同完成阶段的请求时。当批处理中序列的…

pwndbg快速计算栈溢出大小

1.启动pwndbg 生成长度为300的字符串 2.把生成的字符串复制粘贴到 run之后的程序下 查看错误提示“Invalid address 0x62616164” 3.根据错误地址&#xff0c;查看溢出大小

C++_关于异常处理throw-try-catch

文章目录 作用1. 无异常捕获2. 有异常捕获 作用 简单说&#xff0c;异常处理机制主要作用是让程序能够继续执行&#xff0c;或者以一种可控的方式终止&#xff0c;而非让程序因为错误直接崩溃 一个简单的动态数组类&#xff0c;来看看有异常捕获和无异常捕获的区别 1. 无异常…

云原生基础设施指南:精通 Kubernetes 核心与高级用法

1. 云原生的诞生 随着互联网规模的不断增长&#xff0c;以及企业对敏捷开发、快速交付和高可用性的需求日益增强&#xff0c;传统的单体架构逐渐暴露出局限性&#xff0c;难以满足现代业务对动态扩展和高效迭代的要求。为此&#xff0c;云原生应运而生。 云原生是为云计算时代…

如何用python获取图像

方法一&#xff1a;利用PIL中的Image函数&#xff0c;这个函数读取出来不是array格式 这时候需要用 np.asarray(im) 或者 np.array()函数&#xff1b;区别&#xff1a;np.array() 是深拷贝&#xff0c;np.asarray() 是浅拷贝。 from PIL import Image import numpy as npI Im…

[机器学习] 监督学习之线性回归与逻辑回归

这里写目录标题 一、监督学习概述二、线性回归&#xff08;一&#xff09;模型表示&#xff08;二&#xff09;损失函数&#xff08;三&#xff09;梯度下降算法导入所需库生成模拟数据&#xff08;可替换为真实数据&#xff09;初始化参数并进行训练可视化损失函数随迭代次数的…