LeetCode 1584. 连接所有点的最小费用【最小生成树】

news2024/11/23 15:18:40

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。

为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。

由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。

给你一个 points 数组,表示 2D 平面上的一些点,其中 points[i] = [xi, yi]

连接点 [xi, yi] 和点 [xj, yj] 的费用为它们之间的 曼哈顿距离 :|xi - xj| + |yi - yj| ,其中 |val| 表示 val 的绝对值。

请你返回将所有点连接的最小总费用。只有任意两点之间 有且仅有 一条简单路径时,才认为所有点都已连接。

示例 1:

输入:points = [[0,0],[2,2],[3,10],[5,2],[7,0]]
输出:20
解释:
我们可以按照上图所示连接所有点得到最小总费用,总费用为 20 。
注意到任意两个点之间只有唯一一条路径互相到达。


示例 2:

输入:points = [[3,12],[-2,5],[-4,1]]
输出:18

示例 3:

输入:points = [[0,0],[1,1],[1,0],[-1,1]]
输出:4

示例 4:

输入:points = [[-1000000,-1000000],[1000000,1000000]]
输出:4000000

示例 5:

输入:points = [[0,0]]
输出:0

提示:

  • 1 <= points.length <= 1000
    • -10^6 <= xi, yi <= 10^6
  • 所有点 (xi, yi) 两两不同。

根据题意,我们得到了一张 O ( n ) O(n) O(n) 个节点的完全图,任意两点之间的距离均为它们的曼哈顿距离。现在我们需要在这个图中取得一个子图,恰满足子图的任意两点之间有且仅有一条简单路径,且这个子图的所有边的总权值之和尽可能小。

能够满足任意两点之间有且仅有一条简单路径只有树,且这棵树包含 O ( n ) O(n) O(n) 个节点。我们称这棵树为给定的图的生成树,其中总权值最小的生成树,我们称其为最小生成树。

解法 Kruskal算法

最小生成树有一个非常经典的解法: Kruskal \text{Kruskal} Kruskal Kruskal \text{Kruskal} Kruskal 算法是一种常见并且好写的最小生成树算法,由 Kruskal \text{Kruskal} Kruskal 发明。该算法的基本思想是从小到大加入边,是一个贪心算法。

其算法流程为:

  1. 将图 G = { V , E } G=\{V,E\} G={V,E} 中的所有边按照长度由小到大进行排序,等长的边可以按任意顺序。
  2. 初始化图 G ′ G' G { V , ∅ } \{V, \varnothing \} {V,} ,从前向后扫描排序后的边,如果扫描到的边 e e e G ′ G' G 中连接了两个相异的连通块,则将它插入 G ′ G' G 中。
  3. 最后得到的图 G ′ G' G 就是图 G G G 的最小生成树。

在实际代码中,我们首先将这张完全图中的边全部提取到边集数组中(使用 struct 而非 vector<int> 存储每一条边,不然会超时,vector 的创建也需要一定时间),然后对所有边进行排序,从小到大进行枚举当有了 n − 1 n - 1 n1 条边后可提前退出对边集数组的扫描),每次贪心选边加入答案。使用并查集维护连通性,若当前边两端不连通即可选择这条边。

class Solution {
public:
    struct Edge {
        int i, j, w;
    };
    int minCostConnectPoints(vector<vector<int>>& points) {
       vector<Edge> edges;
       int n = points.size();
       for (int i = 0; i < n; ++i) {
           for (int j = 0; j < n; ++j) {
               int dx = abs(points[i][0] - points[j][0]);
               int dy = abs(points[i][1] - points[j][1]);
               edges.push_back({i, j, dx + dy});
           }
       }
       sort(edges.begin(), edges.end(), [&](const auto &a, const auto &b) {
           return a.w < b.w;
       });
        int total = 0;
        vector<int> fa(n, -1);
        function<int(int)> find = [&](int x) -> int { return fa[x] < 0 ? x : fa[x] = find(fa[x]); } ;
        auto merge = [&](int rx, int ry) {
            if (fa[rx] < fa[ry]) {
                fa[rx] += fa[ry];
                fa[ry] = rx;
            } else {
                fa[ry] += fa[rx];
                fa[rx] = ry;
            }
        };
        int num = 0;
        for (int i = 0; i < edges.size(); ++i) {
            int u = edges[i].i, v = edges[i].j;
            int ru = find(u), rv = find(v);
            if (ru != rv) {
                total += edges[i].w;
                merge(ru, rv);
                ++num; // 边数到达了n-1时可以提前退出
                if (num == n - 1) break;
            }
        }
        return total;
    }
};

