【经典算法】LeetCode133克隆图(Java/C/Python3实现含注释说明,中等)

简介: 【经典算法】LeetCode133克隆图(Java/C/Python3实现含注释说明,中等)

题目描述

给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。
图中的每个节点都包含它的值 val(int) 和其邻居的列表(list[Node])。
class Node {
    public int val;
    public List<Node> neighbors;
}
 
测试用例格式:
简单起见,每个节点的值都和它的索引相同。例如,第一个节点值为 1(val = 1),
第二个节点值为 2(val = 2),以此类推。该图在测试用例中使用邻接列表表示。
邻接列表 是用于表示有限图的无序列表的集合。每个列表都描述了图中节点的邻居集。
给定节点将始终是图中的第一个节点(值为 1)。你必须将 给定节点的拷贝 作为对克隆图的引用返回。

示例 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 = []

输出:[]

解释:这个图是空的,它不含任何节点。

提示:

这张图中的节点数在 [0, 100] 之间。

1 <= Node.val <= 100

每个节点值 Node.val 都是唯一的,

图中没有重复的边,也没有自环。

图是连通图,你可以从给定节点访问到所有节点。

原题:LeetCode 133

思路及实现

方式一:深度优先搜索(DFS)

思路

对于图中的每个节点,我们可以使用深度优先搜索(DFS)来遍历图,并在遍历的过程中复制节点和边。首先,我们需要创建一个与原始图相同大小的空图来存储克隆结果。然后,我们可以从任意节点开始,递归地遍历图,并在遍历过程中复制节点和边。

在遍历过程中,我们需要使用一个哈希表来存储原始节点和克隆节点之间的映射关系,以避免重复复制节点。当我们遇到一个尚未复制的节点时,我们创建它的克隆节点,并将其添加到克隆图中。然后,我们递归地复制与该节点直接相连的所有节点,并在克隆图中添加相应的边。

代码实现

Java版本
import java.util.*;  
  
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;  
    }  
}  
  
public class Solution {  
    private Map<Node, Node> visited = new HashMap<>();  
  
    public Node cloneGraph(Node node) {  
        if (node == null) {  
            return null;  
        }  
          
        // 如果已经访问过该节点,则直接返回克隆节点  
        if (visited.containsKey(node)) {  
            return visited.get(node);  
        }  
          
        // 创建克隆节点  
        Node cloneNode = new Node(node.val);  
        visited.put(node, cloneNode); // 将原始节点和克隆节点映射关系存入哈希表  
          
        // 递归复制邻居节点,并添加边到克隆图中  
        for (Node neighbor : node.neighbors) {  
            cloneNode.neighbors.add(cloneGraph(neighbor));  
        }  
          
        return cloneNode;  
    }  
}

说明:

使用了一个visited哈希表来存储原始节点和克隆节点之间的映射关系。

递归地复制邻居节点,并在克隆图中添加相应的边。

C语言版本

C语言实现相对复杂,因为C语言没有内置的数据结构如哈希表,需要自己实现图的节点和邻接表,以及节点的复制逻辑。

#include <stdio.h>  
#include <stdlib.h>  
#include <stdbool.h>  
// 定义节点结构
struct Node {
    int val;
    int numNeighbors;
    struct Node** neighbors;
};
// 深度优先搜索函数
struct Node* cloneGraphDFS(struct Node* node, struct Node** clones) {
    // 如果节点为空,则返回空
    if (node == NULL)
        return NULL;
    // 如果节点已经克隆过,则直接返回克隆节点
    if (clones[node->val] != NULL)
        return clones[node->val];
    // 创建当前节点的克隆节点
    struct Node* cloneNode = (struct Node*)malloc(sizeof(struct Node));
    cloneNode->val = node->val;
    cloneNode->numNeighbors = node->numNeighbors;
    cloneNode->neighbors = (struct Node**)malloc(cloneNode->numNeighbors * sizeof(struct Node*));
    clones[cloneNode->val] = cloneNode;
    // 遍历当前节点的邻居节点
    for (int i = 0; i < node->numNeighbors; i++) {
        // 递归克隆邻居节点并将克隆节点添加到当前节点的克隆节点的邻居列表中
        cloneNode->neighbors[i] = cloneGraphDFS(node->neighbors[i], clones);
    }
    return cloneNode;
}
// 克隆图函数
struct Node* cloneGraph(struct Node* node) {
    // 创建一个数组用于存储已克隆的节点
    struct Node* clones[101] = { NULL };
    // 调用深度优先搜索函数
    return cloneGraphDFS(node, clones);
}

