《并查集算法详解及实用模板》

news2024/11/28 7:53:16

《重生我要成为并查集高手🍔🍔🍔》
并查集:快速查询和快速合并, 路径压缩, 按大小,高度,秩合并。 静态数组实现

😇前言

在数据的海洋中,有一种悄然流淌的力量,它无声地将孤立的元素连结成整体,化解纷繁的复杂与混乱。如同星辰之间看似遥远,实则早已彼此相连的轨道,它用简单的规则架起了信息流动的桥梁,带领我们在无形中寻找到隐藏的联系与秩序。
本篇向您介绍并查集这种数据结构, 它具有简洁优雅的代码实现。
前置知识: 推荐去了解一下并查集的概念和两种优化, 可以看一下我之前写的数据结构并查集
编程语言:Java/C++, 分别是java8和c++11
参考:Oiki, 高水平玩家可以参考算法导论,只需要对照我写的c++,java代码即可。
书籍参考:《程序员代码面试指南》

😛引入

并查集是一系列的集合。 它是一种以简洁优雅高效著称的数据结构。

在图论中,连通分量是指图中节点彼此连通且与其他节点没有连接的部分。并查集正是通过将这些连通分量划分成不同的集合,帮助我们快速找到两个节点是否属于同一连通分量。
并查集可以来解决图论中连通分量问题。它将图论中一个复杂图划分为一个一个不相交的图。 具体特点是同一个连通分量的元素顶点是相互连通的, 但连通分量之间是不相交的。 因此, 并查集也被称为不相交集。
在这里插入图片描述
并查集是一种划分阵营的结构, 有关系的被划分到同一个集合。
并查集的两个核心api, 查询和合并。就如它的名字所说, "并查"集 。不相交集这个名字说明了集合与集合之间的状态是不相交的。 如何将集合之间联系起来, 就要用到合并操作了。 如何证明两个集合是不相交的, 就要通过查询这个api了。

以下是并查集的定义:

