DFS(深度搜索)无向图遍历(JAVA手把手深入解析)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: DFS(深度搜索)无向图遍历(JAVA手把手深入解析)

DFS(深度搜索)无向图遍历(JAVA手把手深入解析)


前言

       到了DFS与BFS这里就是一个省一的分界线了,能搞定的省一基本没有问题,当然,也有靠纯暴力进入省一的,但是几率就会小一些。这篇文章我已经将DFS拆分的很细了呢,希望能帮助大家跨过蓝桥杯的这个分水岭。

       如果帮助到了你,请留下你的三连支持。

DFS深度优先

       深度优先搜索是一种在开发爬虫早期使用较多的方法。它的目的是要达到被搜索结构的叶结点(即那些不包含任何超链的HTML文件) 。在一个HTML文件中,当一个超链被选择后,被链接的HTML文件将执行深度优先搜索,即在搜索其余的超链结果之前必须先完整地搜索单独的一条链。深度优先搜索沿着HTML文件上的超链走到不能再深入为止,然后返回到某一个HTML文件,再继续选择该HTML文件中的其他超链。当不再有其他超链可选择时,说明搜索已经结束。

图中的深度结果就是:0->1->3->4->2

这是深度搜索DFS的遍历方式。

我们已经知道DFS是怎么个逻辑了,那么我们就画一个图做个DFS的搜索。(图随便画,一会自己能根据深度搜索的理论把对应的数组写出来就行)。

无向图

这里我们来自己画。画的跟树类似,可以使用类创建左右孩子的方式来解决,但是咱们为了更好的让大一的孩子们理解,所以用一个类来决绝这个问题。

途中我们依照DFS深度搜索的方式目标输出结果是:1->2->4->7->5->3->6。我们接下来就开始我们的编码,看看是否能按照这个DFS的方式进行遍历。

DFS全局变量定义

1、节点

为了帮助孩子们理解,我这里使用的是拼音拼写的变量【dian】

public static String dian[] = { "1", "2", "3", "4", "5", "6", "7" };

2、节点数

我们所有的操作都会依赖于这个长度来进行遍历,故而这里我单独写了一下:

public static int d_length=d.length;

3、根据图创建数组

我们用二位数组来表达我们的输出顺序。

1的关联方式是:1列链接1,3故而有数组{0,1,1,0,0,0,0}来表示【1】,那么,同理我们依次推导出2~7的数组表达。

挨个根据图来写,只要有链接就记录一个【1】,没有链接就记录【0】,自身遇到自身记录【0】。

public static int[][] arr= {
      {0,1,1,0,0,0,0},
      {1,0,0,1,1,0,0},
      {1,0,0,0,0,1,0},
      {0,1,0,0,0,0,1},
      {0,1,0,0,0,0,0},
      {0,0,1,0,0,0,0},
      {0,0,0,1,0,0,0},
  };

这样我们就把图形的规律记录成了一个长度为定点长度的二位数组。

4、状态记录数组

public static boolean[] isfStatus;

四个全局变量

这里我们共计创建了4个全局变量,依次是:

顶点、图转换数组、判断是否走过、记录每一个节点的遍历过程,走过则记录为true。

/**                                                            
 * 顶点                                                          
 */                                                            