说明:

使用深度优先搜索(DFS)的方式进行图的克隆。通过递归地遍历节点以及节点的邻居节点,创建克隆节点,并将克隆节点的邻居节点也进行克隆,并添加到克隆节点的邻居列表中。使用clones数组来存储已克隆的节点,避免重复克隆。

通过该C语言版本实现的深度优先搜索,在图较大时可能会导致栈溢出。如果需要处理大型图,可以考虑使用循环和栈来替代递归,以减少栈空间的消耗。

Python3版本
class Solution:
    def cloneGraph(self, node: 'Node') -> 'Node':
        if not node:
            return None
        
        visited = {}
        
        def clone(node):
            if node in visited:
                return visited[node]
            
            # 克隆当前节点
            clone_node = Node(node.val)
            visited[node] = clone_node
            
            # 遍历当前节点的邻居节点,并进行克隆
            for neighbor in node.neighbors:
                clone_node.neighbors.append(clone(neighbor))
            
            return clone_node
        
        return clone(node)

说明:

使用了一个visited字典来存储原始节点和克隆节点之间的映射关系。

递归地复制邻居节点,并在克隆图中添加相应的边。

复杂度分析

  • 时间复杂度:O(N+E),其中 N 是节点的个数,E 是边的个数。
  • 空间复杂度:O(N),使用哈希表来存储已克隆的节点。

方式二:(可选)广度优先搜索(BFS)

思路

使用广度优先搜索(BFS)遍历图,并在遍历过程中进行节点的克隆。为了避免重复克隆已经访问过的节点,可以使用一个哈希表来存储已经克隆的节点。具体实现时,可以使用一个队列来辅助进行广度优先搜索,并通过队列来顺序访问节点的邻居节点并进行克隆。

代码实现

Java版本
class Solution {
    public Node cloneGraph(Node node) {
        if (node == null)
            return null;
        // 创建HashMap用于存储已克隆的节点
        Map<Node, Node> visited = new HashMap<>();
        // 创建队列用于辅助广度优先搜索
        Queue<Node> queue = new LinkedList<>();
        // 初始节点克隆
        Node cloneNode = new Node(node.val, new ArrayList<>());
        visited.put(node, cloneNode);
        queue.offer(node);
        while (!queue.isEmpty()) {
            Node currNode = queue.poll();
            // 遍历当前节点的邻居节点
            for (Node neighbor : currNode.neighbors) {
                if (!visited.containsKey(neighbor)) {
                    // 如果邻居节点未克隆过,则进行克隆
                    visited.put(neighbor, new Node(neighbor.val, new ArrayList<>()));
                    queue.offer(neighbor);
                }
                // 将克隆的邻居节点添加到当前节点的克隆节点的邻居列表中
                visited.get(currNode).neighbors.add(visited.get(neighbor));
            }
        }
        return cloneNode;
    }
}

说明:

利用HashMap存储已克隆的节点,以避免重复克隆。

使用队列辅助进行BFS遍历,从初始节点开始,一层一层地克隆节点并处理邻居节点。

