图论与算法(4)图的深度优先遍历应用

news2024/12/26 9:18:45

1. 无向图的联通分量个数

1.1 联通分量个数

无向图的联通分量个数是指图中无法通过边连接到其他分量的顶点集合的个数。可以通过深度优先搜索或广度优先搜索来计算无向图的联通分量个数。

1.2 记录联通分量

(1)多个联通量的数:

7 6
0 1
0 2
1 3
1 4
2 3
2 6
5

(2)查询联通分量个数代码

public class CC {

    private Graph G;                // 顶点数
    private boolean [] visited;     // 边数
    private int cccount = 0;             // 联通分量个数

    public CC(Graph G){
        this.G = G;
        visited = new boolean[G.V()];
        for (int v = 0; v < G.V(); v++ ){
            if (!visited[v]){
                dfs(v);
                cccount++;
            }
        }
    }

    private void dfs(int v){
        visited[v] = true;
        for (int w : G.adj(v)) {
            if (!visited[w]) {
                dfs(w);
            }
        }
    }

    public int count(){
        return cccount;
    }
}

结果

    public static void main(String[] args) {
        Graph graph = new Graph("cc.txt");
        CC cc = new CC(graph);
        System.out.println(cc.count());
    }

执行结果:
2

Process finished with exit code 0

在CC类中,私有成员变量G表示图对象,visited表示顶点访问标记数组,cccount表示联通分量个数。在dfs方法中的递归过程中,会一直深入到无法再继续深入为止,然后回溯到上一层顶点,继续搜索其他未访问过的顶点。每次完成dfs(v)的递归调用后,联通分量个数cccount加1,表示找到了一个新的联通分量。

1.3 记录每个顶点所属联通分量

boolean[] visited改为int[] visited,并使用visited数组来记录每个顶点所属的联通分量ID,进行如下修改:

  1. 在构造函数CC中,将visited数组的类型修改为int[],并初始化数组元素为-1,表示尚未分配联通分量ID。
  2. 在dfs方法中,将visited[v]设置为cccount,表示顶点v属于当前联通分量ID。
  3. 在count方法中,返回cccount
  4. 添加一个新的方法getComponentID(int v),用于获取顶点v所属的联通分量ID。
/**CC类用于计算无向图的联通分量个数。
 * @author wushaopei
 * @create 2023-06-03 21:49
 */
public class CC_INT {

    private Graph G;                // 顶点数
    private int [] visited;     // 边数
    private int cccount = 0;             // 联通分量个数

    public CC_INT(Graph G){
        this.G = G;
        visited = new int[G.V()];   // 初始化顶点访问标记数组,默认为false

        for (int i = 0; i < visited.length; i++){
            visited[i] = -1;
        }
        for (int v = 0; v < G.V(); v++ ){
            if (visited[v] == -1){
                dfs(v, cccount);
                cccount++;   // 搜索完成后,联通分量个数加1
            }
        }
    }

    private void dfs(int v, int ccid){
        visited[v] = ccid;
        for (int w : G.adj(v)) {
            if (visited[w] == -1) {
                dfs(w, ccid);
            }
        }
    }

    public int count(){
        return cccount;
    }

    public boolean isConnected(int v, int w){
        G.validateVertex(v);
        G.validateVertex(w);
        return visited[v] == visited[w];
    }
    public static void main(String[] args) {
        Graph graph = new Graph("cc.txt");
        CC_INT cc = new CC_INT(graph);
        System.out.println(cc.isConnected(0,5));
    }
}

测试顶点是否属于同一个联通量上:

false

返回每个联通分量中的顶点列表:

    public ArrayList<Integer>[] components(){
        ArrayList<Integer>[] res = new ArrayList[cccount];
        for (int i = 0; i < cccount; i ++)
            res[i] = new ArrayList<>();
        for (int v = 0; v < G.V(); v ++)
            res[visited[v]].add(v);
        return res;
    }

这段代码是在CC类中添加了一个新的方法components(),用于返回每个联通分量中的顶点列表。

执行结果:

0 : 0 1 2 3 4 6 
1 : 5 

Process finished with exit code 0

2. 路径问题

如果两个顶点在同一个联通分量中,那么它们之间一定存在路径。联通分量是指图中的一组顶点,这些顶点之间可以相互连通,通过边进行路径的传递。

单源路径问题是指在给定的图中,找到从单个源顶点到其他所有顶点的路径。下面是使用广度优先搜索(BFS)来解决单源路径问题的步骤:

public class SingleSourcePath {