并查集 (不相交集) 是一种描述不相交集合的数据结构,即若一个问题涉及多个元素,它们可划归到不同集合,同属一个集合内的元素等价(,不同集合内的元素不等价。

🙄 岛屿问题

从实际的一道题出发理解并查集吧。

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。

输入:grid = [ ["1","1","1","1","0"], ["1","1","0","1","0"], ["1","1","0","0","0"], ["0","0","0","0","0"] ]
输出:1
在这里插入图片描述

在这个数据的世界里,每个元素就像是一个孤独的小岛,而并查集就像是一个连通这些小岛的桥梁。它通过一些简单的规则,帮助我们快速找出哪些岛屿是相连的,哪些是独立的。
并查集把元素当成一个个集合, 若两个集合之间是存在联系的就把它们合并在一起。例如, 本题是把1附近的1关联在一起, 因为它们属于同一个岛屿。
如何将它们关联在一起呢? 用指针或者引用,像链表一样把它们串起来。 这是链表的基本想法。

在这里插入图片描述
这种画图很像多个链表相交的情况; 或者说它是一种树结构, 最顶部的节点是根节点。
这种结构必然存在一个根节点, 这个根节点就是该集合的代表节点。 为了方便, 我们让这个根节点的指针指向它自己。

你似乎明白了如何区分并查集的两个集合了, 看给定元素的所在集合的代表元素是不是同一个

示例二

输入:grid = [ ["1","1","0","0","0"], ["1","1","0","0","0"], ["0","0","1","0","0"], ["0","0","0","1","1"] ]
输出:3
在这里插入图片描述

在这里插入图片描述
后续,我们实现完所有并查集的核心功能然后解决此题。


🤩API实现

并查集的两个核心API和其它辅助API
int find(x):查询元素x所在集合的代表元素, 代表元素又称代表元, 该集合的根节点。
bool isSameSet(x,y):查询元素x与y是否隶属同一集合。
void build(n):初始化建立并查集, 写题的时候记得初始化!!!
void union(x,y): 合并x与y所在的集合(如果它们不在同一集合)。

初始化

选用什么容器呢? 单链表,哈希表, 数组?
作为一种算法题的模板, 要做到即写即用。
将所有n个抽象的元素进行编号1,2,3,4…n 。 其中抽象的对应可以用哈希表建立对象和编号的关系, 实际解题往往会之间给编号。
因此,最终选择简单的数组。 实际工程需求并查集用哈希表实现, 可以参考我数据结构篇的用哈希表实现的内容。
初始将各元素的父级指针指向它本身, 那么并查集内部各个集合的不相交状态就处理完毕。

const int MAX = 10001;//根据题目量确定。

int father[MAX];//元素的编号->父亲(祖先)节点

void build(int n){
    //初始时, 让元素父级指针指向它本身
    for(int i=1;i<=n;i++){
        father[i] = i;
    }
}
查询操作

前面的引入部分我们提过, 区分两个集合就是找到集合之间的根(祖先)节点, 通过一个辅助函数find实现。
由于我们设定代表元素的父级指针指向它自己, 因此以i == father[i]作为循环结束标志。
find


int find(int i){
    while(i != father[i]){
        i = father[i];
    }
    return i;
}

isSameSet:查询并查集内部的两个集合是否为同一个。
isSameSet(x, y), x和y是并查集的两个元素, 我们的目的是确定两个元素所在的集合是否为同一个, 结果返回布尔值。


//实现逻辑:就是比较两个代表元素是否为同一个。
bool isSameSet(int x,int y){
    return find(x) == find(y);
}
合并操作

并查集的合并操作非常好理解, 想象链表之间如何合并的? 就是改变一下指针指向
两个原本不相交的集合=>合并为一个集合。 实现逻辑就是将一个集合代表元素的父级指针指向另外一个集合的根节点。
在这里插入图片描述

注意: 如果两个合并元素本身就在同一集合, 那么不执行合并操作。
算法流程:

  1. 查找x,y所在集合的根节点
  2. 若它们之的根节点不为同一个, 那么执行合并操作。 father[fy] = fx
void union(int x,int y){
    int fx = find(x);
    int fy = find(y);
    //若x,y不在同一集合, 那么进行合并
    if(fx != fy){
        father[fy] = fx;
    }
}

😉并查集极简版本实现

并查集的简化版本, 即压缩了代码之后, 只需要做扁平化(路径压缩)。
既然查询过程中, 我们只关心元素所在集合的代表元素(根节点), 那么father数组指向x编号节点的父级节点就显得很多余, 为什么不在查询过程中将father[x]指向这个集合的代表元素呢?

在这里插入图片描述
略微修改find函数, 改成递归写法, 递归调用将集合的根节点编号向下返回, 修改所有孩子节点的father指向, 使得满足上图的情况。 这样下次查询就不用爬那么高找根节点了。


int find(int i){
    if (i != father[i]) father[i] = find(father[i]);
    return father[i];
}

洛谷-并查集模板
注释由ChatGPT生成。
c++代码

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 10001;
int parent[MAXN];  // parent[i] 表示节点 i 的父节点
int n, m;  // n 是元素数量,m 是操作数量

// 初始化并查集
void build(int n) {
    for (int i = 1; i <= n; i++) {
        parent[i] = i;  // 每个元素的父节点初始化为它自己
    }
}

// 查找操作,带路径压缩
int find(int i) {
    if (i != parent[i]) {
        parent[i] = find(parent[i]);  // 路径压缩
    }
    return parent[i];
}

// 合并操作,将 x 和 y 合并到同一个集合中
void unionSets(int x, int y) {
    parent[find(x)] = find(y);  // 找到 x 和 y 的根节点,将 x 的根节点指向 y 的根节点
}

int main() {
    cin >> n >> m;  // 读取元素数 n 和操作数 m
    build(n);  // 初始化并查集

    int z, x, y;
    for (int i = 0; i < m; i++) {
        cin >> z >> x >> y;  // 读取操作类型和两个元素 x, y
        if (z == 1) {
            // 如果 z == 1,进行合并操作
            unionSets(x, y);
        } else if (z == 2) {
            // 如果 z == 2,查询是否在同一个集合中
            if (find(x) == find(y)) {
                cout << "Y" << endl;  // 如果 x 和 y 在同一集合,输出 Y
            } else {
                cout << "N" << endl;  // 否则输出 N
            }
        }
    }

    return 0;
}

Java代码

import java.io.*;

public class Main {
    public static int MAX = 10001;
    public static int[] father = new int[MAX];
    public static int n, m;

    static void build(int n) {
        for (int i = 1; i <= n; i++) {
            father[i] = i; // 每个元素的父节点初始化为它自己
        }
    }

    static int find(int i) {
        if (i != father[i]) {
            father[i] = find(father[i]); // 路径压缩
        }
        return father[i];
    }

    static void union(int x, int y) {
        father[find(x)] = find(y); // 合并操作
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StreamTokenizer in = new StreamTokenizer(br);
        PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
        
        while (in.nextToken() != StreamTokenizer.TT_EOF) {
            n = (int) in.nval;
            in.nextToken();
            m = (int) in.nval;
            int x, y, z;
            
            build(n);  // 初始化并查集
            
            for (int i = 0; i < m; i++) {
                in.nextToken();
                z = (int) in.nval;
                in.nextToken();
                x = (int) in.nval;
                in.nextToken();
                y = (int) in.nval;
                
                switch (z) {
                    case 1:
                        union(x, y); // 合并操作
                        break;
                    case 2:
                        // 查询操作,输出 "Y" or "N"
                        out.println(find(x) == find(y) ? "Y" : "N");
                        break;
                }
            }
        }
        
        out.flush();
        out.close();
        br.close();
    }
}

🧐按秩合并优化

事实上, 并查集只需要做到路径压缩, 或者只做到路径压缩的极简优化就可以做算法题了。
这里在上面的路径压缩补充按秩合并的优化。

什么是按秩优化呢?
按秩合并是为了减少树的高度,防止树变得过于“高”,导致查询操作变慢。通过将较小的树合并到较大的树下,可以有效减小树的高度,从而提高操作效率。

实际实现中, 可以粗略用集合的大小(即节点数量)或者集合中树的高度来代替秩。~不过大小和高度实际对于路径压缩版本都不能准确描述。~
在这里插入图片描述
一种直观的感受, 合并过程中小集合挂大集合或者较矮的树挂高的树可以减少寻找代表元素的路径长度。

说明size版本
引入一个size数组, size数组的含义是以当前编号的节点为代表元素所在集合的大小,

#include<bits/stdc++.h>
#define MAXN 10001
using namespace std;

int father[MAXN];
int size[MAXN];
int path[MAXN];

void build(int n){
  for(int i=1;i<=n;i++){
      father[i] = i;
      size[i] = 1;
  }
}

显然, 初始化时每个编号的size应该为1。

你可能好奇这个path数组的作用, path数组是充当容器实现find函数的路径压缩, 你可以仍旧保留递归的写法。

int find(int i){
   int size = 0;
   while(i != father[i]){
       path[size++] = i;
       i = father[i];
   }
   while(size > 0){
       father[path[--size]] = i;
   }
   return i;
}

合并操作要按照小集合挂大集合.
若x,y的代表元素是同一个, 那么不进行合并。这条逻辑前面提过。
如果x所在的集合, 那么y的根节点连接x; 否则反之。
size数组的更新, 若y挂x上, 那么y原先所在代表的元素fy的size成为一个无效值, 不会在用上, 现在y的代表元素是fx, 因此size[fx] += size[fy];, x所在的集合吸收y所在集合的大小。

void unionSets(int x,int y){
   int fx = find(x);
   int fy = find(y);
   if(fx != fy){
       if(size[fx]>=size[fy]){
           father[fy] = fx;
           size[fx] += size[fy];
       }else{
           father[fx] = fy;
           size[fy] += size[fx];
       }
   }
}

cpp代码完整实现

//洛谷并查集size版本: https://www.luogu.com.cn/problem/P3367
//vscode上编辑的cpp代码, 去掉注释直接提交即可。

// #include<bits/stdc++.h>
// #define MAXN 10001
// using namespace std;

// int father[MAXN];
// int size[MAXN];
// int path[MAXN];

// void build(int n){
//    for(int i=1;i<=n;i++){
//        father[i] = i;
//        size[i] = 1;
//    }
// }
// int find(int i){
//    int size = 0;
//    while(i != father[i]){
//        path[size++] = i;
//        i = father[i];
//    }
//    while(size > 0){
//        father[path[--size]] = i;
//    }
//    return i;
// }
// void unionSets(int x,int y){
//    int fx = find(x);
//    int fy = find(y);
//    if(fx != fy){
//        if(size[fx]>=size[fy]){
//            father[fy] = fx;
//            size[fx] += size[fy];
//        }else{
//            father[fx] = fy;
//            size[fy] += size[fx];
//        }
//    }
// }
// int main(void){
//    int n,m,x,y,z;
//    cin>>n>>m;
//    build(n);
//    for(int i=0;i<m;i++){
//    cin>>z>>x>>y;
//    if(z==1){
//        unionSets(x,y);
//    }else{
//        printf("%c\n",find(x)==find(y)?'Y':'N');
//    }
//    }
//    return 0;
// }

java代码实现

package UnionSet;


//洛谷并查集size版本: https://www.luogu.com.cn/problem/P3367
//去除包名,修改类名为Main就可提交通过。
import java.io.IOException;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class Code01_UnionSet {
    public static int MAXN = 10001;
    public static int[] father = new int[MAXN];
    public static int[] size = new int[MAXN];

    public static int[] stack = new int[MAXN];
    public static int n;

    public static void build(int n){
        for(int i=1;i<=n;i++){
            father[i] = i;
            size[i] = 1;
        }
    }

    public static int find(int i){
        int size = 0;
        while(i != father[i]){
            stack[size++] = i;
            i = father[i];
        }
        while(size > 0){
            father[stack[--size]] = i;
        }
        return i;
    }
    public static boolean isSameSet(int x,int y){
        return find(x) == find(y);
    }

    public static void union(int x,int y){
        int fx = find(x);
        int fy = find(y);
        if(fx != fy){
            if(size[fx]>=size[fy]){
                father[fy] = fx;
                size[fx] += size[fy];
            }else{
                father[fx] = fy;
                size[fy] += size[fx];
            }
        }
    }

    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StreamTokenizer in = new StreamTokenizer(br);
        PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
        while(in.nextToken() != StreamTokenizer.TT_EOF){
            n = (int)in.nval;
            build(n);
            in.nextToken();
            int m = (int)in.nval;
            int z,x,y;
            for(int i=0;i<m;i++){
                in.nextToken();
                z = (int)in.nval;
                in.nextToken();
                x = (int)in.nval;
                in.nextToken();
                y = (int)in.nval;
                switch(z){
                    case 1:
                        union(x,y);
                        break;
                    case 2:
                        out.println(isSameSet(x,y)?"Y":"N");
                }
            }
        }
        out.flush();
        out.close();
        br.close();
    }
}

