【面试经典 150 | 图】克隆图

写在前面

本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章,欢迎催更……

专栏内容以分析题目为主,并附带一些对于本题涉及到的数据结构等内容进行回顾与总结,文章结构大致如下,部分内容会有增删:

  • Tag:介绍本题牵涉到的知识点、数据结构;
  • 题目来源:贴上题目的链接,方便大家查找题目并完成练习;
  • 题目解读:复述题目(确保自己真的理解题目意思),并强调一些题目重点信息;
  • 解题思路:介绍一些解题思路,每种解题思路包括思路讲解、实现代码以及复杂度分析;
  • 知识回忆:针对今天介绍的题目中的重点内容、数据结构进行回顾总结。

Tag

【图】【深搜】【广搜】


题目来源

133. 克隆图


解题思路

深拷贝和浅拷贝是 C++ 内存管理中十分重要的知识,通俗的讲,浅拷贝指向同一块内存地址,而深拷贝指向的是不同的内存地址。浅拷贝之后,修改内存地址另一个对象也会被修改,而深拷贝则不会。

对于本题而言,需要对图进行深拷贝,图的深拷贝即构建一张与原图结构、节点值均一样的图,但是其中的节点指向的并不原图的节点。我们需要重新构造节点与结构。

由于题目只给了我们一个节点的引用,因此为了知道整张图的结构以及对应节点的值,我们需要从给定的节点出发,进行「图的遍历」,并在遍历的过程中完成图的深拷贝。

对于「图的遍历」可以通过深搜实现也可以通过广搜实现。

因为本题中的图是无向图,因而无论是深搜还是广搜都要避免在遍历图时陷入死循环,可以使用哈希表来避免这种现象发生,哈希表中键为已经遍历到的节点,其值为克隆的节点。

方法一:深搜

代码

/*
// Definition for a Node.
class Node {
public:
    int val;
    vector<Node*> neighbors;
    Node() {
        val = 0;
        neighbors = vector<Node*>();
    }
    Node(int _val) {
        val = _val;
        neighbors = vector<Node*>();
    }
    Node(int _val, vector<Node*> _neighbors) {
        val = _val;
        neighbors = _neighbors;
    }
};
*/

class Solution {
private:
    unordered_map<Node*, Node*> visited;
public:
    // 每次调用返回的是 node 节点的克隆节点
    Node* cloneGraph(Node* node) {
        if (!node) {
            return nullptr;
        }

        // 如果该节点已经被访问过了,直接从哈希表中取出对应的克隆节点返回
        if (visited.find(node) != visited.end()) {
            return visited[node];
        }

        // 克隆节点并记录到哈希表中
        Node* cloneNode = new Node(node->val);
        visited[node] = cloneNode;

        // 遍历该节点的邻居节点并更新克隆节点的邻居节点
        for (auto& nb : node->neighbors) {
            cloneNode->neighbors.emplace_back(cloneGraph(nb));
        }
        return cloneNode;
    }
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n) n n n 表示节点数量。深度优先搜索遍历图的过程中每个节点只会被访问一次。

空间复杂度: O ( n ) O(n) O(n)。哈希表占用的空间为 O ( n ) O(n) O(n),递归调用栈最坏的空间复杂度也为 O ( n ) O(n) O(n)

方法二:广搜

代码

class Solution {
public:
    Node* cloneGraph(Node* node) {
        if (!node) {    // 特判
            return node;
        }

        unordered_map<Node*, Node*> visited;
        queue<Node*> que;
        que.push(node);

        visited[node] = new Node(node->val);

        while (!que.empty()) {
            Node* cur = que.front(); que.pop();
            // 遍历邻居节点
            for (auto& nb : cur->neighbors) {
                // 如果没有被访问(克隆)过,就克隆并存储
                if (visited.find(nb) == visited.end()) {
                    visited[nb] = new Node(nb->val);
                    que.push(nb);
                }
                // 更新当前节点的邻居
                visited[cur]->neighbors.emplace_back(visited[nb]);
            }
        }
        return visited[node];
    }
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n) n n n 表示节点数量。深度优先搜索遍历图的过程中每个节点只会被访问一次。

空间复杂度: O ( n ) O(n) O(n)。哈希表占用的空间为 O ( n ) O(n) O(n),广搜中的队列在最坏情况下的空间复杂度也为 O ( n ) O(n) O(n)


写在最后

如果您发现文章有任何错误或者对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家有更优的时间、空间复杂度的方法,欢迎评论区交流。

最后,感谢您的阅读,如果有所收获的话可以给我点一个 👍 哦。

  • 52
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wang_nn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值