    private Graph G;           // 图对象
    private int s;             // 源顶点
    private boolean[] visited; // 记录顶点是否访问过
    private int[] pre;         // 记录每个顶点在路径中的前一个顶点

    public SingleSourcePath(Graph G, int s){
        G.validateVertex(s);

        this.G = G;
        this.s = s;

        visited = new boolean[G.V()]; // 初始化visited数组,默认所有顶点未访问
        pre = new int[G.V()];         // 初始化pre数组,默认所有顶点前一个顶点为-1

        for (int i = 0; i < pre.length; i++) {
            pre[i] = -1;
        }

        dfs(s, s); // 调用深度优先搜索方法,从源顶点s开始遍历图
    }

    private void dfs(int v, int parent){
        visited[v] = true;  // 标记当前顶点为已访问
        pre[v] = parent;    // 设置当前顶点的前一个顶点为parent
        for (int w : G.adj(v)) {
            if (!visited[w]) {
                dfs(w, v);  // 递归遍历当前顶点的未访问过的邻接顶点
            }
        }
    }

    public boolean isConnectedTo(int t){
        G.validateVertex(t); // 验证目标顶点t的有效性
        return visited[t];   // 返回目标顶点t是否与源顶点s相连
    }

    public Iterable<Integer> path(int t){
        ArrayList<Integer> res = new ArrayList<>();
        if (!isConnectedTo(t)) return res; // 若目标顶点t与源顶点s不相连,则返回空路径

        int cur = t;
        while (cur != s){
            res.add(cur);          // 将当前顶点加入路径中
            cur = pre[cur];        // 更新当前顶点为其前一个顶点
        }
        res.add(s);                // 将源顶点s加入路径中

        Collections.reverse(res);  // 反转路径列表,得到从源顶点s到目标顶点t的路径
        return res;                // 返回路径列表
    }

    public static void main(String[] args) {
        Graph graph = new Graph("cc.txt");
        SingleSourcePath sspath = new SingleSourcePath(graph, 0);
        System.out.println("0 -> 6 : " + sspath.path(6)); // 打印从顶点0到顶点6的路径
    }
}

在构造函数中,使用深度优先搜索(DFS)从源顶点s开始遍历图,并记录每个顶点在路径中的前一个顶点。通过isConnectedTo(t)方法可以判断目标顶点t是否与源顶点s相连,通过path(t)方法可以获取从源顶点s到目标顶点t的路径。在path(t)方法中,根据记录的前一个顶点信息

3. 从v开始遍历,看是否可以达到t

import java.util.ArrayList;
import java.util.Collections;

/**
 * @author wushaopei
 * @create 2023-06-03 21:49
 */
public class Path {

    private Graph G;           // 图对象
    private int s;             // 源顶点
    private int t;

    private boolean[] visited; // 记录顶点是否访问过
    private int[] pre;         // 记录每个顶点在路径中的前一个顶点

    public Path(Graph G, int s, int t){
        G.validateVertex(s);

        this.G = G;
        this.s = s;
        this.t = t;

        visited = new boolean[G.V()]; // 初始化visited数组,默认所有顶点未访问
        pre = new int[G.V()];         // 初始化pre数组,默认所有顶点前一个顶点为-1

        for (int i = 0; i < pre.length; i++) {
            pre[i] = -1;
        }
        dfs(s, s); // 调用深度优先搜索方法,从源顶点s开始遍历图

        for (boolean e: visited)
            System.out.print(e + " ");
        System.out.println();
    }

    private boolean dfs(int v, int parent){
        visited[v] = true;  // 标记当前顶点为已访问
        pre[v] = parent;    // 设置当前顶点的前一个顶点为parent

        if (v == t) return true;
        for (int w : G.adj(v)) {
            if (!visited[w]) {
                if (dfs(w, v))  // 递归遍历当前顶点的未访问过的邻接顶点
                    return true;
            }
        }
        return false;
    }

    public boolean isConnected(){
        return visited[t];   // 返回目标顶点t是否与源顶点s相连
    }

    public Iterable<Integer> path(){
        ArrayList<Integer> res = new ArrayList<>();
        if (!isConnected()) return res; // 若目标顶点t与源顶点s不相连,则返回空路径

        int cur = t;
        while (cur != s){
            res.add(cur);          // 将当前顶点加入路径中
            cur = pre[cur];        // 更新当前顶点为其前一个顶点
        }
        res.add(s);                // 将源顶点s加入路径中

        Collections.reverse(res);  // 反转路径列表,得到从源顶点s到目标顶点t的路径
        return res;                // 返回路径列表
    }