height数组优化
高度小的挂高度大的集合, 似乎比按大小挂更加地精确。
修改步骤很简单
将size数组用height数组替代。
初始化时,每个元素的集合高度均为1。
然后是合并操作

  • 如果两个集合的高度不同,则将较小高度的树挂到较大高度的树下。
  • 如果两个集合的高度相同,则任选一个作为根,并将其高度加 1。

Java代码实现:差别部分已用注释区分

package UnionSet;


//洛谷并查集height版本: https://www.luogu.com.cn/problem/P3367
//去除包名,修改类名为Main就可提交通过。
import java.io.IOException;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class Code02_UnionSet {
    public static int MAXN = 10001;
    public static int[] father = new int[MAXN];
    //这里是height数组
    public static int[] height = new int[MAXN];

    public static int[] stack = new int[MAXN];
    public static int n;

    public static void build(int n){
        for(int i=1;i<=n;i++){
            father[i] = i;
            height[i] = 1;//初始化高度为1
        }
    }

    public static int find(int i){
        int size = 0;
        while(i != father[i]){
            stack[size++] = i;
            i = father[i];
        }
        while(size > 0){
            father[stack[--size]] = i;
        }
        return i;
    }
    public static boolean isSameSet(int x,int y){
        return find(x) == find(y);
    }

   public static void union(int x, int y) {
    int fx = find(x);  // 找到 x 的根
    int fy = find(y);  // 找到 y 的根
    if (fx != fy) {
        if (height[fx] > height[fy]) {
            father[fy] = fx;  // 将 y 的根挂到 x 的根下
        } else if (height[fx] < height[fy]) {
            father[fx] = fy;  // 将 x 的根挂到 y 的根下
        } else {
            father[fy] = fx;  // 任选一个作为根
            height[fx]++;  // 如果高度相同,则增加新的根的高度
        }
    }
}


    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StreamTokenizer in = new StreamTokenizer(br);
        PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
        while(in.nextToken() != StreamTokenizer.TT_EOF){
            n = (int)in.nval;
            build(n);
            in.nextToken();
            int m = (int)in.nval;
            int z,x,y;
            for(int i=0;i<m;i++){
                in.nextToken();
                z = (int)in.nval;
                in.nextToken();
                x = (int)in.nval;
                in.nextToken();
                y = (int)in.nval;
                switch(z){
                    case 1:
                        union(x,y);
                        break;
                    case 2:
                        out.println(isSameSet(x,y)?"Y":"N");
                }
            }
        }
        out.flush();
        out.close();
        br.close();
    }
}

