每天进步一点点【图的深度优先搜索与广度优先搜索】

2023-11-17

图是一种数据结构,其中节点可以具有零个或多个相邻元素。两个节点之间的连接称为边。节点也可以称为顶点。

图分为三种:无向图、有向图、带权图

图的表示方式有两种:二维数组表示(邻接矩阵);链表表示(邻接表)

邻接矩阵

邻接矩阵是表示图形中顶点之间相邻关系的矩阵,对于n个顶点的图而言,矩阵的行和列表示的是1……n个点。邻接矩阵需要为每个顶点都分配n个边的空间,其实有很多边都是不存在的,会造成空间的一定损失。

3aa9b880fe0ee2bf786e3da991c8eff7.png

邻接表

连接表的实现只关心存在的边,不关心不存在的边。因此没有空间浪费,邻接表由数组+链表组成

83f580b7cedd3fc01c3491c856341056.png

说明:

  1. 标号为0的节点的相关联的节点为1,2,3,4

  2. 标号为1的节点为0,4

  3. 标号为2的节点的相关联的节点为0,4,5

  4. ……

代码实现图结构
7374d643664d3a0f9b2a67d433885db4.png

/**
 * 邻接矩阵表示图
 * @author sixiaojie
 * @date 2022-01-07-19:05
 */
public class Graph {
    /** 存储顶点集合 */
    private ArrayList<String> vertexList;

    /** 存储图对应的邻接矩阵 */
    private int[][] edges;

    /** 表示边的数目 */
    private int numOfEdges;

    public static void main(String[] args) {
        // 顶点个数
        int n = 5;
        String[] vertexs = {"A","B","C","D","E"};
        Graph graph = new Graph(n);
        // 添加顶点
        for (String vertexValue : vertexs) {
            graph.insertVertex(vertexValue);
        }
        // 添加边 A-B A-C B-C B-D B-E
        graph.insertEdge(0,1,1);// A-B
        graph.insertEdge(0,2,1);// A-C
        graph.insertEdge(1,2,1);// B-C
        graph.insertEdge(1,3,1);// B-D
        graph.insertEdge(1,4,1);// B-E

        // 显示
        graph.showGraph();

    }


    /**
     * 构造器
     */
    public Graph(int n) {
        // 初始化矩阵和vertexList
        edges = new int[n][n];
        vertexList = new ArrayList<String>(n);
        numOfEdges = 0;
    }

    /**
     * 插入顶点
     * @param vertex
     */
    public void insertVertex(String vertex){
        vertexList.add(vertex);
    }

    /**
     * 添加边
     * @param v1 第一个顶点对应的下标
     * @param v2 第二个顶点对应的下标
     * @param weight 权值
     */
    public void insertEdge(int v1,int v2,int weight){
        edges[v1][v2] = weight;
        edges[v2][v1] = weight;
        numOfEdges ++ ;
    }

    /**
     * 返回顶点个数
     * @return
     */
    public int getNumOfVertex(){
        return vertexList.size();
    }

    /**
     * 返回边的数目
     * @return
     */
    public int getNumOfEdges(){
        return numOfEdges;
    }

    /**
     * 返回顶点i(下标)对应的数据0->'A',1->'B',2->'C'
     * @param index
     * @return
     */
    public String getValueByIndex(int index){
        return vertexList.get(index);
    }

    /**
     * 返回v1,v2的权值
     * @param v1
     * @param v2
     * @return
     */
    public int getWeiht(int v1, int v2){
        return edges[v1][v2];
    }

    /**
     * 显示对应的矩阵
     */
    public void showGraph(){
        for (int[] link : edges) {
            System.out.println(Arrays.toString(link));
        }

    }
}

图的遍历

图的遍历是指,从给定图中任意指定的顶点(称为初始点)出发,按照某种搜索方法沿着图的边访问图中的所有顶点,使每个顶点仅被访问一次。遍历过程中得到的顶点序列称为图遍历序列。

图的遍历过程中,根据搜索方法的不同,又可以划分为两种搜索策略:

  • 深度优先搜索

  • 广度优先搜索

深度优先搜索(DFS,Depth First Search)

深度优先搜索,从起点出发,从规定的方向中选择其中一个不断地向前走,直到无法继续为止,然后尝试另外一种方向,直到最后走到终点。就像走迷宫一样,尽量往深处走。

DFS 解决的是连通性的问题,即,给定两个点,一个是起始点,一个是终点,判断是不是有一条路径能从起点连接到终点。

假设我们有这么一个图,里面有A、B、C、D、E、F、G、H 8 个顶点,点和点之间的联系如下图所示, 对这个图进行深度优先的遍历。