    public static void main(String[] args) {
        Graph graph = new Graph("cc.txt");
        Path sspath = new Path(graph, 0, 6);
        System.out.println("0 -> 6 : " + sspath.path()); // 打印从顶点0到顶点6的路径

        Path sspath2 = new Path(graph, 0, 6);
        System.out.println("0 -> 1 : " + sspath2.path()); // 打印从顶点0到顶点6的路径
    }
}

上述代码,新增了一个顶点目标顶点t作为构造函数的参数,用于指定要寻找路径的目标顶点。将isConnectedTo()方法更名为isConnected(),用于判断源顶点s和目标顶点t是否相连。在dfs()方法中,增加了一个判断条件if (v == t) return true;,当当前顶点v等于目标顶点t时,直接返回true,表示找到了源顶点到目标顶点的路径。

main()方法中,创建了一个新的Path对象sspath2,用于寻找从顶点0到顶点1的路径。

这个版本的代码在功能上与前一个版本基本相同,不同之处在于可以指定目标顶点,且通过isConnected()方法判断源顶点和目标顶点是否相连,而不再需要单独调用path()方法来判断路径是否存在。

4. 检测无向图中的环

/**
 * @author wushaopei
 * @create 2023-06-04 22:53
 */
import java.util.*;

public class CycleDetection {
    private Graph G;               // 图对象
    private boolean[] visited;     // 记录顶点是否访问过
    private boolean hasCycle;      // 是否存在环

    public CycleDetection(Graph G) {
        this.G = G;
        visited = new boolean[G.V()];
        hasCycle = false;

        for (int v = 0; v < G.V(); v++) {
            if (!visited[v]) {
                if (dfs(v, v)) {
                    hasCycle = true;
                    break;
                }
            }
        }
    }

    // 从顶点v开始,判断图中是否有环
    private boolean dfs(int v, int parent) {
        visited[v] = true;
        for (int w : G.adj(v)) {
            if (!visited[w]) {
                if (dfs(w, v)) return true;
            } else if (w != parent) {
                // 如果顶点w已经被访问过,并且w不是当前顶点v的父节点,说明存在环
                return true;
            }
        }
        return false;
    }

    public boolean hasCycle() {
        return hasCycle;
    }

    public static void main(String[] args) {
        Graph graph = new Graph("cc.txt");
        CycleDetection cycleDetection = new CycleDetection(graph);
        System.out.println("Has cycle: " + cycleDetection.hasCycle());

        Graph graph2 = new Graph("cc2.txt");
        CycleDetection cycleDetection2 = new CycleDetection(graph2);
        System.out.println("Has cycle: " + cycleDetection2.hasCycle());
    }
}

检测的数cc.txt、cc2.txt:

检测结果:

Connected to the target VM, address: '127.0.0.1:50987', transport: 'socket'
Has cycle: true
Has cycle: false
Disconnected from the target VM, address: '127.0.0.1:50987', transport: 'socket'

Process finished with exit code 0

5. 二分图

5.1 概述

二分图,也称为二部图或二分图,是一种特殊的图结构。它的顶点集可以分为两个互不相交的子集,使得同一个子集内的顶点之间没有边相连。换句话说,可以用两种颜色对顶点进行着色,使得任意一条边的两个顶点颜色不相同。

二分图具有许多应用,例如任务分配、时间表调度、匹配问题等。在计算机科学中,判断一个图是否为二分图是一个重要的问题。

二分图的判断可以使用多种算法,其中一种常见的算法是使用深度优先搜索(DFS)或广度优先搜索(BFS)。

染色

染色是判断图是否为二分图的常用方法之一。该方法基于以下原理:如果一个图是二分图,那么可以用两种颜色对图的顶点进行染色,使得相邻顶点的颜色不同。

二分图检测

下面是一个用DFS判断无向图是否为二分图的代码:

import java.util.ArrayList;

/**
 * @author wushaopei
 * @create 2023-06-03 21:49
 */
public class BipartitionDetection {

    private Graph G;           // 顶点数
    private boolean [] visited;         // 边数
    private int[] colors;
    private boolean isBipartite = true;

    public BipartitionDetection(Graph G){
        this.G = G;
        visited = new boolean[G.V()];
        colors = new int[G.V()];

        for (int i = 0; i < G.V(); i ++)
            colors[i] = -1;

        for (int v = 0; v < G.V(); v++ ){
            if (!visited[v]){
                if (!dfs(v, 0))
                    isBipartite = false;
            }
        }
    }

    private boolean dfs(int v, int color){
        visited[v] = true;
        colors[v] = color;
        for (int w : G.adj(v)) {
            if (!visited[w]) {
                if (!dfs(w, color == 0? 1:0)) return false;
            }
            else if (colors[v] == colors[w]) return false;
        }
        return true;
    }

