0103深度优先搜索和单点连通-无向图-数据结构和算法(Java)

news2024/12/23 18:25:30

文章目录

    • 1.1 走迷宫
    • 1.2 图的深度优先搜索实现
    • 1.3 算法分析及性能
    • 1. 4 单点连通性
    • 后记

1.1 走迷宫

简单的迷宫,如下图1.1-1所示:

在这里插入图片描述

探索迷宫而不迷路,我们需要:

  • 选择一条没有标记过的通道,在你走过的路上铺一条绳子;
  • 标记所有你第一次路过的路口和通道;
  • 当来到一个标记过的路口时(用绳子)回退到上一个路口;
  • 当回退的路口已没有可走的通道时继续回退。

绳子可以保证总能找到一条出路,标记能保证你不会两次经过同一条通道或者路口。我们把上迷宫,用等价的图来代替,如下图1.1-2所示:在这里插入图片描述

1.2 图的深度优先搜索实现

图的遍历算法非递归代码如下:

import java.util.Map;

/**
 * key-value pair 类
 * @author: Administrator
 * @createTime: 2023/03/04 21:49
 */
public final class Entry<K, V> implements Map.Entry<K, V>{

    private K key;
    private V value;

    public Entry(K key, V value) {
        this.key = key;
        this.value = value;
    }

    @Override
    public K getKey() {
        return key;
    }

    @Override
    public V getValue() {
        return value;
    }

    @Override
    public V setValue(V value) {
        V oldValue = value;
        this.value = value;
        return oldValue;
    }
}

/**===========*/

import com.gaogzhen.datastructure.stack.Stack;
import edu.princeton.cs.algs4.Graph;

import java.util.Iterator;

/**
 * 单点连通性
 * @author: Administrator
 * @createTime: 2023/03/03 19:58
 */
public class DepthFirstSearch {
    /**
     * 顶点是否标记
     */
    private boolean[] marked;

    /**
     * 与指定顶点连通的顶点总数
     */
    private int count;

    /**
     * 图
    */
    private Graph graph;

    /**
     * 起点
     */
    private int s;

    public DepthFirstSearch(Graph graph, int s) {
        this.graph = graph;
        this.s = s;
        check(s);
        marked = new boolean[graph.V()];
        dfs();
    }

    /**
     * 搜索图g中与起点v连通的所有顶点
     */
    private void dfs() {
        Stack<Entry<Integer, Iterator<Integer>>> path = new Stack<>();
        // marked[v] = true;
        if (!marked[s]) {
            marked[s] = true;
            count++;
            Iterable<Integer> iterable = graph.adj(s);
            Iterator<Integer> it;
            if (iterable != null && (it = iterable.iterator()) != null){
                path.push(new Entry<>(s, it));
            }
        }
        while (!path.isEmpty()) {
            Entry<Integer, Iterator<Integer>> entry = path.pop();
            int x;
            Iterator<Integer> it = entry.getValue();
            Integer f = entry.getKey();
            while (it.hasNext()) {
                x = it.next();
                if (!marked[x]) {
                    marked[x] = true;
                    count++;
                    if (it.hasNext()) {
                        path.push(entry);
                    }
                    Iterable<Integer> iterable = graph.adj(x);
                    if (iterable != null && (it = iterable.iterator()) != null){
                        path.push(new Entry<>(x, it));
                    }
                    break;
                }
            }

        }
    }

    /**
     * 检测索引是否在范围之内
     * @param i 给定索引
     */
    private void check(int i) {
        if (i < 0 || i > graph.V() - 1) {
            throw new IndexOutOfBoundsException("索引越界异常");
        }
    }

    /**
     * 判断起点是否与给定顶点x连通
     * @param x 给定顶点
     * @return
     */
    public boolean marked(int x) {
        check(x);
        return marked[x];
    }

    /**
     * 返回图中与顶点想连通的顶点数
     * @return
     */
    public int count() {
        return count;
    }

}

测试代码:

public static void testDepth() {
    String path = "H:\\gaogzhen\\java\\projects\\algorithm\\asserts\\maze.txt";
    In in = new In(path);
    Graph graph = new Graph(in);
    int s = 0;
    DepthFirstSearch depthFirstSearch = new DepthFirstSearch(graph, s);

    int t = 5;
    System.out.println(depthFirstSearch.marked(t));
    System.out.println(depthFirstSearch.count());
}
// 测试结果
true
6

1.3 算法分析及性能

知识点

  • Entry类就是一个键值对类,存在一对键值;在DepthFirstSearch中key用于存储顶点序号,value存储该顶点对应邻接顶点迭代器。
  • 深度优先搜索非递归实现,主要借助栈来代替递归调用栈帧结构,已节省内存占用和提高运行效率。

算法分析:dfs()方法为该算法实现的主要方法,方法源代码以给出,这里不再赘述整体流程,着重分析下以下一个关键问题。

  • 该非递归dfs方法如何保证深度优先?
    • 首先我把键值对Entry起点和起点对应的邻接(连通)顶点集合迭代器压入栈中
    • 外层循环开始
      • 弹出栈顶元素,获取Entry的顶点及对应的邻接顶点集合迭代器
      • 内层循环判断该迭代器有下一个元素即还有邻接顶点,取出一个邻接顶点。
        • 判断改邻接顶点没有被标记过
          • 标记数组对应顶点索引标记
          • 连通顶点计数+1
          • 判断迭代器还有元素,重新压入栈中
          • break跳出内层循环
    • 总结:只要邻接顶点(更深一层的顶点)没被标记过,标记之后同层迭代器压入栈中,去访问更深一层的顶点;而不是继续访问同层的顶点。
  • 该dfs方法如果保证同层(同一个顶点的邻接顶点)访问全部访问完毕且只访问一次?
    • 每个顶点只访问一次是标记数组marked[]索引和顶点一一对应,默认都是false未标记;标记之后不会在压入栈中,自然不会在标记一次
    • 访问同层元素是通过迭代器完成的,while配合迭代hasNext,next()方法保证全部访问一边且不会重复访问。
  • 深度优先算法性能如何?见下面的命题及证明。
    • 这里根据上面的算法简单分析
    • 完成循环判断栈不为空,那么只有未被标记的顶点及其邻接表(迭代器)会放入栈中;也就是说所有的顶点及其邻接表都会被放入栈中且不会重复
    • 内层判断迭代器有元素那么所有的邻接表会被遍历一边,邻接表代表对应顶点的度数
    • 结论深度优先搜索标记与起点连通的所有顶点所需的时间和顶点的度数之和成正比

命题A。深度优先搜索标记与起点连通的所有顶点所需的时间和顶点的度数之和成正比。

证明:首先,我们要证明这个算法能标记所有与起点s连通的所有顶点(且不会标记其他顶点)。算法仅通过边来寻找顶点,所以每个被标记的顶点都与s连通;反证法证明标记了所有与s连通的顶点,假设某个没有被标记的顶点w与s连通。因为s作为起点是被标记的,由s到w的任意一条路径中至少有一条边连接的两个顶点分别被标记过河没有被标记过,例如v-x。根据算法,在标记了v后比如发现x,因此这样的边不存在。

每个顶点都只会被标记一次保证了时间上限(检查标记的耗时和度数成正比)。

详细搜索轨迹,可参考算法第四版341页。

1. 4 单点连通性

连通性。给定一幅图,回答“两个给定的顶点是否连通”?或者图中有多少个连通子图等类似问题。

问题“两个给定的顶点是否连通?”等价于“两个给定的顶点间是否存在一条路径?”,也可以叫做路经检测问题。深度优先搜索解决了这个问题。

递归方法参考《算法第四版?或者书提供的jar包。

后记

如果小伙伴什么问题或者指教,欢迎交流。

❓QQ:806797785

⭐️源代码仓库地址:https://gitee.com/gaogzhen/algorithm

参考链接:

[1][美]Robert Sedgewich,[美]Kevin Wayne著;谢路云译.算法:第4版[M].北京:人民邮电出版社,2012.10.P338-P342.

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

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

