数据结构与算法 | 图(Graph)
在这之前已经写了数组、链表、二叉树、栈、队列等数据结构,本篇一起探究一个新的数据结构:图(Graphs )。在二叉树里面有着节点(node)的概念,每个节点里面包含左、右两个子节点指针;比对于图来说同样有着节点(node),在图里也称为顶点(vertex)
,顶点之间的关联不在局限于2个(左、右),一个顶点可以与任意(0-n个)个顶点进行链接,这称之为边(edge)
。 一般会把一个图里面顶点的集合记作 V
,图里面边的集合记作 E
,图也就用 G(V,E)
来表示。
对比二叉树可以看到图的约束更少,换一个角度看二叉树结构是图的特殊形式,所谓特殊形式指加上更多的限定条件。
图的分类(Types Of Graph)
可以看到图的基本的结构非常简单,约束也很少,如果在其中加上各种条件约束就可以定义各种类型的图。
- 约束边或者顶点个数来分类:
零图(Null graph)
:只有顶点没有边的图;平凡图(Trivial graph)
:只有一个顶点的图;
- 按照边是否有指向来分类:
有向图(Directed Graph)
:在每个边的定义中,节点都是有序的对。也就是(A,B)与(B,A)表示不同的边,一个代表从A到B方向的边,一个代表从B到A方向的边。无向图(Undirected Graph)
:边只是代表链接,没有指向性。(A,B)与(B,A)表示的同样的边。
- 根据是否在边上存储数据分类:
权重图(Weighted Graph)
:图中的边上附加了权重或值的图。这些权重表示连接两个节点之间的距离、代价、容量或其他度量。权重可以是任何数值,通常用于描述节点间的关系特性。
还有很多分类在此不一一罗列。每类图可能还会有其独特的一些特征描述,比如有向图(Directed Graph)
里面,以某顶点作为开始的边的数量称为这个顶点的入度(Indegree)
,以某个顶点作为结束的边的数量称为这个顶点的出度(Outdegree)
等等。
通过以上描述,可以感受到图其实是非常灵活的数据结构,同时它的衍生概念也非常多;初次探究大可不必一一记牢,有个基本的图结构知识体系即可,后续遇到的时候再扩充图的知识体系更为合适。
图的表达(Representation of Graphs)
图的表达其实也有多种形式,不过最基本的形式是:邻接矩阵(Adjacency Matrix) 与 邻接表(Adjacency List)
邻接矩阵(Adjacency Matrix)
邻接矩阵,所谓“矩阵”具体到代码其实就是二维数组,通过二维数组来表示图中顶点之间的边的关系。二维数组中的行和列分别代表图中的顶点,矩阵中的值表示顶点之间是否相连或连接的边的权重。
且用这种方式来表示先前示例的图结构,矩阵的值 0代表无相连边,1代表有相连边。如下:
邻接表(Adjacency List)
邻接表,所谓“表”指的就是列表 List ,图中的每个节点都有一个对应的列表,用于存储与该节点直接相连的其他节点的信息。邻接表中的每个节点列表包含了该节点相邻节点的标识符或指针等信息。对于无权图,通常使用数组或链表来存储相邻节点的标识符。而对于带权图,列表中可能还包含了边的权重信息。
基本应用示例(Basic Examples)
Leetcode 997. 找到小镇的法官【简单】
小镇里有 n 个人,按从 1 到 n 的顺序编号。传言称,这些人中有一个暗地里是小镇法官。
如果小镇法官真的存在,那么:
小镇法官不会信任任何人。
每个人(除了小镇法官)都信任这位小镇法官。
只有一个人同时满足属性 1 和属性 2 。
给你一个数组 trust ,其中 trusti = ai, bi 表示编号为 ai 的人信任编号为 bi 的人。
如果小镇法官存在并且可以确定他的身份,请返回该法官的编号;否则,返回 -1 。
示例
输入:n = 2, trust = [1,2]
输出:2
题目故事背景描述比较多,可以看到 信任的表述 可以用有向图
的边
来表示,每个人 用顶点
来表示,小镇法官的第1点 代表就是出度
为 0
,第2点 代表就是 入度
为 n-1
。 这样题目就转换为:判断一个n个顶点的有向图中 是否存在出度为0,入度为n-1的顶点 ;存在返回顶点编号,不存在返回 -1。
PS:关键点,将复杂描述的题目,建模成为图
public int findJudge(int n, int[][] trust) {
int[] outDegree = new int[n+1],inDegree = new int[n+1];
for(int i = 0; i < trust.length; i++){
outDegree[trust[i][0]] ++;
inDegree[trust[i][1]]++;
}
for(int i=1; i<= n; i++)
if(outDegree[i] == 0 && inDegree[i] == (n-1))
return i;
return -1;
}
Leetcode 787. K 站中转内最便宜的航班【中等】
有 n 个城市通过一些航班连接。给你一个数组 flights ,其中 flightsi = fromi, toi, pricei ,表示该航班都从城市 fromi 开始,以价格 pricei 抵达 toi。
现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,你的任务是找到出一条最多经过 k 站中转的路线,使得从 src 到 dst 的 价格最便宜 ,并返回该价格。 如果不存在这样的路线,则输出 -1。
示例
输入: n = 3, edges = [0,1,100,1,2,100,0,2,500],src = 0, dst = 2, k = 1
输出: 200
备注:1 <= n <= 100,航班没有重复,且不存在自环
将城市看作是顶点,城市-城市之间的航班看作是 有向图边,航班的价格作为边的权重,也就完成了题意到图的建模。考虑到,城市数量 n < 100, 因此可以采用 邻接矩阵的方式来进行图的表达。
public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
// 图 初始化建模
int[][] map = new int[n][n];
for(int i = 0; i < flights.length; i++){
map[flights[i][0]][flights[i][1]] = flights[i][2];
}
// 其他逻辑
}
以 src 作为 源顶点,通过以 src作为 起始顶点的边 链接到更多的顶点(此时经过 0个站中转);以这些链接到的顶点 为起始点,继续链接到更多的顶点(经过 1个站中转);继而可以推导到 经过 n 个站中转。这也就是典型的广度优先搜索(BFS),来遍历以src作为 源顶点的图,遍历代码如下:
public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
// ...
// BFS
Deque<Integer> que = new ArrayDeque<>();
// src 作为起始点
que.offer(src);
// 经过 k 个中转站
for(int i = 0; i <= k && !que.isEmpty(); i++){
int size = que.size();
while( size-- > 0){
int node = que.poll();
for(int j = 0; j < map[node].length; j++){
// map[node][j] == 0 代表 node -> 不相连跳过
if( map[node][j] == 0) continue;
// ... 这里可以加入遍历过程中更多的逻辑
// 进入下一轮遍历
que.offer(j);
}
}
}
// ...
}
考虑题目需要的是 最多经过 k 站中转的 最便宜线路,不妨 广度优先遍历中 用 distSet[]
记录下 src 可到达站点的 最低价格;最后返回 distSet[ dst ]
即可, 这里注意下的是 如果没到达,按照题意应返回 -1
。
public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
// ...
int[] distSet = new int[n];
que.offer(src);
for(int i = 0; i <= k && !que.isEmpty(); i++){
// 判断当前最小的 标准 是基于上一轮的遍历结果
int[] pre = Arrays.copyOf(distSet,distSet.length);
int size = que.size();
while( size-- > 0){
int node = que.poll();
for(int j = 0; j < map[node].length; j++){
if( map[node][j] == 0) continue;
// distSet[j] == 0 代表之前没有到达过,因此需要 写入 distSet[j]
// 如果当前距离 不之前大,这个顶点不必进行下一轮遍历
if( distSet[j] != 0 &&
distSet[j] < pre[node] + map[node][j]) continue;
// 记录最小结果
distSet[j] = pre[node] + map[node][j] ;
que.offer(j);
}
}
}
// distSet[j] == 0 代表之前没有到达过,返回 -1
return distSet[dst] == 0 ? -1:distSet[dst];
}
这里其实是 使用 Bellman-Ford 算法
的思想进行解题;在图算法领域还有着很多著名的算法,后续可以整理下更专业的解读,这里只是演示个简单的应用。
Bellman-Ford 算法
,最初由Alfonso Shimbel 1955年提出,但以 Richard Bellman 和 Lester Ford Jr.的名字命名,他们分别于 1958年 和 1956年 发表了该算法,向前辈致敬。
最后附上完整代码:
public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
int[][] map = new int[n][n];
for(int i = 0; i < flights.length; i++){
map[flights[i][0]][flights[i][1]] = flights[i][2];
}
int[] distSet = new int[n];
Deque<Integer> que = new ArrayDeque<>();
que.offer(src);
for(int i = 0; i <= k && !que.isEmpty(); i++){
int[] pre = Arrays.copyOf(distSet,distSet.length);
int size = que.size();
while( size-- > 0){
int node = que.poll();
for(int j = 0; j < map[node].length; j++){
if( map[node][j] == 0) continue;
if( distSet[j] != 0 &&
distSet[j] < pre[node] + map[node][j]) continue;
distSet[j] = pre[node] + map[node][j] ;
que.offer(j);
}
}
}
return distSet[dst] == 0 ? -1:distSet[dst];
}
欢迎关注 Java研究者 专栏、博客、公众号等。大伙儿的喜欢是创作最大的动力。
数据结构与算法 | 图(Graph)的更多相关文章
- python数据结构与算法——图的基本实现及迭代器
本文参考自<复杂性思考>一书的第二章,并给出这一章节里我的习题解答. (这书不到120页纸,要卖50块!!,一开始以为很厚的样子,拿回来一看,尼玛.....代码很少,给点提示,然后让读者自 ...
- python数据结构与算法——图的广度优先和深度优先的算法
根据维基百科的伪代码实现: 广度优先BFS: 使用队列,集合 标记初始结点已被发现,放入队列 每次循环从队列弹出一个结点 将该节点的所有相连结点放入队列,并标记已被发现 通过队列,将迷宫路口所有的门打 ...
- python数据结构与算法——图的最短路径(Floyd-Warshall算法)
使用Floyd-Warshall算法 求图两点之间的最短路径 不允许有负权边,时间复杂度高,思路简单 # 城市地图(字典的字典) # 字典的第1个键为起点城市,第2个键为目标城市其键值为两个城市间的直 ...
- python数据结构与算法——图的最短路径(Dijkstra算法)
# Dijkstra算法——通过边实现松弛 # 指定一个点到其他各顶点的路径——单源最短路径 # 初始化图参数 G = {1:{1:0, 2:1, 3:12}, 2:{2:0, 3:9, 4:3}, ...
- python数据结构与算法——图的最短路径(Bellman-Ford算法)解决负权边
# Bellman-Ford核心算法 # 对于一个包含n个顶点,m条边的图, 计算源点到任意点的最短距离 # 循环n-1轮,每轮对m条边进行一次松弛操作 # 定理: # 在一个含有n个顶点的图中,任意 ...
- 数据结构与算法-图的最短路径Dijkstra
一 无向图单源最短路径,Dijkstra算法 计算源点a到图中其他节点的最短距离,是一种贪心算法.利用局部最优,求解全局最优解. 设立一个visited访问和dist距离数组,在初始化后每一次收集一 ...
- 数据结构与算法——图(游戏中的自动寻路-A*算法)
在复杂的 3D 游戏环境中如何能使非玩家控制角色准确实现自动寻路功能成为了 3D 游戏开 发技术中一大研究热点.其中 A*算法得到了大量的运用,A*算法较之传统的路径规划算法,实时性更高.灵活性更强, ...
- python数据结构与算法
最近忙着准备各种笔试的东西,主要看什么数据结构啊,算法啦,balahbalah啊,以前一直就没看过这些,就挑了本简单的<啊哈算法>入门,不过里面的数据结构和算法都是用C语言写的,而自己对p ...
- 算法与数据结构基础 - 图(Graph)
图基础 图(Graph)应用广泛,程序中可用邻接表和邻接矩阵表示图.依据不同维度,图可以分为有向图/无向图.有权图/无权图.连通图/非连通图.循环图/非循环图,有向图中的顶点具有入度/出度的概念. 面 ...
- 数据结构与算法系列研究七——图、prim算法、dijkstra算法
图.prim算法.dijkstra算法 1. 图的定义 图(Graph)可以简单表示为G=<V, E>,其中V称为顶点(vertex)集合,E称为边(edge)集合.图论中的图(graph ...
随机推荐
- 【.NET6 + Vue3 + CentOS7.9 + Docker + Docker-Compose + SSL】个人博客前后端运维部署
个人博客 前端:https://lujiesheng.cn 个人博客 后端:https://api.lujiesheng.cn 个人博客 运维:https://portainer.lujiesheng ...
- TCP的可靠性之道:确认重传和流量控制
TCP 全称为 Transmission Control Protocol(传输控制协议),是一种面向连接的.可靠的.基于字节流的传输层通信协议,其中可靠性是相对于其他传输协议的优势点.TCP 为了确 ...
- AI绘画StableDiffusion实操教程:可爱头像奶茶小女孩(附高清图片)
本教程收集于:AIGC从入门到精通教程汇总 今天继续分享AI绘画实操教程,如何用lora包生成超可爱头像奶茶小女孩 放大高清图已放到教程包内,需要的可以自取. 欢迎来到我们这篇特别的文章--<A ...
- JSTL fn函数使用总结
首先,我们要在页面的最上方引用: <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/function ...
- 开源.NetCore通用工具库Xmtool使用连载 - 扩展动态对象篇
[Github源码] <上一篇> 介绍了Xmtool工具库中的图形验证码类库,今天我们继续为大家介绍其中的扩展动态对象类库. 扩展动态对象是整个工具库中最重要的一个设计.在软件开发过程中, ...
- Python实现与MySQL长连接的客户端
下面的代码是使用Python建立的和MySQL长连接的简单客户端示例. 当和MySQL的连接断开后,会自动进行重连(被动式的重连,即只有调用增self.execute().删self.execute( ...
- mooc第四单元《管理团队》单元测试
第四单元<管理团队>单元测试 返回 本次得分为:6.00/10.00, 本次测试的提交时间为:2020-08-30, 如果你认为本次测试成绩不理想,你可以选择 再做一次 . 1 判断(2分 ...
- zend framework 数据库操作(DB操作)总结
(1)数据查询总结 fetchRow()这个方法返回一行,具体返回是相关数组还是什么用setFetchMode()决定fetchCol()返回第一列fetchOne()返回第一行,第一列.为一个值不是 ...
- 学习一下Java的ArrayList和contains函数和扩容机制
起因 在Leetcode上做题写了两种暴力解法,但是执行效率上不太一样. 时间上差很远,内存虽然差不多但是前者击败30%,后者击败94%.这两种解法区别是用一条ArrayList还是两条来存数据,所以 ...
- Chromium Command Buffer原理解析
Command Buffer 是支撑 Chromium 多进程硬件加速渲染的核心技术之一.它基于 OpenGLES2.0 定义了一套序列化协议,这套协议规定了所有 OpenGLES2.0 命令的序列化 ...