    public boolean isBipartite(){
        return isBipartite;
    }


    public static void main(String[] args) {
        Graph graph = new Graph("cc.txt");
        BipartitionDetection bipartitionDetection = new BipartitionDetection(graph);
        System.out.println(bipartitionDetection.isBipartite());
    }
}

以上代码使用DFS进行图的遍历,并在遍历过程中对顶点进行染色。初始时,将第一个顶点的颜色设为0,然后递归地遍历该顶点的邻接顶点,并将它们染色为与当前顶点颜色不同的颜色(0或1)。如果发现某个顶点的邻接顶点已经被访问过,并且颜色与当前顶点相同,则图不是二分图,返回false。如果遍历结束后没有发现冲突,则图是二分图,返回true。

使用染色法进行二分图的判断具有简单直观的思路,时间复杂度为O(V+E),其中V为顶点数,E为边数。该方法在实际应用中也有较好的效果。

6. 扩展

DFS还有图同构、NP难等知识点。

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

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

相关文章

【MCS-51单片机汇编语言】期末复习总结⑥——串口通信(题型六)

文章目录 知识准备发送/接收缓冲器 SBUF串口通信控制寄存器SCON电源控制寄存器 PCON各个工作方式波特率的设定 常考题型例题1题目描述题目解析题解 例题2题目描述题解 知识准备 发送/接收缓冲器 SBUF 单片机在发送或接收数据的前先将数据存储在SBUF中&#xff1b;接收&#x…

STM32单片机蓝牙APP语音识别取暖器GSM短信超温报警

实践制作DIY- GC0141-蓝牙APP语音识别取暖器 基于STM32单片机设计---蓝牙APP语音识别取暖器 二、功能介绍&#xff1a; 电路&#xff1a;STM32F103C最小系统DS18B20温度传感器 多个按键 LCD1602显示器 1个串口语音识别模块1个5V 加热片 模拟加热蜂鸣器SIM800 GSM短信模块 HC0…

Type-C口统一在即,多节锂电池充放电管理难题何解?

在USB PD3.0时代&#xff0c;100W的充电功率已经能够满足绝大多数便携设备的充电需求&#xff0c;如智能手机、平板电脑、笔记本电脑等。最新的USB PD3.1快充标准&#xff0c;充电功率从原有的100W提升至240W&#xff0c;并支持最大48V的电压输出&#xff0c;将快充场景进一步延…

第四章 部署远程访问服务

♥️作者介绍&#xff1a;奇妙的大歪 ♥️个人名言&#xff1a;但行前路&#xff0c;不负韶华&#xff01; ♥️个人简介&#xff1a;云计算网络运维专业人员 目录 一.什么是远程访问&#xff1f; 二.远程访问的组成 三.远程访问的方式有哪些&#xff1f; 一、远程访问软件…

2017 年一月联考逻辑真题

2017 年一月联考逻辑真题 真题&#xff08;2017-26&#xff09; 26. 倪教授认为&#xff0c;我国工程技术领域可以考虑与国外先进技术合作&#xff0c;但任何涉及核心技术的项目就不能受制于人&#xff0c;我国许多网络安全建设项目涉及信息核心技术。如果全盘引进国外先进技术…

深圳市有什么靠谱的PMP机构推荐吗?

PMP项目管理专业人士资格认证是由美国项目管理协会&#xff08;Project Management Institute&#xff0c;简称PMI&#xff09;发起的。PMP作为世界级的项目管理认证证书&#xff0c;拥有着最先进的项目管理知识体系&#xff0c;它严格评估项目管理人员知识技能是否具有高品质的…

Soundful:AI音乐生成器

【产品介绍】 Soundful是一个基于人工智能的AI音乐生成器&#xff0c;可以让你在点击按钮的瞬间&#xff0c;生成适合视频、直播、播客等内容的免版税背景音乐。不仅拥有多种风格和情绪的模板&#xff0c;还可以让你下载高质量的音轨和分轨&#xff0c;以及自定义音乐的参数。…

基于Django Admin+HttpRunner-1.5.6开发简易的接口测试平台

前言 这是一个使用HttpRunner开发接口平台的简单Demo。 新建Django项目 安装依赖包 pip install httprunner1.5.6 -i https://pypi.doubanio.com/simple/ 模型规划 项目Project&#xff1a;包含 名称、创建时间、修改时间测试套件TestSuite&#xff1a;对应HttpRunner的一个…

自学测试半年,终于收到了字节的offer,那一刻我哭出了声...

