0108检测环-无向图-数据结构和算法(Java)

news2024/11/25 3:29:58

文章目录

    • 1 API
    • 2 实现和分析
    • 3 测试
    • 后记

1 API

检测一幅图是否还有环,如果有找出环路(任意一条),API如下:

public classCycle
Cycle(Grpah G)预处理函数
booleanhasCycle()
Iterable<Interge>cycle()有环给出环路,没返回null
booleanhasSelfLoop(Graph)是否有自环
booleanhasParallelEdges(Graph)是否有平行边

2 实现和分析

非递归深度优先搜索实现,源代码如下;

package com.gaogzhen.datastructure.graph.undirected;

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

import java.util.Iterator;

/**
 * 检测环
 * @author: Administrator
 * @createTime: 2023/03/10 19:27
 */
public class Cycle {
    private boolean[] marked;
    private int[] edgeTo;
    private Stack<Integer> cycle;

    /**
     * 检测是否有环,如果有给出环路
     *
     * @param G the undirected graph
     */
    public Cycle(Graph G) {
        // 如果有平行边,返回
        if (hasParallelEdges(G)) {
            return;
        }

        // don't need special case to identify self-loop as a cycle
        // if (hasSelfLoop(G)) return;

        marked = new boolean[G.V()];
        edgeTo = new int[G.V()];
        dfs(G);
    }

    private void dfs(Graph G) {
        Stack<Node> stack = new Stack<>();
        for (int v = 0; v < G.V(); v++) {
            if (!marked[v]) {
                if (dfs(G, -1, v, stack) ) {
                    return;
                }
            }
        }
    }