a61193e99e0b0118e461c822826ec878.png

依赖栈(Stack),特点是后进先出(LIFO)。

第一步,选择一个起始顶点,例如从顶点 A 开始。把 A 压入栈,标记它为访问过(用红色标记),并输出到结果中。

6d2359f107fd4ccf046e4859c10f9ebb.png

第二步,寻找与 A 相连并且还没有被访问过的顶点,顶点 A 与 B、D、G 相连,而且它们都还没有被访 问过,我们按照字母顺序处理,所以将 B 压入栈,标记它为访问过,并输出到结果中。

15ae7a0e55824f05ab0da69f85f9fcf2.png

第三步,现在我们在顶点 B 上,重复上面的操作,由于 B 与 A、E、F 相连,如果按照字母顺序处理的话,A 应该是要被访问的,但是 A 已经被访问了,所以我们访问顶点 E,将 E 压入栈,标记它为访问过,并输出到结果中。

6c4479d7b7e3b4cd650dc190d23d3d0f.png

第四步,从 E 开始,E 与 B、G 相连,但是B刚刚被访问过了,所以下一个被访问的将是G,把G压入 栈,标记它为访问过,并输出到结果中。

0833f49d2240d0df25a52324f671bc6a.png

第五步,现在我们在顶点 G 的位置,由于与 G 相连的顶点都被访问过了,类似于我们走到了一个死胡同,必须尝试其他的路口了。所以我们这里要做的就是简单地将 G 从栈里弹出,表示我们从 G 这里已经无法继续走下去了,看看能不能从前一个路口找到出路。

fe1000c5cef0fbde3ec27f566f3a0122.png

如果发现周围的顶点都被访问了,就把当前的顶点弹出。

第六步,现在栈的顶部记录的是顶点 E,我们来看看与 E 相连的顶点中有没有还没被访问到的,发现它们都被访问了,所以把 E 也弹出去。

e0bc1bb3fec97a41eebae37d2ce6fe8a.png

第七步,当前栈的顶点是 B,看看它周围有没有还没被访问的顶点,有,是顶点 F,于是把 F 压入栈, 标记它为访问过,并输出到结果中。

9be3850c8eb8228a7100620bcbe62b72.png

第八步,当前顶点是 F,与 F 相连并且还未被访问到的点是 C 和 D,按照字母顺序来,下一个被访问的点是 C,将 C 压入栈,标记为访问过,输出到结果中。

ae062f097c9c716debd0e52050c9cc54.png

第九步,当前顶点为 C,与 C 相连并尚未被访问到的顶点是 H,将 H 压入栈,标记为访问过,输出到结果中。

99cddd76823999a2c260c5ec83baeb7b.png

第十步,当前顶点是 H,由于和它相连的点都被访问过了,将它弹出栈。

290d9099bc345240a7de4284884970c8.png

第十一步,当前顶点是 C,与 C 相连的点都被访问过了,将 C 弹出栈。

74062c8b282e850bf2411b888f241c7b.png

第十二步,当前顶点是 F,与 F 相连的并且尚未访问的点是 D,将 D 压入栈,输出到结果中,并标记为访问过。

aceda0ef9cf6559cc7443ccb1089a1b4.png

第十三步,当前顶点是 D,与它相连的点都被访问过了,将它弹出栈。以此类推,顶点 F,B,A 的邻居都被访问过了,将它们依次弹出栈就好了。最后,当栈里已经没有顶点需要处理了,我们的整个遍历结束。

9783b0be9c5d4efd06e3b0a9f77555a5.png

广度优先搜索(BFS,Breadth First Search)

直观地讲,它其实就是一种“地毯式”层层推进的搜索策略,即先查找离起始顶点最近的,然后是次近的,依次往外搜索。假设我们有这么一个图,里面有A、B、C、D、E、F、G、H 8 个顶点,点和点之间的联系如下图所示, 对这个图进行深度优先的遍历。

1afbe67dd0c0455b040b6805099f8511.png

依赖队列(Queue),先进先出(FIFO)。

一层一层地把与某个点相连的点放入队列中,处理节点的时候正好按照它们进入队列的顺序进行。

第一步,选择一个起始顶点,让我们从顶点 A 开始。把 A 压入队列,标记它为访问过。

122b618e07f2491cf009c34fd4e9fa3f.png

第二步,从队列的头取出顶点 A,打印输出到结果中,同时将与它相连的尚未被访问过的点按照字母大小顺序压入队列,同时把它们都标记为访问过,防止它们被重复地添加到队列中。