复杂度分析:

  • 时间复杂度: O ( n 2 log ⁡ ( n ) ) O(n^2\log(n)) O(n2log(n)) ,其中 O ( n ) O(n) O(n) 是节点数。一般 Kruskal \text{Kruskal} Kruskal O ( m log ⁡ m ) O(m\log m) O(mlogm) 的算法,但本题中 m = n 2 m=n^2 m=n2 ,因此总时间复杂度为 O ( n 2 log ⁡ ( n ) ) O(n^2\log(n)) O(n2log(n))
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2) ,其中 O ( n ) O(n) O(n) 是节点数。并查集使用 O ( n ) O(n) O(n) 的空间,边集数组需要使用 O ( n 2 ) O(n^2) O(n2) 的空间。

解法2 建图优化的 Kruskal \text{Kruskal} Kruskal

方法一中,虽然使用了 Kruskal \text{Kruskal} Kruskal 算法,但时间复杂度仍然较高,因为本题中的边数是 O ( n 2 ) O(n^2) O(n2) 的,所以我们需要想办法将减少边数。为此,我们提出几个结论:

结论一:对于图中的任意三点 A , B , C A,B,C A,B,C,假设边 A B , A C , B C AB,AC,BC AB,AC,BC A B AB AB 为最长边,那么最终答案中必然不包含边 A B AB AB

我们利用反证法证明:假设最后答案中包含 A B AB AB ,那么此时 A C AC AC B C BC BC 两边中至少有一条边是没有被选用的,我们总可以在保证连通性的情况下,将 A B AB AB 边替换为 A C AC AC B C BC BC 两边中的某一个,使最小生成树的总权值变得更小

结论二:对于下图中同属同一个区块的任意两点 B , C B,C B,C A A A 为原点,那么 B C BC BC 不可能为三边中最长边

图中任意一个区块的两分割线的夹角均为 4 5 ∘ 45^\circ 45 。我们以 P 1 P1 P1 区块为例,假设 B ( x B , y B ) , C ( x C , y C ) B(x_B,y_B),C(x_C,y_C) B(xB,yB),C(xC,yC) ,不失一般性,假设 x B + y B ≤ x C + y C x_B + y_B \leq x_C + y_C xB+yBxC+yC

因为处于 P 1 P1 P1 区域,所以有 0 ≤ x B ≤ y B 0 \leq x_B \leq y_B 0xByB 0 ≤ x C ≤ y C 0 \leq x_C \leq y_C 0xCyC 。所以 B C = ∣ x B − x C ∣ + ∣ y B − y C ∣ BC = |x_B - x_C| + |y_B - y_C| BC=xBxC+yByC 。下面我们尝试分类讨论:

  1. x B > x C , y B > y C x_B > x_C, y_B > y_C xB>xC,yB>yC ,这与 x B + y B ≤ x C + y C x_B + y_B \leq x_C + y_C xB+yBxC+yC 矛盾。
  2. x B ≤ x C , y B > y C x_B \leq x_C, y_B > y_C xBxC,yB>yC ,此时有 ∣ B C ∣ = x C − x B + y B − y C |BC| = x_C - x_B + y_B - y_C BC=xCxB+yByC ∣ A C ∣ − ∣ B C ∣ = x C + y C − x C + x B − y B + y C = x B − y B + 2 × y C |AC| - |BC| = x_C + y_C - x_C + x_B - y_B + y_C = x_B - y_B + 2 \times y_C ACBC=xC+yCxC+xByB+yC=xByB+2×yC 。由前面各种关系可得 y B > y C > x C > x B y_B > y_C > x_C > x_B yB>yC>xC>xB 。假设 ∣ A C ∣ < ∣ B C ∣ |AC| < |BC| AC<BC ,即 y B > 2 × y C + x B y_B > 2 \times y_C + x_B yB>2×yC+xB ,那么 ∣ A B ∣ = x B + y B > 2 × x B + 2 × y C |AB| = x_B + y_B > 2 \times x_B + 2 \times y_C AB=xB+yB>2×xB+2×yC ∣ A C ∣ = x C + y C < 2 × y C < ∣ A B ∣ |AC| = x_C + y_C < 2 \times y_C < |AB| AC=xC+yC<2×yC<AB 与前提矛盾,故 ∣ A C ∣ ≥ ∣ B C ∣ |AC| \geq |BC| ACBC
  3. x B > x C x_B> x_C xB>xC y B ≤ y c y_B \leq y_c yByc 。与 2 同理;
  4. x B ≤ x C x_B \leq x_C xBxC y B ≤ y C y_B \leq y_C yByC 。此时显然有 ∣ A B ∣ + ∣ B C ∣ = ∣ A C ∣ |AB| + |BC| = |AC| AB+BC=AC ,即有 ∣ A C ∣ > ∣ B C ∣ |AC| > |BC| AC>BC