cpp版本

//洛谷并查集height版本: https://www.luogu.com.cn/problem/P3367
//vscode上编辑的cpp代码, 去掉注释直接提交即可。
#include<bits/stdc++.h>
#define MAXN 10001
using namespace std;

int father[MAXN];
int height[MAXN];
int path[MAXN];

void build(int n){
    for(int i=1;i<=n;i++){
        father[i] = i;
        height[i] = 1;
    }
}
int find(int i){
    int size = 0;
    while(i != father[i]){
        path[size++] = i;
        i = father[i];
    }
    while(size > 0){
        father[path[--size]] = i;
    }
    return i;
}
void unionSets(int x,int y){
    int fx = find(x);
    int fy = find(y);
    if(fx != fy){
        if(height[fx]>height[fy]){
            father[fy] = fx;
        }else if(height[fy]>height[fx]){
            father[fx] = fy;
        }else{
            father[fy] = fx;
            height[fx]++;
        }
    }
}
int main(void){
    int n,m,x,y,z;
    cin>>n>>m;
    build(n);
    for(int i=0;i<m;i++){
    cin>>z>>x>>y;
    if(z==1){
        unionSets(x,y);
    }else{
        printf("%c\n",find(x)==find(y)?'Y':'N');
    }
    }
    return 0;
}