我是一名毕业于普通一本的化学专业学生&#xff0c;毕业的两年时间里&#xff0c;我一直奔波在化工厂里。每天工作三班倒&#xff0c;下了班就是一包烟一瓶酒&#xff0c;生活过得非常堕落。 原本想着虽然每天很累&#xff0c;但是至少稳定。然而没有想到的是&#xff0c;化工…

【复杂网络建模】——通过平均度和随机概率构建ER网络(Python)

&#x1f935;‍♂️ 个人主页&#xff1a;Lingxw_w的个人主页 ✍&#x1f3fb;作者简介&#xff1a;计算机科学与技术研究生在读 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4a…

频谱仪设置积分功率

按键作用说明&#xff1a; 1&#xff0c;freq&#xff1a;设置频谱仪显示要采集的中心频率范围&#xff0c;先观察明白要测的是哪一段。 按下freq后&#xff0c;手动输入数字大小&#xff0c;然后从竖着的一列选择单位。 2&#xff0c;SPAN:以扫描频率为中心&#xff0c;信号…

大型企业数智化关键举措太难懂?这本数智平台白皮书带你秒理解

“IDC观点&#xff1a;未来企业都会成为数字原生企业&#xff0c;数智化业务将成为主流&#xff0c;因而企业需要积极探索适合自己的数智化转型方法&#xff0c;统筹结合外部业务商业创新、内部管理变革以及产业互联进程&#xff0c;并密切关注数智化升级和转型过程的安全性和可…

Python的缩进规则

目录 缩进规则 缩进异常 IDLE 开发环境对缩进量的设置 缩进规则 和其它程序设计语言&#xff08;如 Java、C 语言&#xff09;采用大括号“{}”分隔代码块不同&#xff0c;Python 采用代码缩进和冒号&#xff08; : &#xff09;来区分代码块之间的层次。 在 Python 中&…

Android 反编译工具 jadx-gui

jadx-gui 是一种基于 jadx 项目的图形界面工具&#xff0c;用于反编译 Android 应用程序的工具。通过使用jadx-gui&#xff0c;开发人员可以打开 APK&#xff08;Android应用程序包&#xff09;文件&#xff0c;并查看其反编译的源代码。这对于分析、理解和调试 Android 应用程…

组合预测模型 | ARIMA-LSTM时间序列预测(Python)

组合预测模型 | ARIMA-LSTM时间序列预测&#xff08;Python&#xff09; 目录 组合预测模型 | ARIMA-LSTM时间序列预测&#xff08;Python&#xff09;预测结果基本介绍程序设计参考资料 预测结果 基本介绍 ARIMA-LSTM时间序列预测&#xff08;Python完整源码和数据&#xff09…

设计模式——中介者

1.定义 用一个中介对象封装一系列对象交互&#xff0c;中介者使各对象不需要显示的相互引用&#xff0c;从而使其耦合松散&#xff0c;而且可以独立的改变它们之间的交互。 2.使用场景 1、系统中对象之间存在比较复杂的引用关系&#xff0c;导致它们之间的依赖关系结构混乱而…

关于Nginx的那些事

关于Nginx的那些事 一、Nginx的基础nginx VS apache 二、 编译安装Nginx服务1.关闭防火墙&#xff0c;将安装nginx所需软件包传到/opt目录下2.安装依赖包3.创建运行用户、组4.编译安装Nginx5.检查、启动、重启、停止 nginx服务新版本升级&#xff1a; 6.添加 Nginx 系统服务方法…

【机器学习】正则化详解和过拟合的解决

https://blog.csdn.net/weixin_45434953/article/details/130970273 上一篇文章的例子中&#xff0c;如果使用一个四次多项式去拟合房价函数&#xff0c;会导致过拟合问题 而正则化是解决过拟合的一个方法。右图过拟合是因为其三次方项和四次方项的影响&#xff0c;我们再回顾…

ChatGPT们对今后社会生活的影响

探索ChatGPT&#xff0c;协助工作学习创作。加入「阿杰与AI」公众号&#xff0c;一同探讨&#xff0c;一同成长&#xff0c;比他人更进一步。 1.AI、OpenAI、MidJourney发展史2.ChatGPT们对今后社会生活的影响3.目前市面比较好的AI产品介绍4.注册方式汇总5.针对初学者的 ChatG…

C#,码海拾贝(28)——求解“对称正定方程组”的“平方根法”之C#源代码

using System; namespace Zhou.CSharp.Algorithm { /// <summary> /// 求解线性方程组的类 LEquations /// 原作 周长发 /// 改编 深度混淆 /// </summary> public static partial class LEquations { /// <summary> /…