综上有 ∣ A C ∣ ≥ ∣ B C ∣ |AC| \geq |BC| ACBC ,这个性质可以从 P 1 P1 P1 区域推导到其他七个区域。

结论三:假设存在一点 A A A 在原点处,那么对于图中的任意一个 4 5 ∘ 45^\circ 45 区域,我们都至多只选择其中的一个点与 A A A 相连,且该点必然为该区域中距离 A A A 最近的点。

我们首先利用反证法证明:假设最后答案中包含 A B AB AB A C AC AC,且 B B B C C C 均位于同一个 4 5 ∘ 45^\circ 45 区域中。那么由结论二可知, B C BC BC 必不为三边中的最长边。即最长边必然为 A B AB AB A C AC AC由结论一可知, A B AB AB A C AC AC 中必然有一个不包含在答案中,这与假设相悖,因此我们最多仅会选择一个点与 A A A 相连。

我们进一步思考,既然最多仅会选择一个点与 A A A 相连,且三边中的最长边不为 A A A 的对边,那么仅有距离 A A A 最近的点与 A A A 所连的边可能出现在答案中。证毕。

依据结论三我们可以知道,一个点至多连八条边,因此我们至多只需要连出 O ( n ) O(n) O(n) 条边

细节:为防止重复连边,我们对每一个点只考虑对 P 1 , P 2 , P 3 , P 4 P1,P2,P3,P4 P1,P2,P3,P4 连边的情况,假设 A A A 点坐标为 ( x , y ) (x,y) (x,y) ,对于这四个点,我们可以概括为:

  • 对于 P 1 P1 P1 区域的 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) ,有 x 1 ≥ x , y 1 − x 1 ≥ y − x x_1 \geq x, y_1 - x_1 \geq y - x x1x,y1x1yx ,其中最近点的 x 1 + y 1 x_1 + y_1 x1+y1 最小。
  • 对于 P 2 P2 P2 区域的 ( x 2 , y 2 ) (x_2,y_2) (x2,y2) ,有 y 2 ≥ y , y 2 − x 2 ≤ y − x y_2 \geq y, y_2 - x_2 \leq y - x y2y,y2x2yx ,其中最近点的 x 2 + y 2 x_2 + y_2 x2+y2 最小。
  • 对于 P 3 P3 P3 区域的 ( x 3 , y 3 ) (x_3,y_3) (x3,y3) ,有 y 3 ≤ y , y 3 + x 3 ≥ y + x y_3 \leq y, y_3 + x_3 \geq y + x y3y,y3+x3y+x ,其中最近点的 y 3 − x 3 y_3 - x_3 y3x3 最小。
  • 对于 P 4 P4 P4 区域的 ( x 4 , y 4 ) (x_4,y_4) (x4,y4) ,有 x 4 ≥ x , y 4 + x 4 ≤ y + x x_4 \geq x, y_4 + x_4 \leq y + x x4x,y4+x4y+x ​,其中最近点的 y 4 − x 4 y_4 - x_4 y4x4 最小。