public static String d[] = { "1", "2", "3", "4", "5", "6", "7" 
/**                                                            
 * 图转换数组                                                       
 */                                                            
public static int[][] arr= {                                   
    {0,1,1,0,0,0,0},                                       
    {1,0,0,1,1,0,0},                                       
    {1,0,0,0,0,1,0},                                       
    {0,1,0,0,0,0,1},                                       
    {0,1,0,0,0,0,0},                                       
    {0,0,1,0,0,0,0},                                       
    {0,0,0,1,0,0,0},                                       
};                                                             
/**                                                            
 * 顶点长度
 */                                                            
public static int d_length=d.length;                           
/**                                                            
 * 记录每一个节点的遍历过程,走过则记录为true                                     
 */                                                            
public static boolean[] isfStatus;

这里我们所需要的变量就准备好了。

DFS代码

1、DFS启动·进入到递归搜索中

我们这里其实是注意行的深入,故而只要false就代表没有走过,我们需要遍历一下图,看看是否有对应的链接数组。

/**                                                        
 * 开始DFS
 */                                                        
public static void DFSStart() {                            
  //初始化数组,记录使用过程                                         
  isfStatus = new boolean[d_length];                     
  //遍历图数组                                                
  for (int i = 0; i < d_length; i++) {                   
    //因为初始的布尔数组都是false,所以只要是false就是没有走过                
    if (isfStatus[i] == false) {                       
      //进度到深度搜索的递归                                   
      DFS(i);                                        
    }                                                  
  }                                                      
}

2、深度递归

我们这里需要注意的是深度搜索的节点遍历范围,我们从第一个开始,然后逐一遍历。

节点控制(深搜核心):

从i行的列0开始遍历,只要有不是0的就代表有直接连接的,并且要找到的下层没有走过,也就是没有递归到,就开始判断。这里是核心。

/**                                                       
 * 递归深搜                                                   
 * @param i                                               
 */                                                       
private static void DFS(int i) {                          
  // TODO Auto-generated method stub                    
  System.out.print(d[i] + " ");                         
  isfStatus[i] = true;                                  
  // 深度搜索子节点·从第一个节点到下一个节点                               
  for (int j = firstValue(i); j >= 0; j = nextValue(i, j)) {
    if (!isfStatus[j]) {                              
      DFS(j);                                       
    }                                                 
  }                                                     
}

3、遍历节点

这里能看到我写了两个判断,判断1是arr[i][j]>0,还有第二个判断arr[i][k] == 1其实都是一样的,因为咱们的数组当中只有1与0两个,大于零是1,等等于1还是1,所以是一样的。

全局控制:变量【i】,我们通过变量【i】来控制我们遍历的行数,这样就能逐一击破了。

初始点:坐标点需要从最左侧的0开始遍历,只要找到不是0的数就代表有链接点了。

下一个链接:坐标得从第N个开始遍历了,因为之前的已经遍历过了。这里的N是变量【j】。

我们这里再加强一下理解:

先看【第一个连接点】,例如图中的【1】与【2,3】相连,我们遍历到2的时候也就是坐标【arr[0][1]】就代表1与2相连接,我们在继续向下层递归,也就是i向下走一层【DFS(i)】也就下一个有相关链接的点。有难度的时候我们可以通过debug来一层层的理解。

/**                                           
 * 第一个连接点                                     
 * @param i                                   
 * @return                                    
 */                                           
private static int firstValue(int i) {        
  for (int j = 0; j < d_length; j++) {      
    if (arr[i][j] > 0) {                  
      return j;                         
    }                                     
  }                                         
  return -1;                                
}                                             
/**                                           
 * 下一个连接点                                     
 * @param i                                   
 * @param j                                   
 * @return                                    
 */                                           
private static int nextValue(int i, int j) {  
  for (int k = (j + 1); k < d_length; k++) {
    if (arr[i][k] == 1) {                 
      return k;                         
    }                                     
  }                                         
  return -1;                                
}

4、最终输出:

public static void main(String[] args) {
  // TODO Auto-generated method stub
  DFSStart();
}

5、输出效果:

可以看到,输出内容与我们的目标结果是相同的,代表我们深度搜索的编码没有问题。

完整代码对照

这个是我把类都复制了一下啊,你需要改一下包名以及你的类名即可进行测试。

package com.item.action;
public class Demo_def {
  /**
   * 顶点
   */
  public static String d[] = { "1", "2", "3", "4", "5", "6", "7" };
  /**
   * 图转换数组
   */
  public static int[][] arr= {
      {0,1,1,0,0,0,0},
      {1,0,0,1,1,0,0},
      {1,0,0,0,0,1,0},
      {0,1,0,0,0,0,1},
      {0,1,0,0,0,0,0},
      {0,0,1,0,0,0,0},
      {0,0,0,1,0,0,0},
  };
  /**
   * 顶点长度
   */
  public static int d_length=d.length;
  /**
   * 记录每一个节点的遍历过程,走过则记录为true
   */
  public static boolean[] isfStatus;
  /**
   * 开始递归
   */
  public static void DFSStart() {
    //初始化数组,记录使用过程
    isfStatus = new boolean[d_length];
    //遍历图数组
    for (int i = 0; i < d_length; i++) {
      //因为初始的布尔数组都是false,所以只要是false就是没有走过
      if (isfStatus[i] == false) {
        //进度到深度搜索的递归
        DFS(i);
      }
    }
  }
  /**
   * 递归深搜
   * @param i
   */
  private static void DFS(int i) {
    // TODO Auto-generated method stub
    System.out.print(d[i] + " ");
    isfStatus[i] = true;
    // 深度搜索子节点·从第一个节点到下一个节点
    for (int j = firstValue(i); j >= 0; j = nextValue(i, j)) {
      if (!isfStatus[j]) {
        DFS(j);
      }
    }
  }
  /**
   * 第一个连接点
   * @param i
   * @return
   */
  private static int firstValue(int i) {
    for (int j = 0; j < d_length; j++) {
      if (arr[i][j] > 0) {
        return j;
      }
    }
    return -1;
  }
  /**
   * 下一个连接点
   * @param i
   * @param j
   * @return
   */
  private static int nextValue(int i, int j) {
    for (int k = (j + 1); k < d_length; k++) {
      if (arr[i][k] == 1) {
        return k;
      }
    }
    return -1;
  }
  public static void main(String[] args) {
    // TODO Auto-generated method stub
    DFSStart();
  }
}

完整代码留在这里是帮助大家对照的啊,别直接复制用,这样意义就不那么高了。

总结

       我们咋做蓝桥杯题的时候很多的时候都是有套路的,我们很多时候通过我们背过的一些套路去套题目也会直接出结果的,例如全排列的方法,还有很多的公式,欧几里得,欧拉等等,都是很方便的,我们其实不是算法的创造者,我们只是知识的搬运工,争取将更多的知识搬运到咱们的大脑中啊。

下一篇我会好好准备一下BFS深度搜索,大家好好用用心是可以完全搞定的,加油。

目录
打赏
0
0
0
0
122
分享
相关文章
|
6天前
|
Java代码结构解析:类、方法、主函数(1分钟解剖室)
### Java代码结构简介 掌握Java代码结构如同拥有程序世界的建筑蓝图,类、方法和主函数构成“黄金三角”。类是独立的容器,承载成员变量和方法;方法实现特定功能,参数控制输入环境;主函数是程序入口。常见错误包括类名与文件名不匹配、忘记static修饰符和花括号未闭合。通过实战案例学习电商系统、游戏角色控制和物联网设备监控,理解类的作用、方法类型和主函数任务,避免典型错误,逐步提升编程能力。 **脑图速记法**:类如太空站,方法即舱段;main是发射台,static不能换;文件名对仗,括号要成双;参数是坐标,void不返航。
25 5
Java中Log级别和解析
日志级别定义了日志信息的重要程度,从低到高依次为:TRACE(详细调试)、DEBUG(开发调试)、INFO(一般信息)、WARN(潜在问题)、ERROR(错误信息)和FATAL(严重错误)。开发人员可根据需要设置不同的日志级别,以控制日志输出量,避免影响性能或干扰问题排查。日志框架如Log4j 2由Logger、Appender和Layout组成,通过配置文件指定日志级别、输出目标和格式。
Java二维数组的使用技巧与实例解析
本文详细介绍了Java中二维数组的使用方法
59 15
深潜数据海洋:Java文件读写全面解析与实战指南
通过本文的详细解析与实战示例,您可以系统地掌握Java中各种文件读写操作,从基本的读写到高效的NIO操作,再到文件复制、移动和删除。希望这些内容能够帮助您在实际项目中处理文件数据,提高开发效率和代码质量。
24 4
【潜意识Java】深度解析黑马项目《苍穹外卖》与蓝桥杯算法的结合问题
本文探讨了如何将算法学习与实际项目相结合,以提升编程竞赛中的解题能力。通过《苍穹外卖》项目,介绍了订单配送路径规划(基于动态规划解决旅行商问题)和商品推荐系统(基于贪心算法)。这些实例不仅展示了算法在实际业务中的应用,还帮助读者更好地准备蓝桥杯等编程竞赛。结合具体代码实现和解析,文章详细说明了如何运用算法优化项目功能,提高解决问题的能力。
82 6
如何在 Java 代码中使用 JSqlParser 解析复杂的 SQL 语句?
大家好,我是 V 哥。JSqlParser 是一个用于解析 SQL 语句的 Java 库,可将 SQL 解析为 Java 对象树,支持多种 SQL 类型(如 `SELECT`、`INSERT` 等)。它适用于 SQL 分析、修改、生成和验证等场景。通过 Maven 或 Gradle 安装后,可以方便地在 Java 代码中使用。
417 11
【潜意识Java】期末考试可能考的高质量大题及答案解析
Java 期末考试大题整理:设计一个学生信息管理系统,涵盖面向对象编程、集合类、文件操作、异常处理和多线程等知识点。系统功能包括添加、查询、删除、显示所有学生信息、按成绩排序及文件存储。通过本题,考生可以巩固 Java 基础知识并掌握综合应用技能。代码解析详细,适合复习备考。
29 4
基于Java的Hadoop文件处理系统:高效分布式数据解析与存储
本文介绍了如何借鉴Hadoop的设计思想,使用Java实现其核心功能MapReduce,解决海量数据处理问题。通过类比图书馆管理系统,详细解释了Hadoop的两大组件:HDFS(分布式文件系统)和MapReduce(分布式计算模型)。具体实现了单词统计任务,并扩展支持CSV和JSON格式的数据解析。为了提升性能,引入了Combiner减少中间数据传输,以及自定义Partitioner解决数据倾斜问题。最后总结了Hadoop在大数据处理中的重要性,鼓励Java开发者学习Hadoop以拓展技术边界。
72 7
|
2月前
|
【潜意识Java】期末考试可能考的选择题(附带答案解析)
本文整理了 Java 期末考试中常见的选择题,涵盖数据类型、控制结构、面向对象编程、集合框架、异常处理、方法、流程控制和字符串等知识点。每道题目附有详细解析,帮助考生巩固基础,加深理解。通过这些练习,考生可以更好地准备考试,掌握 Java 的核心概念和语法。
46 1
【潜意识Java】期末考试可能考的简答题及答案解析
为了帮助同学们更好地准备 Java 期末考试,本文列举了一些常见的简答题,并附上详细的答案解析。内容包括类与对象的区别、多态的实现、异常处理、接口与抽象类的区别以及垃圾回收机制。通过这些题目,同学们可以深入理解 Java 的核心概念,从而在考试中更加得心应手。每道题都配有代码示例和详细解释,帮助大家巩固知识点。希望这些内容能助力大家顺利通过考试!
31 0

热门文章

最新文章

推荐镜像

更多
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等