相关文章

基于linux 实现DNS Client请求

DNS是什么&#xff1a; DNS是域名系统,Domain Name System的缩写,是一个服务。 作用&#xff1a; DNS就是把域名解析为IP地址&#xff0c;提供我们上网&#xff0c;我们能够上网最终是找到IP地址。 DNS请求报文格式&#xff1a; 分别包含 Transaction ID&#xff1a;会话标…

Linux环境开发stm32+vscode编码+gcc-arm编译+openocd烧录

文章目录Linux环境下STM32开发1前言2环境搭建3点灯&#xff1a;脚本方式命令行操作方式具体见参考2vscode将以上命令集成起来4总结Linux环境下STM32开发 1前言 最近在使用Keil开发STM32的时候总感觉代码写起来很很费劲&#xff0c;然后打算用vscode试试&#xff0c;刚开始用的…

js作用域和作用域链

1、局部作用域分为函数作用域和块作用域 1.1、函数作用域: 在函数内部声明的变量只能在函数内部被访问&#xff0c;外部无法直接访问。 总结 1.函数内部声明的变量&#xff0c;在函数外部无法被访问 2.函数的参数也是函数内部的局部变量 3.不同函数内部声明的变量无法互相访…

iscsi windows使用教程与smb das 区别

介绍——为什么不用smb而用iscsi 历史 说到SAN等传统存储设备&#xff0c;我们不得不提到SCSI&#xff0c;SCSI作为外部块设备的连接和传输协议&#xff0c;是最广泛的块设备协议&#xff0c;于1979首次提出&#xff0c;是为小型机研制的一种接口技术&#xff0c;现在已完全普…

taobao.item.joint.img( 商品关联子图 )

&#xffe5;开放平台免费API必须用户授权 关联一张商品图片到num_iid指定的商品中传入的num_iid所对应的商品必须属于当前会话的用户商品图片关联在卖家身份和图片来源上的限制&#xff0c;卖家要是B卖家或订购了多图服务才能关联图片&#xff0c;并且图片要来自于卖家自己的…

汇编指令学习(JMP、JE、JS、JP,JO,JB)

一、JMP无条件跳转不用看标志位&#xff0c;jmp后面跟一个内存地址&#xff0c;直接跳转到该地址jmp 0x0046B994二、JE&#xff08;JZ&#xff09;条件跳转当ZF标致为1的时候发生跳转&#xff0c;为0的时候不跳转&#xff0c;可以双击标志位&#xff0c;进行判断je 0x0046B99F三…

802.11 mac帧

mac帧格式帧格式MAC headFrame Control域Protocol VersionType和SubtypeTo DS和From DSMore FragmentsRetryPower ManagementMore DataProtected FrameOrderDuration/ID域Address域Address1 接收Address2 发送Address3 携带其他信息帮助mac帧传输Sequence Control域管理帧格式定…

vue3:生命周期(onErrorCaptured)

一、背景 当项目如果发生报错&#xff0c;影响程序体验。如果能以捕获的方式得到错误信息&#xff0c;而且还能定位问题&#xff0c;这样就好了&#xff0c;本文介绍onErrorCaptured实现我们想要的效果。 vue2&#xff1a;errorCaptured。使用与vue3同理。 vue3&#xff1a;…

某游戏app sig参数分析

今天要分析的app 叫 dGFwdGFwIDIuMjA= (base64 解码),来一起学习下。 找个视频接口,上来先抓个包,没错今天就是要分析下这个sig参数。 这个app 在高版本上有加固壳,并且还有frida检测(ps:遇到困难不会放弃,以后慢慢研究),这里只是研究sig参数,所以采用低版本了。 把…

ARM uboot 的移植2-从三星官方 uboot 开始移植

一、inand 驱动问题的解决 1、先从现象出发定位问题 (1) 解决问题的第一步&#xff0c;是定位问题。所谓定位问题&#xff0c;就是找到源代码当中导致这个问题的那一句或者那几句代码。有时候解决这个问题需要修改的代码和直接导致这个问题的代码是不同的。我们这里说的定位问…