这样,我们分别处理每一个区域即可,以 P 1 P1 P1 区域为例,我们先通过排序使得所有点按照横坐标从大到小排列,然后将每一个点的 y i − x i y_i - x_i yixi 信息记录,将离散化后记录在数组的下标为 y i − x i y_i - x_i yixi 的位置中,并利用树状数组维护该数组的前缀最小值。这样我们就可以动态地、单次 O ( log ⁡ n ) O(\log n) O(logn) 地计算每个点的 P 1 P1 P1 区域所选择的点

为了提升编码效率,实际代码中我们只实现了 P 1 P1 P1 区域的算法,对于其它三个区域,我们通过巧妙的坐标变化使其条件变为 P 1 P1 P1 区域,使得代码能够更加高效地复用。

class DisjointSetUnion {
private:
    vector<int> f, rank;
    int n;

public:
    DisjointSetUnion(int _n) {
        n = _n;
        rank.resize(n, 1);
        f.resize(n);
        for (int i = 0; i < n; i++) {
            f[i] = i;
        }
    }

    int find(int x) {
        return f[x] == x ? x : f[x] = find(f[x]);
    }

    int unionSet(int x, int y) {
        int fx = find(x), fy = find(y);
        if (fx == fy) {
            return false;
        }
        if (rank[fx] < rank[fy]) {
            swap(fx, fy);
        }
        rank[fx] += rank[fy];
        f[fy] = fx;
        return true;
    }
};

class BIT {
public:
    vector<int> tree, idRec;
    int n;

    BIT(int _n) {
        n = _n;
        tree.resize(n, INT_MAX);
        idRec.resize(n, -1);
    }

    int lowbit(int k) {
        return k & (-k);
    }

    void update(int pos, int val, int id) {
        while (pos > 0) {
            if (tree[pos] > val) {
                tree[pos] = val;
                idRec[pos] = id;
            }
            pos -= lowbit(pos);
        }
    }

    int query(int pos) {
        int minval = INT_MAX;
        int j = -1;
        while (pos < n) {
            if (minval > tree[pos]) {
                minval = tree[pos];
                j = idRec[pos];
            }
            pos += lowbit(pos);
        }
        return j;
    }
};

struct Edge {
    int len, x, y;
    Edge(int len, int x, int y) : len(len), x(x), y(y) {
    }
    bool operator<(const Edge& a) const {
        return len < a.len;
    }
};

struct Pos {
    int id, x, y;
    bool operator<(const Pos& a) const {
        return x == a.x ? y < a.y : x < a.x;
    }
};

class Solution {
public:
    vector<Edge> edges;
    vector<Pos> pos;

    void build(int n) {
        sort(pos.begin(), pos.end());
        vector<int> a(n), b(n);
        for (int i = 0; i < n; i++) {
            a[i] = pos[i].y - pos[i].x;
            b[i] = pos[i].y - pos[i].x;
        }
        sort(b.begin(), b.end());
        b.erase(unique(b.begin(), b.end()), b.end());
        int num = b.size();
        BIT bit(num + 1);
        for (int i = n - 1; i >= 0; i--) {
            int poss = lower_bound(b.begin(), b.end(), a[i]) - b.begin() + 1;
            int j = bit.query(poss);
            if (j != -1) {
                int dis = abs(pos[i].x - pos[j].x) + abs(pos[i].y - pos[j].y);
                edges.emplace_back(dis, pos[i].id, pos[j].id);
            }
            bit.update(poss, pos[i].x + pos[i].y, i);
        }
    }

    void solve(vector<vector<int>>& points, int n) {
        pos.resize(n);
        for (int i = 0; i < n; i++) {
            pos[i].x = points[i][0];
            pos[i].y = points[i][1];
            pos[i].id = i;
        }
        build(n);
        for (int i = 0; i < n; i++) {
            swap(pos[i].x, pos[i].y);
        }
        build(n);
        for (int i = 0; i < n; i++) {
            pos[i].x = -pos[i].x;
        }
        build(n);
        for (int i = 0; i < n; i++) {
            swap(pos[i].x, pos[i].y);
        }
        build(n);
    }

    int minCostConnectPoints(vector<vector<int>>& points) {
        int n = points.size();
        solve(points, n);

        DisjointSetUnion dsu(n);
        sort(edges.begin(), edges.end());
        int ret = 0, num = 1;
        for (auto& [len, x, y] : edges) {
            if (dsu.unionSet(x, y)) {
                ret += len;
                num++;
                if (num == n) {
                    break;
                }
            }
        }
        return ret;
    }
};