66048a6179fcddf1758e3cf92fcd00a0.png

第三步,从队列的头取出顶点 B,打印输出它,同时将与它相连的尚未被访问过的点(也就是 E 和 F) 压入队列,同时把它们都标记为访问过。

77b86c2ff19bfb63ffd157c22833556a.png

第四步,继续从队列的头取出顶点D,打印输出它,此时我们发现,与 D 相连的顶点 A 和 F 都被标记访问过了,所以就不要把它们压入队列里。

030095f6aeb7fa00bb161816a38fd6b2.png

第五步,接下来,队列的头是顶点 G,打印输出它,同样的,G 周围的点都被标记访问过了。我们不做任何处理。

4b7a12e67f498d7ff3c253a6346eacb8.png

第六步,队列的头是 E,打印输出它,它周围的点也都被标记为访问过了,我们不做任何处理。

cbdd9fc6ebef4af2d190e2a156e8606b.png

第七步,接下来轮到顶点 F,打印输出它,将 C 压入队列,并标记 C 为访问过。

b34153008728637a6c427845a2661e2b.png

第八步,将 C 从队列中移出,打印输出它,与它相连的 H 还没被访问到,将 H 压入队列,将它标记为访问过。

f45a5ecb9a39c7e221b651e472923c5a.png

第九步,队列里只剩下 H 了,将它移出,打印输出它,发现它的邻居都被访问过了,不做任何事情。

585b913af3aef5e1fee27045ca82c890.png

社交网络可以用图来表示。这个问题就非常适合用图的广度优先搜索算法来解决,因为广度优先搜索是 层层往外推进的。首先,遍历与起始顶点最近的一层顶点,也就是用户的一度好友,然后再遍历与用户 距离的边数为 2 的顶点,也就是二度好友关系,以及与用户距离的边数为 3 的顶点,也就是三度好友关系。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