最后就是按秩优化。
秩并不直接表示树中节点的数量或深度,而是一个 估计树高度 或 树的“复杂度” 的值。
无论前面的size还是height都是树的具体某些属性。
用秩写出来的代码和高度height写出的代码一模一样, 但为什么起一个新名字秩呢?
因为路径压缩过程中每个元素的实际高度都有可能下降,但height中并没有维护这些更新, 导致实际的height在经过find后不能反映真实的高度。 因此, 引入秩的概念作为树近似高度的替代。

那么实际代码的修改, 就是对于上面height数组将其重命名为rank(秩)。
秩就是集合的近似高度或者近似深度

  • 找到 x 和 y 的根节点(使用 find(x) 和 find(y)),如果它们不在同一集合,就进行合并。
  • 如果 x 和 y 的秩值不同,将秩较小的树挂到秩较大的树下。
  • 如果 x 和 y 的秩值相同,任选一个作为根并将其秩加 1,树的高度只会增加 1。

😤时间复杂度

直接给结论, 如果不满足结论可以自行去看算法导论中的推导。
如果实现按秩优化和路径压缩, 那么并查集的api时间复杂度均为 O ( 1 ) O(1) O(1)
如果只实现路径压缩, api时间复杂度最坏也才 O ( l o g n ) O(logn) O(logn).
如果只实现按秩优化,api时间复杂度最后是 O ( l o g n ) O(logn) O(logn).
如果不优化,那么最坏是线性时间。

实际实现可以掌握路径优化的极简写法。 最坏情况是对数时间的复杂度足以应付算法题了。

😆总结