    /**
     * 深度优先搜索环路
     *
     * @param G     无向图
     * @param u
     * @param v
     * @param stack
     */
    private boolean dfs(Graph G, int u, int v, Stack<Node> stack) {
        marked[v] = true;
        Iterable<Integer> adj = G.adj(v);
        if (adj != null) {
            stack.push(new Node(v, u, adj.iterator()));
        }

        while (!stack.isEmpty()) {
            Node c = stack.pop();

            while (c.adj.hasNext()) {
                Integer w = c.adj.next();
                if (!marked[w]) {
                    marked[w] = true;
                    edgeTo[w] = c.v;
                    if (c.adj.hasNext()) {
                        stack.push(c);
                    }
                    Iterable<Integer> adjW = G.adj(w);
                    if (adjW != null) {
                        stack.push(new Node(w, c.v, adjW.iterator()));
                    }
                    break;
                }

                // check for cycle (but disregard reverse of edge leading to v)
                else if (w != c.parent) {
                    cycle = new Stack<>();
                    for (int x = c.v; x != w; x = edgeTo[x]) {
                        cycle.push(x);
                    }
                    cycle.push(w);
                    cycle.push(c.v);
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 检测无向图G是否有子环
     * @param G
     * @return
     */
    private boolean hasSelfLoop(Graph G) {
        for (int v = 0; v < G.V(); v++) {
            for (int w : G.adj(v)) {
                if (v == w) {
                    cycle = new Stack<Integer>();
                    cycle.push(v);
                    cycle.push(v);
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 检测无向图G是否有平行边
     * @param G
     * @return
     */
    private boolean hasParallelEdges(Graph G) {
        marked = new boolean[G.V()];

        for (int v = 0; v < G.V(); v++) {

            // check for parallel edges incident to v
            for (int w : G.adj(v)) {
                if (marked[w]) {
                    cycle = new Stack<Integer>();
                    cycle.push(v);
                    cycle.push(w);
                    cycle.push(v);
                    return true;
                }
                marked[w] = true;
            }

            // reset so marked[v] = false for all v
            for (int w : G.adj(v)) {
                marked[w] = false;
            }
        }
        return false;
    }

    /**
     * 是否有环
     *
     * @return {@code true} if the graph has a cycle; {@code false} otherwise
     */
    public boolean hasCycle() {
        return cycle != null;
    }

    /**
     * 如果有环,返回环路
     * @return a cycle if the graph {@code G} has a cycle,
     *         and {@code null} otherwise
     */
    public Iterable<Integer> cycle() {
        return cycle;
    }



    static class Node {

        /**
         * 顶点索引
         */
        private int v;

        /**
         * 顶点父结点索引
         */
        private int parent;

        /**
         * 顶点邻接表
         */
        private Iterator<Integer> adj;

        public Node(int v, int parent, Iterator<Integer> adj) {
            this.v = v;
            this.parent = parent;
            this.adj = adj;
        }
    }
}

检测环原理:

  • 给定一个起点,开始深度优先搜索。
  • 标记当前顶点v,遍历当前顶点的邻接表。获取当前邻接表顶点w,判断如果w没被标记,开始下一层搜索;
  • 如果顶点w被标记过,在判断它是否等于它的爷爷顶点。如果w等于它的爷爷顶点说明是刚刚搜索过的v-w这条边;如果不是它的爷爷顶点,说明有环。
    • 无向图用连接表实现,v-w和w-v是同一条边
    • 没有被探索过的边v-w,但是w已经被标记过,说明在之前的探索中经过这个顶点。再次经过说明有环。

环路:edgeTo[]数组索引对应顶点,记录每个顶点到起点的路径,是一棵由父链接表示的树。如果顶点w处检测到环,那么从w的父顶点v开始记录,通过edgeTo[v]一直找到成环的顶点w,最后放入v,表示一个环。

自环检测:遍历顶点v和它的邻接表,如果邻接表中顶点定于v说明有自环。

平行边检测:一个标记数组marked[],索引对应顶点。遍历顶点v和它的邻接表,它的邻接表当前顶点w如果被标记过,说明第二次遍历,存在平行边;没有被标记过,mark[w]=true,标记。遍历完成一对顶点和邻接表之后,初始化标记数组。

3 测试

测试用无向图,从txt中读取生成图:第一行表示顶点数,第二行表示边数,第三行至末尾表示具体的边。

6
8
0 5
2 4
2 3
1 2
0 1
3 4
3 5
0 2

无向图如下所示:在这里插入图片描述

测试代码:

public static void testCycle() {
    String path = "H:\\gaogzhen\\java\\projects\\algorithm\\asserts\\maze.txt";
    In in = new In(path);
    Graph graph = new Graph(in);
    Cycle cycle = new Cycle(graph);
    System.out.println("是否有环:" + cycle.hasCycle());
    System.out.println("环路:" + cycle.cycle());
}

搜索edgeTo[]图示:在这里插入图片描述

后记

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

❓QQ:806797785

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

参考链接:

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

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

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

相关文章

用友开发者中心应用构建实践指引!

基于 iuap 技术底座&#xff0c;用友开发者中心致力于为企业和开发者提供一站式技术服务&#xff0c;让人人都能轻松构建企业级应用。 本文以人力资源领域常用的应聘人员信息登记与分析功能为例&#xff0c;详细介绍如何在用友开发者中心使用 YonBuilder 进行应用构建。 功能…

计算机操作系统--哈工大(2)

操作系统的那棵树 本来看着网课是20个小时&#xff0c;还自以为是想着几周学完&#xff0c;是我太自大了&#xff0c;被现实狠狠殴打CPU调度策略如何让进程满意总原则&#xff1a;系统专注于任务执行又能合理调配任务前台任务关注响应时间&#xff0c;后台任务关注周转时间各种…

【C++】C++11新特性——右值引用

文章目录一、左值引用、 右值引用1.1 左值与右值1.2 左值引用1.3 右值引用二、右值引用的意义三、移动语句3.1 移动构造3.2 移动赋值3.3 总结四、move问题五、完美转发5.1 万能引用与折叠5.2 完美转发std::forward一、左值引用、 右值引用 1.1 左值与右值 我们经常能听到左值…

服务搭建篇(九) 使用GitLab+Jenkins搭建CI\CD执行环境 (上) 基础环境搭建

1.前言 每当我们程序员开发在本地完成开发之后 , 都要部署到正式环境去使用 , 在一些传统的运维体系中 , 开发与运维都是割裂的 , 开发人员不允许操作正式服务器 , 服务器只能通过运维团队来操作 , 这样可以极大的提高服务器的安全性 , 不经过安全保护的开放服务器 , 对于黑客…

6、DDIM

简介 去噪扩散概率模型(DDPM)在没有对抗性训练的情况下已经实现了高质量的图像生成&#xff0c;但它们需要模拟马尔可夫链许多步骤才能生成样本。 例如&#xff0c;从DDPM采样50k张大小为32 32的图像需要大约20个小时&#xff0c;而从Nvidia 2080 Ti GPU上的GAN采样则需要不…

Vue:(三十五)路由vue-router

今天&#xff0c;我们开始学习vue中一个很关键的知识点&#xff0c;路由。理解vue的一个插件库&#xff0c;专门用来实现SPA应用单页web应用整个应用只有一个完整的页面点击页面中的导航连接不会刷新页面&#xff0c;只会做页面的局部更新数据需要通过ajax请求获取下来&#xf…

css制作动画(动效的序列帧图)

相信 animation 大家都用过很多&#xff0c;知道是 CSS3做动画用的。而我自己就只会在 X/Y轴 上做位移旋转&#xff0c;使用 animation-timing-function 规定动画的速度曲线&#xff0c;常用到的 贝塞尔曲线。但是这些动画效果都是连续性的。 今天发现个新功能 animation-timi…

【C语言】详讲qsort库函数

qsort函数介绍具体作用qsort函数是一种用于对不同类型数据进行快速排序的函数&#xff0c;排序算法有很多最常用的冒泡排序法仅仅只能对整形进行排序,qsort不同,排序类型不受限制,qsort函数的底层原理是一种快速排序.基本构造qsort( void* arr, int sz, int sizeof, cmp_code);…

【毕业设计】基于Java的五子棋游戏的设计(源代码+论文)

简介 五子棋作为一个棋类竞技运动&#xff0c;在民间十分流行&#xff0c;为了熟悉五子棋规则及技巧&#xff0c;以及研究简单的人工智能&#xff0c;决定用Java开发五子棋游戏。主要完成了人机对战和玩家之间联网对战2个功能。网络连接部分为Socket编程应用&#xff0c;客户端…

IP协议+以太网协议

在计算机网络体系结构的五层协议中&#xff0c;第三层就是负责建立网络连接&#xff0c;同时为上层提供服务的一层&#xff0c;网络层协议主要负责两件事&#xff1a;即地址管理和路由选择&#xff0c;下面就网络层的重点协议做简单介绍~~ IP协议 网际协议IP是TCP/IP体系中两…

20230310英语学习

Some Narcissists Chase Status, Others Want to Win Admiration 自恋并非自尊心膨胀&#xff0c;那它因何而来&#xff1f; Narcissists often rub their friends and family the wrong way by bragging about their exploits, seemingly a symptom of an overinflated sense …

什么是AIGC?

目录前言一、什么是AIGC&#xff1f;1、什么是PGC&#xff1f;2、什么是UGC&#xff1f;3、什么是PUCG&#xff1f;4、什么是AIGC&#xff1f;二、总结前言 很明显&#xff0c;ChatGPT的爆火&#xff0c;带动了AIGC&#xff08;AI-Generated Content&#xff09;概念的火热。 …

DP算法:动态规划算法

步骤&#xff08;1&#xff09;确定初始状态&#xff08;2&#xff09;确定转移矩阵&#xff0c;得到每个阶段的状态&#xff0c;由上一阶段推到出来&#xff08;3&#xff09;确定边界条件。例题蓝桥杯——印章&#xff08;python实现&#xff09;使用dp记录状态&#xff0c;d…

为 Argo CD 应用程序指定多个来源

在 Argo CD 2.6 中引入多源功能之前,Argo CD 仅限于管理来自 单个 Git 或 Helm 存储库 的应用程序。用户必须将每个应用程序作为 Argo CD 中的单个实体进行管理,即使资源存储在多个存储库中也是如此。借助多源功能,现在可以创建一个 Argo CD 应用程序,指定存储在多个存储库…

ADS中导入SPICE模型

这里写目录标题在官网中下载SPICE模型ADS中导入SPICE模型在官网中下载SPICE模型 英飞凌官网 ADS中导入SPICE模型 点击option&#xff0c;设置导入选项 然后点击ok 如果destination选择当前的workspace&#xff0c;那么导入完成之后如下&#xff1a; &#xff08;推荐使用…

API 网关日志的价值,你了解多少?

本文介绍了 API 网关日志的价值&#xff0c;并以知名网关 Apache APISIX 为例&#xff0c;展示如何集成 API 网关日志。 作者钱勇&#xff0c;API7.ai 技术工程师&#xff0c;Apache APISIX Committer。 原文链接 网关日志的价值 在数字化时代&#xff0c;软件架构随着业务成…

单例模式之懒汉式

在上篇文章中&#xff0c;我们讲了单例模式中的饿汉式&#xff0c;今天接着来讲懒汉式。 1.懒汉式单例模式的实现 public class LazySingleton {private static LazySingleton instance null;// 让构造函数为private&#xff0c;这样该类就不会被实例化private LazySingleto…

unicode字符集与utf-8编码的区别,unicode转中文工具、中文转unicode工具(汉字)

在cw上报的报警信息中&#xff0c;有一个name字段的值是\u4eba\u4f53 不知道是啥&#xff0c;查了一下&#xff0c;是unicode编码&#xff0c;用下面工具转换成汉字就是“人体” 参考文章&#xff1a;https://tool.chinaz.com/tools/unicode.aspx 那么我很好奇&#xff0c;uni…

Web3中文|无聊猿Otherside元宇宙启动第二次旅行

3月9日消息&#xff0c;无聊猿Bored Ape Yacht Club母公司Yuga Labs公布了其Otherside元宇宙游戏平台第二次测试的最新细节。Yuga Labs公司称&#xff0c;“第二次旅行”将于3月25日举行&#xff0c;由四位Otherside团队长带领完成近两小时的游戏故事。本次旅行对Otherdeed NFT…

JavaScript String 字符串对象实例合集

文章目录JavaScript String 字符串对象实例合集返回字符串的长度为字符串添加样式返回字符串中指定文本首次出现的位置 - indexOf()方法查找字符串中特定的字符&#xff0c;若找到&#xff0c;则返回该字符 - match() 方法替换字符串中的字符 - replace()JavaScript String 字符…