C语言版本
/** Definition for a Node. */
struct Node {
    int val;
    int numNeighbors;
    struct Node** neighbors;
};
struct Node* cloneGraph(struct Node* node) {
    if (node == NULL)
        return NULL;
    // 创建一个HashMap用于存储已克隆的节点
    struct Node* clones[101] = { NULL };
    // 创建一个队列用于辅助广度优先搜索
    Queue* queue = createQueue();
    // 创建初始节点克隆
    struct Node* cloneNode = (struct Node*)malloc(sizeof(struct Node));
    cloneNode->val = node->val;
    cloneNode->numNeighbors = node->numNeighbors;
    cloneNode->neighbors = (struct Node**)malloc(cloneNode->numNeighbors * sizeof(struct Node*));
    clones[cloneNode->val] = cloneNode;
    // 将原始节点入队
    enqueue(queue, node);
    while (!isEmpty(queue)) {
        // 出队当前节点
        struct Node* currNode = dequeue(queue);
        // 遍历当前节点的邻居节点
        for (int i = 0; i < currNode->numNeighbors; i++) {
            // 如果邻居节点未克隆过,则进行克隆
            if (clones[currNode->neighbors[i]->val] == NULL) {
                clones[currNode->neighbors[i]->val] = (struct Node*)malloc(sizeof(struct Node));
                clones[currNode->neighbors[i]->val]->val = currNode->neighbors[i]->val;
                clones[currNode->neighbors[i]->val]->numNeighbors = currNode->neighbors[i]->numNeighbors;
                clones[currNode->neighbors[i]->val]->neighbors = (struct Node**)malloc(clones[currNode->neighbors[i]->val]->numNeighbors * sizeof(struct Node*));
                enqueue(queue, currNode->neighbors[i]);
            }
            // 将克隆的邻居节点添加到当前节点的克隆节点的邻居列表中
            cloneNode->neighbors[i] = clones[currNode->neighbors[i]->val];
        }
    }
    return cloneNode;
}

说明

利用clones数组存储已克隆的节点,下标为节点值。

使用自定义的队列数据结构辅助进行BFS遍历。

Python3版本
class Solution:
    def cloneGraph(self, node: 'Node') -> 'Node':
        if not node:
            return None
        # 创建字典用于存储已克隆的节点
        visited = {}
        # 创建队列用于辅助广度优先搜索
        queue = deque()
        # 初始节点克隆
        clone_node = Node(node.val)
        visited[node] = clone_node
        queue.append(node)
        while queue:
            curr_node = queue.popleft()
            # 遍历当前节点的邻居节点
            for neighbor in curr_node.neighbors:
                if neighbor not in visited:
                    # 如果邻居节点未克隆过,则进行克隆
                    visited[neighbor] = Node(neighbor.val)
                    queue.append(neighbor)
                # 将克隆的邻居节点添加到当前节点的克隆节点的邻居列表中
                visited[curr_node].neighbors.append(visited[neighbor])
        return clone_node

说明:

利用visited字典存储已克隆的节点,以避免重复克隆。

使用内置的deque数据结构实现队列,辅助进行BFS遍历。

复杂度分析

  • 时间复杂度:O(N + E),其中N是节点的个数,E是边的个数。
  • 空间复杂度:O(N),用于存储已克隆的节点和队列的空间。

总结

方式 优点 缺点 时间复杂度 空间复杂度
DFS 直观易理解 可能需要较多递归调用栈空间 O(V + E) O(V)
BFS 层次遍历 需要额外维护队列 O(V + E) O(V)

相似题目

相似题目 难度 链接
图的深度优先搜索 中等 LeetCode 200
图的广度优先搜索 中等 LeetCode 130
克隆含有随机指针的节点 困难 LeetCode 138

注意:上述相似题目中的链接是指向类似问题的LeetCode题目,而不是直接关联到克隆图问题的相似题目。对于克隆图这类特定问题,可能没有直接的相似题目,但它可以看作是图遍历问题的一个变种。