通过这篇文章,我们了解了并查集这一高效的数据结构及其在图论中的应用。在算法竞赛中,我们只需要用静态数组和路径压缩就可以完全应付所有并查集相关的题目了。
有关具体并查集应用解题部分还有未完成的岛屿问题, 下篇再说。

再见!~
在这里插入图片描述

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

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

相关文章

群聊前选择患者功能的实现

和普通群聊不同&#xff0c;开启一个图文会话聊天&#xff0c;必须先选择患者、团队、医生。 原来是集成到腾讯IM当中&#xff0c;现在需要单独写一个页面 原来的代码在这里&#xff1a; const handleShow () > {uni.navigateTo({url: /pageB/active-home/active-home})}…

基于边缘智能网关的机房安全监测应用

随着我国工业互联网的扎实推进&#xff0c;越来越多地区积极建设信息基础设施&#xff0c;以充沛算力支撑产业物联网的可持续发展&#xff0c;数据机房就是其中的典型代表。而且随着机房规模的扩大&#xff0c;对于机房的安全管理难题挑战也日益增加。 面向数据机房安全监测与管…

unity 使用UI上的数字按钮,给text添加数字,并且显示光标,删除光标前数字,

今天有个需求&#xff0c;输入身份证&#xff0c;但是不用键盘&#xff0c;要点击按钮输入数字&#xff0c;并且可以控制光标&#xff0c; 1、数字按钮&#xff1a;点击后text添加数字内容 2、删除按钮&#xff1a;删除光标前的一个字符 3、左箭头&#xff1a;移动光标向左移动…

C++设计模式(单例模式)

一、介绍 1.动机 在软件系统中&#xff0c;经常有这样一些特殊的类&#xff0c;必须保证它们在系统中只存在一个实例&#xff0c;才能确保它们的逻辑正确性、以及良好的效率。 如何绕过常规的构造器&#xff0c;提供一种机制来保证一个类只有一个实例? 这应该是类设计者的…

Could not locate device support files.

报错信息&#xff1a;Failure Reason: The device may be running a version of iOS (13.6.1 17G80) that is not supported by this version of Xcode.[missing string: 869a8e318f07f3e2f42e11d435502286094f76de] 问题&#xff1a;xcode15升级到xcode16之后&#xff0c;13.…

【Webgl_glslThreejs】制作流水效果/毛玻璃效果材质

效果预览 shadertory源码 source&#xff1a; https://www.shadertoy.com/view/lldyDs 材质代码 import { DoubleSide, ShaderChunk, ShaderMaterial, TextureLoader } from "three"; /** * * source https://www.shadertoy.com/view/lldyDs */export default fu…

海康VsionMaster学习笔记(学习工具+思路)

一、前言 VisionMaster算法平台集成机器视觉多种算法组件&#xff0c;适用多种应用场景&#xff0c;可快速组合算法&#xff0c;实现对工件或被测物的查找测量与缺陷检测等。VM算法平台依托海康威视在图像领域多年的技术积淀&#xff0c;自带强大的视觉分析工具库&#xff0c;可…

XML JSON

XML 与 JSON 结构 XML&#xff08;eXtensible Markup Language&#xff09; 1. 定义 XML 是一种标记语言&#xff0c;用于描述数据的结构和内容。主要用于数据存储与交换。 2. 特点 可扩展性&#xff1a;用户可以自定义标签。层次化结构&#xff1a;数据以树形结构组织&…

[VSCode] vscode下载安装及安装中文插件详解(附下载文件)

前言 vscode 链接&#xff1a;https://pan.quark.cn/s/3acbb8aed758 提取码&#xff1a;dSyt VSCode 是一款由微软开发且跨平台的免费源代码编辑器&#xff1b;该软件支持语法高亮、代码自动补全、代码重构、查看定义功能&#xff0c;并且内置了命令行工具和Git版本控制系统。 …

wireshark基础

免责声明&#xff1a; 笔记的只是方便各位师傅学习知识&#xff0c;以下代码、网站只涉及学习内容&#xff0c;其他的都与本人无关&#xff0c;切莫逾越法律红线&#xff0c;否则后果自负。 泷羽sec官网&#xff1a;https://longyusec.com/ 泷羽sec B站地址&#xff1a;https:/…

