LeetCode 133:克隆图(图的深度优先遍历DFS和广度优先遍历BFS)

回顾

图的Node数据结构

图的数据结构,以下两种都可以,dfs和bfs的板子是不变的。

class Node {public int val;public List<Node> neighbors;public Node() {val = 0;neighbors = new ArrayList<Node>();}public Node(int _val) {val = _val;neighbors = new ArrayList<Node>();}public Node(int _val, ArrayList<Node> _neighbors) {val = _val;neighbors = _neighbors;}
}```java
public class Node{public int value;//点的编号,不一定是Integer类型的,要看具体的题,有的题点编号为字母。public int in;//入度public int out;//出度public ArrayList<Node>nexts;//出去的边直接相连的邻居。public ArrayList<Edge>edges//出去的边public Node(int value){this.value=value;in = 0;out = 0;nexts = new ArrayList<>();edges = new ArrayList<>();}}

bfs和dfs的板子

图和二叉树的宽搜最大的不同的就是,图是可能有环的。二叉树是没环的,所以图可能死循环卡住,所以需要额外记录是否有访问过,一般是哈希表或者数组。
深搜是点入栈之前就需要处理了,广搜是点入队列之后开始处理。


public static void bfs(Node node){if(node==null) return;Queue<Node> queue = new LinkedList<>();HashSet<Node> set = new HashSet<>();queue.add(node);set.add(node);while(!queue.isEmpty()){Node cur = queue.poll();/*  具体的处理逻辑(宽搜一般是结点入队列后再处理)*/for(Node next: cur.nexts){if(!set.contains(next)){//如果set中没有,那么说明这个next结点没有被访问过queue.add(next);//扔到队列里set.add(next);//并且标记访问}}}
}public static void dfs(Node node){if(node==null) return;Stack<Node> stack = new Stack<>();HashSet<Node> set = new HashSet<>();stack.add(node);set.add(node);/*具体的处理逻辑(深搜一般是结点入栈前就进行处理)*/while(!stack.isEmpty()){Node cur = stack.pop();for(Node next:cur.nexts){if(!set.contains(next)){stack.push(cur);//在这里需要把cur和next两个结点同时入栈是因为stack.push(next);//想在栈里保持深度搜索的路径。这次搜索相比于上一次搜索,在栈中就多了一个next结点。set.add(cur);set.add(next);/*具体的处理逻辑 */break;//之所以立马break是因为深搜每次只走一步,不像宽搜每次走一层。}}}
}

题目

给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。

图中的每个节点都包含它的值 val(int) 和其邻居的列表(list[Node])。

class Node {
public int val;
public List neighbors;
}

测试用例格式:

简单起见,每个节点的值都和它的索引相同。例如,第一个节点值为 1(val = 1),第二个节点值为 2(val = 2),以此类推。该图在测试用例中使用邻接列表表示。

邻接列表 是用于表示有限图的无序列表的集合。每个列表都描述了图中节点的邻居集。

给定节点将始终是图中的第一个节点(值为 1)。你必须将 给定节点的拷贝 作为对克隆图的引用返回。
在这里插入图片描述
输入:adjList = [[2,4],[1,3],[2,4],[1,3]]
输出:[[2,4],[1,3],[2,4],[1,3]]
解释:
图中有 4 个节点。
节点 1 的值是 1,它有两个邻居:节点 2 和 4 。
节点 2 的值是 2,它有两个邻居:节点 1 和 3 。
节点 3 的值是 3,它有两个邻居:节点 2 和 4 。
节点 4 的值是 4,它有两个邻居:节点 1 和 3 。

示例 2:
输入:adjList = [[]]
输出:[[]]
解释:输入包含一个空列表。该图仅仅只有一个值为 1 的节点,它没有任何邻居。
示例 3:

输入:adjList = []
输出:[]
解释:这个图是空的,它不含任何节点。

提示:

节点数不超过 100 。
每个节点值 Node.val 都是唯一的,1 <= Node.val <= 100。
无向图是一个简单图,这意味着图中没有重复的边,也没有自环。
由于图是无向的,如果节点 p 是节点 q 的邻居,那么节点 q 也必须是节点 p 的邻居。
图是连通图,你可以从给定节点访问到所有节点。

思路

图的深拷贝可以由dfs或者bfs实现。但是需要注意:在这里的set不再是HashSet,因为它不是存储这个节点是否访问过。而是直接存储原节点和克隆节点的地址的键值对。因为图是无向图,或者说,双向图,所以访问过的节点也可能要再次处理的。譬如节点1的邻居是节点2,那么我们遍历节点1时,会增加“节点1->节点2”。但是如果只是看是否访问过时,遍历到节点2时就会因为访问过节点1而丧失掉“节点2->节点1”的这条边。


这道题用bfs会好一点。因为它是个存在环的无向图,bfs不存在回溯的情况,可以保证每个节点只被遍历一次,譬如遍历到节点1
时,直接构建节点1和其原有邻居列表的邻居关系,那么节点1为出发点的所有单边情况都构建好了。之后分别遍历到节点2和节点4时,也会将节点1为接受点的所有单边情况都构建好。所以刚好双边都能不多不少不重不漏地构建完。


但是dfs不同,因为dfs的每个节点不止会被只遍历一次,因为它要回溯。所以可能出现邻居节点重复添加的情况,所以构建邻居关系时比bfs需要多一步判重。
譬如开始时节点1先克隆并入栈。它原来的邻居节点有节点2和节点4,因为节点2没有被克隆过,所以开始遍历节点2。

节点2遍历时,它原来的邻居节点有节点1和节点3,因为节点1被克隆过,所以会构建“节点2->节点1”的邻居关系。但是因为节点3没有被克隆过,所以开始遍历节点3。

==节点3遍历时,原有邻居有节点2和节点4,因为节点2被克隆过,所以会构建“节点3->节点2”==的邻居关系,但是因为节点4没有被克隆过,所以开始遍历节点4.。

节点4遍历时,原有邻居有节点3和节点1,因为节点1被克隆过,所以会构建“节点4->节点1”的邻居关系。同时节点3也被克隆过所以会构建“节点4->节点3”的邻居关系。然后开始遍历节点3。
节点3遍历时,原有邻居有节点2和节点4,因为节点2被克隆过,所以如果不判重,会多构建一个“节点3->节点2”的邻居关系。

bfs代码

class Solution {public Node cloneGraph(Node node) {if(node==null) return node;//直接返回node就好了,因为是空指针。HashMap<Node, Node> set = new HashMap<>();Queue<Node> queue = new LinkedList<>();set.put(node, cloneNode(node));queue.add(node);while(!queue.isEmpty()){Node cur = queue.poll();for(Node next: cur.neighbors){if(!set.containsKey(next)){//如果这个节点没有被克隆过queue.add(next);//说明没有被遍历过,直接加入队列set.put(next, cloneNode(next));//并加入set}set.get(cur).neighbors.add(set.get(next));//不管有没有被克隆过,因为每个节点只会被遍历一次,那么它的邻居节点都需要一次性加入该节点的邻居列表中。同时注意不是直接set.get(cur).neighbors.add(next),而是set中next的克隆地址。}}return set.get(node);}
//这个函数,单纯地实现克隆功能,不连接邻居关系。private Node cloneNode(Node node){return new Node(node.val, new ArrayList<>());}
}

26ms,击败25.12%使用 Java 的用户

说实话,待优化……

dfs代码

class Solution {public Node cloneGraph(Node node) {if(node==null) return node;HashMap<Node, Node> set = new HashMap<>();Stack<Node> stack = new Stack<>();set.put(node,cloneNode(node));stack.add(node);while(!stack.isEmpty()){Node cur = stack.pop();for(Node next: cur.neighbors){if(!set.containsKey(next)){//如果这个next节点没有被克隆过stack.add(cur);//说明也没有被遍历过,为了不会回退到上次遍历过的节点,cur和next依次加入栈。stack.add(next);set.put(next,cloneNode(next)); //克隆next节点,放入setbreak;}List<Node> list = set.get(cur).neighbors;//如果这个next节点已经有克隆过了,那么看是否需要构建当前节点和next节点的邻居关系if(!list.contains(set.get(next))) set.get(cur).neighbors.add(set.get(next));//如果之前已经有过当前节点和next节点的邻居关系,说明我们不是第一次遍历到当前节点,而是回溯过程中遇到的当前节点,所以不再需要构建邻居关系。}}return set.get(node);}public Node cloneNode(Node node){return new Node(node.val, new ArrayList<>());}
}

27ms,击败15.19%使用 Java 的用户

依旧待优化……

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

非常好看的CSS加载中特效,引用css文件既可用

非常好看的CSS加载中特效 demo效果源码&#xff1a; <!DOCTYPE html5> <head><link rel"stylesheet" type"text/css" href"demo.css"/><link rel"stylesheet" type"text/css" href"loaders.css&…

【CSS + ElementUI】更改 el-carousel 指示器样式且隐藏左右箭头

需求 前三条数据以走马灯形式展现&#xff0c;指示器 hover 时可以切换到对应内容 实现 <template><div v-loading"latestLoading"><div class"upload-first" v-show"latestThreeList.length > 0"><el-carousel ind…

CSS-IN-JS

CSS-IN-JS 为什么会有CSS-IN-JS CSS-IN-JS是web项目中将CSS代码捆绑在JavaScript代码中的解决方案。 这种方案旨在解决CSS的局限性&#xff0c;例如缺乏动态功能&#xff0c;作用域和可移植性。 CSS-IN-JS介绍 1&#xff1a;CSS-IN-JS方案的优点&#xff1a; 让css代码拥…

sqlserver alwayson部署文档手册

1、ALWAYSON概述 详细介绍参照官网详细文档,我就不在这里赘述了&#xff1a; https://learn.microsoft.com/zh-cn/sql/database-engine/availability-groups/windows/overview-of-always-on-availability-groups-sql-server?viewsql-server-ver16 下图显示的是一个包含一个…

matlab使用jdbc连接数据库

1、打包jdbc 2、在matlab安装目录下&#xff0c;进去toolbox目录下&#xff0c;新建一个对应放jdbc包的文件夹&#xff0c;加入放入的是mysql的jdbc驱动包&#xff0c;就新建一个mysql目录&#xff0c;将驱动包放入mysql目录下 3、在toolbox目录下&#xff0c;找到local目录&a…

Mac安装Homebrew+MySQL+Redis+Nginx+Tomcat等

Mac安装HomebrewMySQLRedisNginxTomcat等 文章目录 Mac安装HomebrewMySQLRedisNginxTomcat等一、Mac安装Mysql 8①&#xff1a;下载②&#xff1a;安装③&#xff1a;配置环境变量④&#xff1a;外部连接测试 二、Mac安装Redis和可视化工具①&#xff1a;安装Redis01&#xff1…

JDK和Spring的SPI机制原理分析

目录 一、JDK 二、Spring框架介绍 三、SPI机制原理 一、JDK JDK是Java Development Kit的缩写&#xff0c;是Java开发工具包的意思。它是用于开发Java应用程序和运行Java程序的软件包。JDK包含了Java编译器&#xff08;javac&#xff09;和Java虚拟机&#xff08;JVM&#…

vue中 日期选择--本日、本周、本月、本年选择器实现(基于elementui)

效果图&#xff1a; 由于项目需要图标统计展示&#xff0c;需要日期美观化选择如上图所示&#xff0c;代码如下&#xff1a; <template><div class"el-page body"><el-row><el-col class"statistic-analysis-report-style" :span&qu…

大数据 - Spark系列《三》- 加载各种数据源创建RDD

Spark系列文章&#xff1a; 大数据 - Spark系列《一》- 从Hadoop到Spark&#xff1a;大数据计算引擎的演进-CSDN博客 大数据 - Spark系列《二》- 关于Spark在Idea中的一些常用配置-CSDN博客 目录 3.1&#x1f9c0;加载文件(本地) 1. 加载本地文件路径 &#x1f32e;使用te…

kafka客户端生产者消费者kafka可视化工具(可生产和消费消息)

点击下载《kafka客户端生产者消费者kafka可视化工具&#xff08;可生产和消费消息&#xff09;》 1. 前言 因在工作中经常有用到kafka做消息的收发&#xff0c;每次调试过程中&#xff0c;经常需要查看接收的消息内容以及人为发送消息&#xff0c;从网上搜寻了一下&#xff0…

python常用pandas函数nlargest / nsmallest及其手动实现

目录 pandas库 Series和DataFrame nlargest和nsmallest 用法示例 代替方法 手动实现 模拟代码 pandas库 是Python中一个非常强大的数据处理库,提供了高效的数据分析方法和数据结构。它特别适用于处理具有关系型数据或带标签数据的情况,同时在时间序列分析方面也有着出…

银河麒麟 aarch64 Mysql环境安装

一、操作系统版本信息 组件版本操作系统Kylin V10 (SP3) /(Lance)-aarch64-Build23/20230324Kernel4.19.90-52.22.v2207.ky10.aarch64MySQLmysql-8.3.0JDK1.8.0_312 二、MySQL下载 官网下载地址&#xff1a;https://dev.mysql.com/downloads/mysql/ 三、MySQL 安装 3.1 删…