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

news2024/9/24 1:18:23

回顾

图的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节点,放入set
                    break;
                }
                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.coloradmin.cn/o/1439550.html

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

相关文章

【大模型上下文长度扩展】MedGPT:解决遗忘 + 永久记忆 + 无限上下文

MedGPT&#xff1a;解决遗忘 永久记忆 无限上下文 问题&#xff1a;如何提升语言模型在长对话中的记忆和处理能力&#xff1f;子问题1&#xff1a;有限上下文窗口的限制子问题2&#xff1a;复杂文档处理的挑战子问题3&#xff1a;长期记忆的维护子问题4&#xff1a;即时信息检…

Docker的镜像和容器的区别

1 Docker镜像 假设Linux内核是第0层&#xff0c;那么无论怎么运行Docker&#xff0c;它都是运行于内核层之上的。这个Docker镜像&#xff0c;是一个只读的镜像&#xff0c;位于第1层&#xff0c;它不能被修改或不能保存状态。 一个Docker镜像可以构建于另一个Docker镜像之上&…

前端ajax技术

ajax可以实现局部刷新&#xff0c;也叫做无刷新&#xff0c;无刷新指的是整个页面不刷新&#xff0c;只是局部刷新&#xff0c;ajax可以自己发送http请求&#xff0c;不用通过浏览器的地址栏&#xff0c;所以页面整体不会刷新&#xff0c;ajax获取到后台数据&#xff0c;更新页…

RabbitMQ高可用架构涉及常用功能整理

RabbitMQ高可用架构涉及常用功能整理 1. rabbitmq的集群模式2. 镜像模式高可用系统架构和相关组件3. rabbitmq的核心参数3.1 镜像策略3.2 新镜像同步策略3.3 从节点晋升策略3.4 主队列选择策略 4. rabbitmq常用命令4.1 常用基础命令4.1.1 服务管理4.1.2 用户管理4.1.3 角色管理…

基于微信上海美食小程序系统设计与实现 研究背景和意义、国内外现状

博主介绍&#xff1a;黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者&#xff0c;CSDN博客专家&#xff0c;在线教育专家&#xff0c;CSDN钻石讲师&#xff1b;专注大学生毕业设计教育和辅导。 所有项目都配有从入门到精通的基础知识视频课程&#xff…

DiskGenius v4.30专业版下载

DiskGenius是一款专业级的数据恢复软件&#xff0c;算法精湛、功能强大&#xff0c;用户群体广泛&#xff1b;支持各种情况下的文件恢复和分区恢复&#xff0c;恢复效果好&#xff1b;文件预览、扇区编辑、加密分区恢复、Ext4分区恢复、RAID恢复等高级功能应有尽有&#xff0c;…

Redis篇之分布式锁

一、为什么要使用分布式锁 1.抢劵场景 &#xff08;1&#xff09;代码及流程图 &#xff08;2&#xff09;抢劵执行的正常流程 就是正好线程1执行完整个操作&#xff0c;线程2再执行。 &#xff08;3&#xff09;抢劵执行的非正常流程 因为线程是交替进行的&#xff0c;所以有…

leetcode1079:游戏玩法分析——求留存率

求留存率 题目描述题解 题目描述 表&#xff1a;Activity --------------------- | Column Name | Type | --------------------- | player_id | int | | device_id | int | | event_date | date | | games_played | int | --------------------- &#xff08;player_id&…

点云——噪声(代码)

本人硕士期间研究的方向就是三维目标点云跟踪&#xff0c;对点云和跟踪有着较为深入的理解&#xff0c;但一直忙于实习未进行梳理&#xff0c;今天趁着在家休息对点云的噪声进行梳理&#xff0c;因为预处理对于点云项目是至关重要的&#xff0c;所有代码都是近期重新复现过。 这…

中科大计网学习记录笔记(七):Web and HTTP