李宏毅LLM探索(1)

1引入 1.1 提问&#xff1a;请列出你能做的事情&#xff0c;至少三十项&#xff0c;每一项都简单扼要:然后把你能做的事情制成文字云 文心一言生成&#xff1a; 以下是我能做的至少三十项事情&#xff0c;每一项都简单扼要地列出&#xff1a;回答问题 提供信息 生成文本 理解…

磁盘文件系统问题排查

1. ext4磁盘结构 块组&#xff1a;超级块&#xff1a;块位图&#xff1a;inode位图&#xff1a;inode表&#xff1a;空闲inode表&#xff1a;空闲块表&#xff1a;2. 块组结构 Group 0: (Blocks 0-32767) csum 0xfd42 [ITABLE_ZEROED]Primary superblock at 0, Group descript…

百度雪花算法id默认配置过期注意更新配置

百度雪花id项目地址&#xff1a;GitHub - baidu/uid-generator: UniqueID generator 默认配置根据redme看容易看迷糊&#xff0c;图和配置它压根就不是对应的 默认的配置如下 <!-- Specified bits & epoch as your demand. No specified the default value will be us…

(11)(2.2) BLHeli32 and BLHeli_S ESCs(二)

文章目录 前言 1 传递支持 前言 BLHeli 固件和配置应用程序的开发是为了允许配置 ESC 并提供额外功能。带有此固件的 ESC 允许配置定时、电机方向、LED、电机驱动频率等。在尝试使用 BLHeli 之前&#xff0c;请按照 DShot 设置说明进行操作(DShot setup instructions)。 1 传…

【初阶数据结构和算法】初识树与二叉树的概念以及堆和完全二叉树之间的关系

文章目录 一、树的概念与结构1.树的概念2.树的相关术语3.树的表示4.树形结构实际运用举例 二、二叉树的概念及特殊二叉树1.二叉树的概念2.特殊的二叉树满二叉树完全二叉树二叉树的性质(由满二叉树特点推导) 三、二叉树的存储结构1.二叉树的顺序结构2.二叉树的链式结构 四、堆和…

如何在Canvas中添加背景图片、图片元素和文字元素

Canvas是HTML5中一个强大的元素&#xff0c;它允许我们在网页上进行图形绘制。在本文中&#xff0c;我们将学习如何在Canvas中添加背景图片、图片元素以及文字元素。 创建Canvas元素 首先&#xff0c;我们需要在HTML文档中创建一个<canvas>元素。以下是创建一个500x500像…

单点登录深入详解之设计方案总结

基于cookie的单点登录解决方案 概述 用户登录之后 , 将认证信息存储至 Cookie &#xff0c;当再次访问本服务或者访问其他应用服务时&#xff0c;直接从 Cookie 中传递认证信息&#xff0c;进行鉴权处理。 问题 1. 如何保障Cookie内用户认证信息的安全性? 第一, Cookie…

深入探讨 Redis 持久化机制:原理、配置与优化策略

文章目录 一、引言二、Redis持久化概述三、RDB&#xff08;Redis DataBase&#xff09;持久化1、RDB概念与工作原理2、RDB的配置选项3、RDB优化配置项4、RDB的优势与劣势 三、AOF&#xff08;Append-Only File&#xff09;持久化1、AOF概念与工作原理2、AOF的三种写回策略3、Re…

Java图书管理系统(简易保姆级)

前面学习了这么多知识&#xff0c;为了巩固之前的知识&#xff0c;我们就要写一个图书管理系统来帮助大家复习&#xff0c;让大家的知识融会贯通~~~ 话不多说&#xff0c;直接开始今天的内容~ 首先呢&#xff0c;我们要有一个大体的思路&#xff1a; 实现效果思路有两种情况&a…

网络安全在现代企业中的重要作用

网络安全是这个数字时代最令人担忧的事情之一。对技术的依赖性越来越强&#xff0c;使其同时面临多种网络威胁。其声誉和法律后果的大幅下降可能归因于一次妥协。 这使得良好的网络安全成为所有企业的选择和必需品。本文介绍了网络安全的重要性、企业中常见的网络威胁以及公司…