相关文章
|
15天前
|
机器学习/深度学习 人工智能 算法
猫狗宠物识别系统Python+TensorFlow+人工智能+深度学习+卷积网络算法
宠物识别系统使用Python和TensorFlow搭建卷积神经网络,基于37种常见猫狗数据集训练高精度模型,并保存为h5格式。通过Django框架搭建Web平台,用户上传宠物图片即可识别其名称,提供便捷的宠物识别服务。
190 55
|
1月前
|
人工智能 安全 Java
Java和Python在企业中的应用情况
Java和Python在企业中的应用情况
55 7
|
3天前
|
存储 缓存 监控
局域网屏幕监控系统中的Python数据结构与算法实现
局域网屏幕监控系统用于实时捕获和监控局域网内多台设备的屏幕内容。本文介绍了一种基于Python双端队列(Deque)实现的滑动窗口数据缓存机制,以处理连续的屏幕帧数据流。通过固定长度的窗口,高效增删数据,确保低延迟显示和存储。该算法适用于数据压缩、异常检测等场景,保证系统在高负载下稳定运行。 本文转载自:https://www.vipshare.com
91 66
|
1月前
|
搜索推荐 Python
利用Python内置函数实现的冒泡排序算法
在上述代码中,`bubble_sort` 函数接受一个列表 `arr` 作为输入。通过两层循环,外层循环控制排序的轮数,内层循环用于比较相邻的元素并进行交换。如果前一个元素大于后一个元素,就将它们交换位置。
130 67
|
1月前
|
存储 搜索推荐 Python
用 Python 实现快速排序算法。
快速排序的平均时间复杂度为$O(nlogn)$,空间复杂度为$O(logn)$。它在大多数情况下表现良好,但在某些特殊情况下可能会退化为最坏情况,时间复杂度为$O(n^2)$。你可以根据实际需求对代码进行调整和修改,或者尝试使用其他优化策略来提高快速排序的性能
123 61
|
1月前
|
算法 数据安全/隐私保护 开发者
马特赛特旋转算法:Python的随机模块背后的力量
马特赛特旋转算法是Python `random`模块的核心,由松本真和西村拓士于1997年提出。它基于线性反馈移位寄存器,具有超长周期和高维均匀性,适用于模拟、密码学等领域。Python中通过设置种子值初始化状态数组,经状态更新和输出提取生成随机数,代码简单高效。
115 63
|
25天前
|
机器学习/深度学习 人工智能 算法
【宠物识别系统】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+图像识别
宠物识别系统,本系统使用Python作为主要开发语言,基于TensorFlow搭建卷积神经网络算法,并收集了37种常见的猫狗宠物种类数据集【'阿比西尼亚猫(Abyssinian)', '孟加拉猫(Bengal)', '暹罗猫(Birman)', '孟买猫(Bombay)', '英国短毛猫(British Shorthair)', '埃及猫(Egyptian Mau)', '缅因猫(Maine Coon)', '波斯猫(Persian)', '布偶猫(Ragdoll)', '俄罗斯蓝猫(Russian Blue)', '暹罗猫(Siamese)', '斯芬克斯猫(Sphynx)', '美国斗牛犬
130 29
【宠物识别系统】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+图像识别
|
5天前
|
存储 算法 Python
文件管理系统中基于 Python 语言的二叉树查找算法探秘
在数字化时代,文件管理系统至关重要。本文探讨了二叉树查找算法在文件管理中的应用,并通过Python代码展示了其实现过程。二叉树是一种非线性数据结构,每个节点最多有两个子节点。通过文件名的字典序构建和查找二叉树,能高效地管理和检索文件。相较于顺序查找,二叉树查找每次比较可排除一半子树,极大提升了查找效率,尤其适用于海量文件管理。Python代码示例包括定义节点类、插入和查找函数,展示了如何快速定位目标文件。二叉树查找算法为文件管理系统的优化提供了有效途径。
36 5
|
5天前
|
存储 缓存 算法
探索企业文件管理软件:Python中的哈希表算法应用
企业文件管理软件依赖哈希表实现高效的数据管理和安全保障。哈希表通过键值映射,提供平均O(1)时间复杂度的快速访问,适用于海量文件处理。在Python中,字典类型基于哈希表实现,可用于管理文件元数据、缓存机制、版本控制及快速搜索等功能,极大提升工作效率和数据安全性。
34 0
|
29天前
|
Java
Java 中的注释
1. 单行注释:// 2. 多行注释:/* */ 3. 文档注释::/** **/