每天进步一点点【图的深度优先搜索与广度优先搜索】 的相关文章

  • 在java中将StreamWriter转换为OutputStream?

    我正在尝试使用 System setOut 将 System out 重定向到字符串 它需要一个 PrintStream 有什么方法可以将 StringWriter 转换为 Stream 以便我可以将其传递给 setOut 吗 你不能完全这
  • JAVA - 带有特殊字符的 LDAP 密码不起作用

    我试图在我的系统上创建一个登录屏幕 在 Active Directory 中进行查询 但是当用户的密码包含一些特殊字符 如 和 时 它不会验证 我需要加密密码才能工作吗 我该怎么做 我使用 getPassword 通过 JPasswordF
  • JPanel透明背景和显示元素[重复]

    这个问题在这里已经有答案了 我插入一个背景图e 变成 aJPanel但一些界面元素消失了 以下 Java Swing 元素不会出现 标签标题 标签 usuario 标签 密码 按钮加速器 你能否使图像透明或元素不透明 setOpaque f
  • 以编程方式将 PEM 证书导入 Java KeyStore

    我有一个由两个文件 crt 和 key 组成的客户端证书 我希望将其导入到 java KeyStore 中 然后在 SSLContext 中使用 以通过 Apache 的 HTTPClient 发送 HTTP 请求 但是 我似乎找不到一种以
  • Maven + Cobertura:无法找到[您的班级]。你指定了源目录吗?

    我有 MyMath 类 有两个简单的方法 multi 和 add 和测试类只会测试多种方法 public class MainTest Test public void testMultiply MyMath tester new MyMa
  • 如何在Spring的applicationContext.xml中指定默认范围来请求范围?

    我想让所有 bean 请求默认作用域 但是 Spring 文档说默认作用域是 Singleton 第 3 4 1 和 3 4 2 节http static springsource org spring docs 2 5 x referen
  • @PreUpdate 不适用于 Spring Data JPA

    我有一个实体 Entity EntityListeners MyEntityListener class class MyEntity 还有听者 class MyEntityListener PrePersist PreUpdate pub
  • 独占锁定ConcurrentHashMap

    我知道不可能锁定 ConcurrentHashMap 进行独占访问 但是 我找不到原因 是因为构成CHM的 Segment 没有被api公开吗 据推测 如果是的话 客户端代码可以执行 交接 锁定 Cheers 我知道不可能锁定 Concur
  • JSP 标签+ scriptlet。如何启用脚本?

    我有一个使用标签模板的页面 我的 web xml 非常基本 我只是想在页面中运行一些代码 不 我对标签或其他替代品不感兴趣 我想使用不好的做法 scriptlet 哈哈 到目前为止 我收到了 HTTP ERROR 500 错误 Script
  • 如何自定义JProgressBar?

    我正在制作一个启动器 我想要一个自定义的进度栏 我已经做了一些研究 并且可以使用 JavaFX 从未用它做过任何事情 并且可以通过替换 UI 来实现 我正在寻找一个具有圆形边缘和圆形填充的酒吧 像这样的事情 package gui impo
  • 如何使用 Java 原生接口从 Java 调用 Go 函数?

    可以通过以下方式调用 C 方法JNA https en wikipedia org wiki Java Native AccessJava 中的接口 如何使用 Go 实现相同的功能 package main import fmt impor
  • Java G1 GC 处理引用对象运行缓慢

    我已经在 J ava 上运行了计数器 它24小时工作 每秒点击通过100次左右 白天 GC 处理时间从 20 60 毫秒缓慢上升到 10000 60000 毫秒 然后下降到 20 60 毫秒 这种模式不时地重复 从 GC 日志中我发现 GC
  • .class 与 .java

    class 文件和 java 文件有什么区别 我正在尝试让我的小程序工作 但目前我只能在 Eclipse 中运行它 还不能嵌入 HTML 谢谢 编辑 那么如何使用 JVM 进行编译呢 class 文件是编译后的 java 文件 java 都
  • Java中的DRY原则[关闭]

    Closed 这个问题需要细节或清晰度 help closed questions 目前不接受答案 我一直在读关于DRY https en wikipedia org wiki Don 27t repeat yourself原则 虽然看起来
  • 使用外部硬盘写入和存储 mysql 数据库

    我已经设置了 mysql 数据库在我的 Mac 上使用 java 和 eclipse 运行 它运行得很好 但现在我将生成大约 43 亿行数据 这将占用大约 64GB 的数据 我存储了大量的密钥和加密值 我有一个 1TB 外部我想用作存储位置
  • 哪种 Java DOM 包装器是最好或最受欢迎的? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 存储过程将多个表返回到 spring jdbc 模板

    我正在使用 JdbcTemplate 从 Spring DAO 类调用存储过程 我的问题是 存储过程返回多个表 有没有办法使用 Spring JdbcTemplate 访问多个表 如果我使用jdbcTemplate queryForList
  • 如何计算文件中单词的长度?爪哇

    我正在尝试编写一个代码来计算文件中特定长度的单词数 例如 How are you 会打印 Proportion of 3 letter words 100 3 words 我想计算长度为 1 2 3 4 5 6 7 8 9 10 11 12
  • Android同步onSensorChanged?

    这是我的问题的后续 Android线程可运行性能 https stackoverflow com questions 36395440 android thread runnable performance 我在理解应用程序的同步方法时遇到
  • 将隐藏(生物识别)数据附加到 pdf 上的数字签名

    我想知道是否可以使用 iText 我用于签名 或 Java 中的其他工具在 pdf 上添加生物识别数据 我会更好地解释一下 在手写板上签名时 我会收集签名信息 例如笔压 签名速度等 我想将这些信息 java中的变量 与pdf上的签名一起存储