复杂度分析:

  • 时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn) ,其中 O ( n ) O(n) O(n) 是节点数。预处理建边的时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn) ,因为需要排序,以及使用树状数组维护。在只有 O ( n ) O(n) O(n) 条边的情况下, Kruskal \text{Kruskal} Kruskal 的时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn) ,因此总时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)
  • 空间复杂度: O ( n ) O(n) O(n),其中 O ( n ) O(n) O(n) 是节点数。树状数组,并查集、离散化以及边集数组都只使用 O ( n ) O(n) O(n) 的空间。
    加粗样式

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

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

相关文章

竞赛 基于机器视觉的行人口罩佩戴检测

简介 2020新冠爆发以来&#xff0c;疫情牵动着全国人民的心&#xff0c;一线医护工作者在最前线抗击疫情的同时&#xff0c;我们也可以看到很多科技行业和人工智能领域的从业者&#xff0c;也在贡献着他们的力量。近些天来&#xff0c;旷视、商汤、海康、百度都多家科技公司研…

使用电力系统稳定器 (PSS) 和静态 VAR 补偿器 (SVC) 提高瞬态稳定性(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

fastjson远程命令执行

fastjson远程代码执行 漏洞原因&#xff1a;fastjson在对json字符串反序列化的时候&#xff0c;会读取到type的内容&#xff0c;将json内容反序列化为java对象并调用这个类的setter方法。 1、搭建rmi服务 直接利用jndi-exploit工具 2、抓包改为POST。开启nc监听、发包 PO…

Python 函数的定义

视频版教程 Python3零基础7天入门实战视频教程 函数 函数是执行特定任务的一段代码&#xff0c;程序通过将一段代码定义成函数&#xff0c;并为该函数指定一个函数名&#xff0c;这样即可在需要的时候多次调用这段代码。 比如我们前面学到的range()函数&#xff0c;就是系统…

js dispatchEvent派发自定义事件

低版本IE浏览器不兼容 dispatchEvent使用 在标准浏览器提供了元素触发自定义事件的方法 element.dispatchEvent()&#xff0c;就是说&#xff0c;我们可以不用在DOM上点击按钮触发事件&#xff0c;在代码里通过 dispatchEvent&#xff08;&#xff09;就能触发事件。如下&…

【Shiro】入门概述

1.是什么 Apache Shiro 是一个功能强大且易于使用的 Java 安全(权限)框架。Shiro 可以完 成&#xff1a;认证、授权、加密、会话管理、与 Web 集成、缓存 等。借助 Shiro 您可以快速轻松 地保护任何应用程序——从最小的移动应用程序到最大的 Web 和企业应用程序。 官网&…

使用 React Native 针对 Android 进行开发

&#x1f3ac; 岸边的风&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 概述 通过安装所需工具开始使用 React Native 创建新的 React Native 项目 本指南将有助于开始使用 Windows 上的…

【操作系统】聊聊进程间通信方式

作为操作系统软件治理的核心 进程&#xff0c;那么进程间通信的方式就非常重要&#xff0c;常见的比如管道、消息队列、共享内存、信号量、信号、Socket等。本篇主要简单介绍下 我们知道每个进程都有自己独立的用户空间&#xff0c;而内核空间是共享的。 管道 ps -ef | gre…

大数据与云计算——让我们进入数字化的新纪元

当谈论大数据和云计算时&#xff0c;我们进入了一个数字化时代的新纪元。这两个领域在科技和商业领域都有着深远的影响&#xff0c;改变了我们如何处理和存储数据&#xff0c;以及如何进行计算和分析。本文将探讨大数据和云计算的基本概念&#xff0c;它们的关系以及它们在不同…

YOLO Magic - 强化YOLOv5的视觉任务框架

YOLO Magic&#x1f680; - 强化YOLOv5的视觉任务框架 YOLO Magic&#x1fa84;是一个基于Ultralytics YOLOv5 v7.0 版本的扩展&#xff0c;旨在为视觉任务提供更强大的功能和更简单的操作。它在YOLOv5的基础上引入了丰富的网络模块&#xff0c;并提供了直观易用的Web操作界面&…

mysql如何实现根据经纬度判断某一个坐标是否在一个多边形区域范围内

要根据经纬度判断一个坐标是否在一个多边形区域内&#xff0c;MySQL提供了几种函数来处理地理空间数据&#xff0c;其中包括用于处理多边形区域的函数。 1.创建一个包含多边形区域的表&#xff1a; 首先&#xff0c;创建一个表来存储多边形区域。可以使用ST_GeomFromText函数将…

Java集合之LinedList

LinedList类实现了List接口&#xff0c;他提供了&#xff08;双向的&#xff09;链表数据结构 在该链表中的每一个元素除了存储本身的内容之外还存储指向前一个元素的指针和指向后一个元素的指针&#xff0c;下图展示了一个包含三个元素的双向链表&#xff0c;每个链表都有一个…

C#使用DirectX SDK 加载.x三维模型

最近因为项目要做显示一个三维模型&#xff0c;所以研究了下如何在Winform中加载并显示三维模型。在Windows平台巨硬公司提供了DirectX SDK用于渲染图形&#xff0c;参考了几篇文章做了个demo记录下&#xff0c;以便日后温习只用。这个SDK涉及到了计算机图形学的一些基础知识&a…

听GPT 讲Istio源代码--operator

File: istio/istioctl/pkg/waypoint/waypoint.go 在Istio项目中&#xff0c;istio/istioctl/pkg/waypoint/waypoint.go文件是istioctl的源代码之一&#xff0c;用于管理Istio的路由规则。 revision变量用于指定Istio的版本号&#xff0c;例如可以值 v1.2.3。这个版本号用于检索…

openGauss学习笔记-71 openGauss 数据库管理-创建和管理普通表-删除表中数据

文章目录 openGauss学习笔记-71 openGauss 数据库管理-创建和管理普通表-删除表中数据 openGauss学习笔记-71 openGauss 数据库管理-创建和管理普通表-删除表中数据 在使用表的过程中&#xff0c;可能会需要删除已过期的数据&#xff0c;删除数据必须从表中整行的删除。 SQL不…

arcgis拓扑检查实现多个矢量数据之间消除重叠区域

目录 环境介绍&#xff1a; 操作任务&#xff1a; 步骤&#xff1a; 1、数据库和文件结构准备 2、建立拓扑规则 3、一直下一页默认参数后&#xff0c;进行拓扑检查 4、打开TP_CK_Topology&#xff0c;会自动带出拓扑要素&#xff0c;红色区域为拓扑错误的地方&#xff1…

Python 魔法方法

视频版教程 Python3零基础7天入门实战视频教程 Python的魔法方法&#xff0c;也称为特殊方法或双下划线方法&#xff0c;是一种特殊的方法&#xff0c;用于在类中实现一些特殊的功能。这些方法的名称始终以双下划线开头和结尾&#xff0c;例如__init__&#xff0c;repr&#x…

NV040D语音芯片丨助力空气净化器语音功能

空气净化器通过过滤网和电子静电等技术&#xff0c;可以清除室内空气中的有害物质&#xff0c;如灰尘、花粉、细菌、甲醛等&#xff0c;达到净化空气的目标&#xff0c;让人们呼吸到更加清新的空气&#xff0c;保护人体健康。在空气净化器中加入九芯语音芯片的提醒功能&#xf…

jvm 内存模型介绍

一、类加载子系统 1、类加载的过程&#xff1a;装载、链接、初始化&#xff0c;其中&#xff0c;链接又分为验证、准备和解析 装载&#xff1a;加载class文件 验证&#xff1a;确保字节流中包含信息符合当前虚拟机要求 准备&#xff1a;分配内存&#xff0c;设置初始值 解析&a…

Nginx运维知识基础详解

一. nginx简介 1. nginx介绍 nginx是一个HTTP和反向代理服务器&#xff0c;邮件代理服务器&#xff0c;通用的TCP/UDP代理服务器。 反向代理服务器&#xff1a; 作用就是负载均衡 2. 编译安装 #!/bin/bash#新建文件夹存放nginx源码包 mkdir -p /nginx cd /nginx# 下载nginx压…