前言&#xff1a; 学习视频&#xff1a;中科大郑烇、杨坚全套《计算机网络&#xff08;自顶向下方法 第7版&#xff0c;James F.Kurose&#xff0c;Keith W.Ross&#xff09;》课程 该视频是B站非常著名的计网学习视频&#xff0c;但相信很多朋友和我一样在听完前面的部分发现信…

vulnhub通关-3 DC-3(含靶场资源)

文章目录 一、环境搭建1.环境描述靶场描述题目表述概括&#xff08;目标&#xff09; 2.靶场下载下载地址 3.环境启动 二、渗透靶场1.信息收集&#xff1a;寻找靶机IP分析nmap扫描存活主机 2.信息收集&#xff1a;服务和端口探测命令解析 3.访问Web4.探测框架版本号joomscan安装…

编码技巧——在项目中使用Alibaba Cloud Toolkit远程部署

背景 在新公司项目开发&#xff0c;当前项目为自建项目&#xff0c;意思是从开发到运维都需要自己负责&#xff0c;远程的服务器也是自己搭建的win操作系统&#xff1b; 之前在大厂工作时&#xff0c;一般提交代码之后&#xff0c;CICD流水线会自动的执行最新代码的拉取、构建打…

【iOS ARKit】3D人体姿态估计实例

与2D人体姿态检测一样&#xff0c;在ARKit 中&#xff0c;我们不必关心底层的人体骨骼关节点检测算法&#xff0c;也不必自己去调用这些算法&#xff0c;在运行使用 ARBodyTrackingConfiguration 配置的 ARSession 之后&#xff0c;基于摄像头图像的3D人体姿态估计任务也会启动…

SERVLET过滤器

SERVLET过滤器 全球因特网用户使用不同类型的Web浏览器访问应用服务器上存储的Web应用程序。每个浏览器根据对应的Web浏览器窗口中的设置显示应用程序中的信息。Web应用程序可能会有一些客户机的Web浏览器不支持的HTML标记或功能。这种情况下,应用程序在客户机的Web浏览器中可…

基于SpringBoot3的快速迭代平台

SpringBoot3的快速开发平台 前言一、技术栈二、项目结构三、总结 前言 MateBoot是一个基于SpringBoot3的快速开发平台&#xff0c;采用前后端分离的模式&#xff0c;前端采用Element Plus组件&#xff0c;后端采用SpringBoot3、Sa-token、Mybatis-Plus、Redis、RabbitMQ、Fast…

蓝桥杯Web应用开发-CSS3 新特性【练习三:文本阴影】

文本阴影 text-shadow 属性 给文本内容添加阴影的效果。 文本阴影的语法格式如下&#xff1a; text-shadow: x-offset y-offset blur color;• x-offset 是沿 x 轴方向的偏移距离&#xff0c;允许负值&#xff0c;必须参数。 • y-offset 是沿 y 轴方向的偏移距离&#xff0c…

项目02《游戏-09-开发》Unity3D

基于 项目02《游戏-08-开发》Unity3D &#xff0c; 本次任务是做抽卡界面&#xff0c;获取的卡片增添在背包中&#xff0c;并在背包中可以删除卡片&#xff0c; 首先在Canvas下创建一个空物体&#xff0c;命名为LotteryPanel&#xff0c;作为抽卡界面&#xff0c; …

软件价值10-数字时钟

这是一个数字时钟程序&#xff1a; # importing whole module from tkinter import * from tkinter.ttk import *# importing strftime function to # retrieve systems time from time import strftime# creating tkinter window root Tk() root.title(Clock)# This functio…

多路服务器技术如何处理大量并发请求?

在当今的互联网时代&#xff0c;随着用户数量的爆炸性增长和业务规模的扩大&#xff0c;多路服务器技术已成为处理大量并发请求的关键手段。多路服务器技术是一种并行处理技术&#xff0c;它可以通过多个服务器同时处理来自不同用户的请求&#xff0c;从而显著提高系统的整体性…

three.js 箭头ArrowHelper的实践应用

效果&#xff1a; 代码&#xff1a; <template><div><el-container><el-main><div class"box-card-left"><div id"threejs" style"border: 1px solid red"></div></div></el-main></…