随机推荐

  • #systemverilog# 之 event region 和 timeslot 仿真调度(九)assign 赋值 和 always 组合赋值的调度区别

    有时候 我们会发现一个问题 举个最简单的例子 比如将两个信号进行简单的异或运算 该逻辑运算 我们可以使用 assign 数据流建模完成 也可以使用always 组合逻辑过程赋值语句实现 那仿真工具在对它进行调度的时候 有什么区别吗 不慌 今
  • Ubuntu安装可视化界面ElasticSearch-head插件

    1 下载地址 GitHub mobz elasticsearch head A web front end for an elastic search cluster 上传并解压 root zq virtual machine home e
  • 一次url请求的过程

    1 HTTP协议 超文本传输协议 Hyper Text Transfer Protocol HTTP 一个简单的请求 响应协议 指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应 2 域名解析 DNS Domain Name Sy
  • 如何开启bios虚拟化

    要开启 BIOS 虚拟化 首先需要进入 BIOS 设置 通常可以在电脑启动时按下 F2 或 Del 键进入 具体操作可能因电脑品牌和型号而异 在 BIOS 设置中 需要找到 虚拟化支持 或 硬件虚拟化 选项 并将其设置为 开启 有些电脑可能
  • 货币的教训——汇率与货币系列评论

    这本书中介绍了中国的人民币的具体的流转形式 就是不知到底准确否 2013 9 29
  • numpy广播机制

    NumPy的广播机制 文章目录 NumPy的广播机制 Broadcast 最简单的广播机制 稍微复杂的广播机制 广播机制到底做了什么 一个典型的错误案例 一个正确的经典示例 一种更便捷的计算方式 Broadcast 广播是numpy对不同形
  • 【计算机视觉

    文章目录 一 分割 语义相关 12篇 1 1 UniSeg A Unified Multi Modal LiDAR Segmentation Network and the OpenPCSeg Codebase 1 2 Learning S
  • IDEA导入eclipse项目

    第一步 File gt new gt Project from Existing Sources 导入已存在的项目 并选择要导入的文件或目录 第二步 选择eclipse选项 第三步 配置jdk 导入完成了 项目目录如图示 gt 开始配置mo
  • PCB 过孔简介

    做过 PCB 设计的最先了解的应该就是过孔了 因为有过孔的存在我们才能做出多层板 过孔应该是 PCB 中最简单的部分了 也是最容易被我们忽略的地方 常见的过孔分为两大类 1 用作各层之间的电气连接 2 用作器件的固定或定位 一 过孔的介绍
  • 如何判断三点共线

    如何判断三点共线 在二维坐标系中 给出三点A x1 y1 B x2 y2 C x3 y3 的坐标 判断三点共线的条件是 实质是判断有三个点组成的三角形面积为0 神爱世人 甚至将他的独生子 耶稣 赐给他们 叫一切信他的 不至灭亡 反得永生 圣
  • 智能制造面临的主要问题

    随着工业4 0的发展 工业互联网 智能制造 智能工厂等概念正在兴起 但从本质上讲 制造业的目标是利用大数据 人工智能 互联网等先进技术改造制造业 使制造业成为定制体验 创新交付的竞争核心 在逐步实施工业4 0和中国制造2025的背景下 国内
  • R语言-引用函数对象作为参数

    问题描述 如何在在R的函数中通过字符串调用别的函数 以下面为例子 testFun lt function Fun x lt 1 100 Fun x 解法 这个问题没什么其实很笨 就是想记录一下 1 直接调用 testFun lt funct
  • 文本后缀“SCRIPT_EXP”无效;未找到文文本运算符或文本运算符模板“operator ““““SCRIPT_EXP”

    今天下载了一份源码 然后在编译的时候出现了这个问题 我查阅了相关资料 解决方法有两个 下面列举一下 1 字符文件编码 Visual Studio编译器 首先选中代码当前页 然后文件 gt 打开 高级保存选项 选中GB2312 确定 2 空格
  • HBase篇(1)-特性与应用场景

    结束了Zookeeper篇 接下来我们来说下Google三驾马车之一BigTable的开源实现 HBase 要讲的内容暂定如下 这是第一篇我们先不聊技术实现 只讨论特性和场景 hbase的特点 千万级高并发 PB级存储 非结构化存储 动态列
  • 树莓派升级ubuntu mate 16.04 到 18.04

    gt gt gt sudo do release upgrade Checking for a new Ubuntu release Get 1 Upgrade tool signature 819 B Get 2 Upgrade tool
  • 理解 es6 class 中 constructor 方法 和 super 的作用

    首先 ES6 的 class 属于一种 语法糖 所以只是写法更加优雅 更加像面对对象的编程 其思想和 ES5 是一致的 function Point x y this x x this y y Point prototype toStrin
  • html鼠标经过状态,30种炫酷html5鼠标滑过图片标题显示效果

    这款插件集合和30种html5不同效果的鼠标滑过图片时标题动画效果 这个插件使用css 3D transforms和伪元素来制作动画效果 请确保你的浏览器支持这些css3特性 另外 在文本上使用css transitions时 火狐浏览器存
  • Appium 实现一个 apk 的二级页面的点击操作

    前言 在本文中 我们将介绍如何使用 Appium 和 Python 来实现一个 apk 的二级页面的点击操作 用例目标 实现一个 apk 的二级页面的点击操作 初始思路 进入到该界面的直接点击该 button 即可 遇到问题 1 启动不起来
  • Jenkins安装与入门(+Git+Docker)自动化交互

    https jenkins io zh yum install git y yum install jdk 8u171 linux x64 rpm y rpm qa grep java 如果过滤出open jdk 删掉防止冲突 yum in
  • 每天进步一点点【图的深度优先搜索与广度优先搜索】

    图是一种数据结构 其中节点可以具有零个或多个相邻元素 两个节点之间的连接称为边 节点也可以称为顶点 图分为三种 无向图 有向图 带权图 图的表示方式有两种 二维数组表示 邻接矩阵 链表表示 邻接表 邻接矩阵 邻接矩阵是表示图形中顶点之间相邻