一文深入分析虚拟机中对象锁实现!

一、前言 编程过程中经常会遇到线程的同步问题&#xff0c;Java 中对同步问题的解决方案比较多&#xff08;synchronized、JUC、原子操作、volatile、条件变量等&#xff09;&#xff0c;其中synchronized 最方便、简单易用&#xff0c;也是java 编程中使用最多的临界区保护方…

接口自动化入门-TestNg

目录1.TestNg介绍2、TestNG安装3、TestNG使用3.1 编写测试用例脚本3.2 创建TestNG.xml文件&#xff08;1&#xff09;创建testng.xml文件&#xff08;2&#xff09;修改testng.xml4、测试报告生成1.TestNg介绍 TestNg是Java中开源的自动化测试框架&#xff0c;灵感来源于Junit…

CSAPP第九章 虚拟内存

理解虚拟内存的原因 本章前部分描述虚拟内存是如何工作的&#xff0c;后一部分描述应用程序如何使用和管理虚拟内存 物理和虚拟寻址 虚拟内存作为缓存的工具 页表 页命中 缺页 虚拟内存作为内存管理的工具 简化链接&#xff0c;简化加载&#xff0c;简化共享&#xff0c;简化…

K8s集群部署

#部署方式有多种&#xff0c;本文采用kubeadm组件的方式来部署K8s集群 安装要求&#xff1a; 至少三台主机内存最少2G&#xff0c;CPU2核集群网络互通可以访问外网禁止swap分区 环境说明: 系统&#xff1a;ubuntu22.04.1 版本信息&#xff1a;kubernetes&#xff1a;1.26.…

HashMap底层的实现原理

目录一、知识点回顾二、HashMap 的 put() 和 get() 的实现2.1 map.put(k, v) 实现原理2.2 map.get(k) 实现原理2.3 为何随机增删、查询效率都很高&#xff1f;2.4 为什么放在 HashMap 集合 key 部分的元素需要重写 equals 方法?2.5 HashMap总结2.6 JDK8 之后&#xff0c;HashM…

由点到面贯穿整个Java泛型理解

泛型概述 Java泛型(generics)是DK5中引入的一个新特性&#xff0c;泛型提供了编译时类型安全监测机制&#xff0c;该机制允许我们在编译时检测到非法的类型数据结构。 泛型的本质就是参数化类型&#xff0c;也就是所操作的数据类型被指定为一个参数。 如我们经常使用的Array…

信息安全与数学基础-笔记-③一次同余方程

知识目录一次同余方程的解中国剩余定理中国剩余定理的应用一次同余方程的解 本文只研究一次同余方程的解。 f(x) 三 0 (mod m)&#xff0c; 若有一个s能够满足该式子&#xff0c;那么该数字就是该式子的解&#xff0c; 在同余方程式中的解一般写成&#xff1a;x三s (mod m) 同…

Git学习入门(2)- 基本命令操作总结

个人博客&#xff1a;我的个人博客&#xff0c;各位大佬来玩1 创建 git仓库1.1 从现有工作目录中初始化新仓库需要到你需要用git管理的项目中输入以下命令&#xff1a;git init便会创建一个空的git项目&#xff0c;并且当前目录下会出现一个名为 .git 的目录&#xff0c; Git 需…

1.SpringSecurity快速入门

*SpringScurity的核心功能: 认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户 授权:经过认证后判断当前用户是否有权限进行某个操作 *第一步:创建springboot工程 *第二步:引入SpringSecurity依赖 *第三步:写controller,访问对应的url:localhos…

常用训练tricks,提升你模型的鲁棒性

目录一、对抗训练FGM(Fast Gradient Method): ICLR2017代码实现二、权值平均1.指数移动平均&#xff08;Exponential Moving Average&#xff0c;EMA&#xff09;为什么EMA会有效&#xff1f;代码实现2. 随机权值平均&#xff08;Stochastic Weight Averaging&#xff0c;SWA&a…