【经典算法】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题目,而不是直接关联到克隆图问题的相似题目。对于克隆图这类特定问题,可能没有直接的相似题目,但它可以看作是图遍历问题的一个变种。

相关文章
|
1月前
|
算法 前端开发 数据处理
小白学python-深入解析一位字符判定算法
小白学python-深入解析一位字符判定算法
46 0
|
2天前
|
机器学习/深度学习 人工智能 算法
基于Python深度学习的【垃圾识别系统】实现~TensorFlow+人工智能+算法网络
垃圾识别分类系统。本系统采用Python作为主要编程语言,通过收集了5种常见的垃圾数据集('塑料', '玻璃', '纸张', '纸板', '金属'),然后基于TensorFlow搭建卷积神经网络算法模型,通过对图像数据集进行多轮迭代训练,最后得到一个识别精度较高的模型文件。然后使用Django搭建Web网页端可视化操作界面,实现用户在网页端上传一张垃圾图片识别其名称。
15 0
基于Python深度学习的【垃圾识别系统】实现~TensorFlow+人工智能+算法网络
|
2天前
|
机器学习/深度学习 人工智能 算法
【手写数字识别】Python+深度学习+机器学习+人工智能+TensorFlow+算法模型
手写数字识别系统,使用Python作为主要开发语言,基于深度学习TensorFlow框架,搭建卷积神经网络算法。并通过对数据集进行训练,最后得到一个识别精度较高的模型。并基于Flask框架,开发网页端操作平台,实现用户上传一张图片识别其名称。
11 0
【手写数字识别】Python+深度学习+机器学习+人工智能+TensorFlow+算法模型
|
2天前
|
机器学习/深度学习 人工智能 算法
基于深度学习的【蔬菜识别】系统实现~Python+人工智能+TensorFlow+算法模型
蔬菜识别系统,本系统使用Python作为主要编程语言,通过收集了8种常见的蔬菜图像数据集('土豆', '大白菜', '大葱', '莲藕', '菠菜', '西红柿', '韭菜', '黄瓜'),然后基于TensorFlow搭建卷积神经网络算法模型,通过多轮迭代训练最后得到一个识别精度较高的模型文件。在使用Django开发web网页端操作界面,实现用户上传一张蔬菜图片识别其名称。
10 0
基于深度学习的【蔬菜识别】系统实现~Python+人工智能+TensorFlow+算法模型
|
6天前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
24 2
|
16天前
|
算法 测试技术 开发者
在Python开发中,性能优化和代码审查至关重要。性能优化通过改进代码结构和算法提高程序运行速度,减少资源消耗
在Python开发中,性能优化和代码审查至关重要。性能优化通过改进代码结构和算法提高程序运行速度,减少资源消耗;代码审查通过检查源代码发现潜在问题,提高代码质量和团队协作效率。本文介绍了一些实用的技巧和工具,帮助开发者提升开发效率。
18 3
|
18天前
|
机器学习/深度学习 人工智能 算法
【车辆车型识别】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+算法模型
车辆车型识别,使用Python作为主要编程语言,通过收集多种车辆车型图像数据集,然后基于TensorFlow搭建卷积网络算法模型,并对数据集进行训练,最后得到一个识别精度较高的模型文件。再基于Django搭建web网页端操作界面,实现用户上传一张车辆图片识别其类型。
62 0
【车辆车型识别】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+算法模型
|
23天前
|
机器学习/深度学习 算法 Java
机器学习、基础算法、python常见面试题必知必答系列大全:(面试问题持续更新)
机器学习、基础算法、python常见面试题必知必答系列大全:(面试问题持续更新)
|
1月前
|
机器学习/深度学习 人工智能 算法
【玉米病害识别】Python+卷积神经网络算法+人工智能+深度学习+计算机课设项目+TensorFlow+模型训练
玉米病害识别系统,本系统使用Python作为主要开发语言,通过收集了8种常见的玉米叶部病害图片数据集('矮花叶病', '健康', '灰斑病一般', '灰斑病严重', '锈病一般', '锈病严重', '叶斑病一般', '叶斑病严重'),然后基于TensorFlow搭建卷积神经网络算法模型,通过对数据集进行多轮迭代训练,最后得到一个识别精度较高的模型文件。再使用Django搭建Web网页操作平台,实现用户上传一张玉米病害图片识别其名称。
52 0
【玉米病害识别】Python+卷积神经网络算法+人工智能+深度学习+计算机课设项目+TensorFlow+模型训练
|
1月前
|
Java Python
如何通过Java程序调用python脚本
如何通过Java程序调用python脚本
25 0