当前位置: 首页 > news >正文

并查集介绍和常用模板

并查集介绍和常用模板

前言:

并查集(Union-find set 也叫Disjoint Sets)是图论里面一种用来判断节点之间是否连通的数据结构,学会使用它可以处理一些跟节点连通性的问题。它有两个很重要的方法:

  • Find(x):查找x的父元素

  • Union(x,y):将x,y两个对应的集合合并到一起

接下来我们先看一个例子,看看怎么判断节点之间是否相连,怎么把两个集合合并到一起:

image-20230910223411521

先看第一个问题,怎么判断两个节点是否相连?

在上面这张图里面我们肉眼可以看到(0,1,2)是连在一起,(3,4)是连在一起,(5)是单独存在的,这也是前置客观存在的条件。

那么我们怎么判断0和3是不是连到一起了呢?思路是这样,我们把这三块看成是三个团队,(0,1,2)是一个团队,队长是0,(3,4)是一个团队,队长是3,(5)是一个团队,队长是5。判断逻辑是,两个元素的队长是相同的,就认为在一个团队里面。所以更加计算机化的语言描述是,三个集合,每个集合都有一个根节点,如果根节点是相同的,就是在一个集合里面。因此你要想到,我们需要存储每个节点对应的根节点,这样才能判断出两个节点是否在同一个集合内。

再看第二个问题,怎么将两个集合合并?

看过问题一后,你可能就已经有一些思路了,既然只要是根节点相同就是同一个集合,判断是否相连,那么我是不是合并两个集合的时候只要根节点改成同一个就行了?没错,就是这样。合并的时候就把某个集合的根节点改成另外一个集合的根节点就行了。这里引申出一个问题,应该把哪个集合的根节点修改掉?后面我们讲到按照秩来合并集合的时候会讲到,一般情况,随便用哪个集合的根节点都可以。

并查集常用模板:

1.QuickFind:

我们直接快进到代码部分,因为有了前面的例子的铺垫,再来讲代码应该就比较好理解了,说明我都放到代码的注释里面了。

public class UnionFind1 {// 首先我们创建了一个数组 用来保存每个元素的根节点public int root[];// 然后我们根据元素的数量 初始化数组,数组的下标就是节点,数组的值就是元素i的根节点,一开始每个元素的根节点都是自己public UnionFind1(int size) {root = new int[size];for (int i = 0; i < size; i++) {root[i] = i;}}// find找根节点,直接返回x对应的根节点root[x]public int find(int x) {return root[x];}// union合并两个元素x,ypublic void union(int x, int y) {// 先找到各自的根节点 一开始都是元素自己int rootX = find(x);int rootY = find(y);if (rootX != rootY) {// 因为两个根节点不一样 所以我们随便取一个元素的根节点作为新的根节点// 这里取的rootX为新的根节点,然后把原来所有根节点是rootY的都修改为rootXfor (int i = 0; i < root.length; i++) {if (root[i] == rootY) {root[i] = rootX;}}}};// 判断两个元素是否相连 其实就是判断根节点是否相同 是对find()的一种运用public boolean connected(int x, int y) {return find(x) == find(y);}public static void main(String[] args) throws Exception {// 下面的例子就更好理解了,我们先初始化并查集一共6个元素,就是上面图中的元素// 在图中的连接情况 把0,1,2连起来,3,4连起来,并用connected()测试下元素的连通性// 最后我们把元素5加入到1的集合中,测试下2和5的连通性,发现确实2和5也相连了,程序测试完成。UnionFind1 uf = new UnionFind1(6);// 0-1-2 3-4 5uf.union(0, 1);uf.union(0, 2);uf.union(3, 4);// trueSystem.out.println(uf.connected(1, 2));// falseSystem.out.println(uf.connected(1, 3));// falseSystem.out.println(uf.connected(4, 5));// 0-1-2 3-4 5uf.union(1, 5);// trueSystem.out.println(uf.connected(2, 5));}
}

这个模板为什么要叫QuickFind呢?因为这个模板的find方法比较快,是O(1)的时间复杂度,但是union方法就比较麻烦得元素都遍历一遍,复杂度是O(N)

2.QuickUnion

quickUnion主要是union的操作比较快,直接将x,y元素中任意一个元素变成另外一个元素的爸爸,操作是O(1),但是要找根节点的时候就毕竟慢,得一直往上找直到是根节点为止。

public class UnionFind2 {public int[] root;public UnionFind2(int size) {root = new int[size];for (int i = 0; i < size; i++) {root[i] = i;}}public int find(int x) {while (root[x] != x) {return root[x] = find(root[x]);}return x;}public void union(int x, int y) {int xRoot = find(x);int yRoot = find(y);if (xRoot != yRoot) {root[yRoot] = xRoot;}}public boolean connected(int x, int y) {return find(x) == find(y);}public static void main(String[] args) throws Exception {// 下面的例子就更好理解了,我们先初始化并查集一共6个元素,就是上面图中的元素// 在图中的连接情况 把0,1,2连起来,3,4连起来,并用connected()测试下元素的连通性// 最后我们把元素5加入到1的集合中,测试下2和5的连通性,发现确实2和5也相连了,程序测试完成。UnionFind2 uf = new UnionFind2(6);// 0-1-2 3-4 5uf.union(0, 1);uf.union(0, 2);uf.union(3, 4);// trueSystem.out.println(uf.connected(1, 2));// falseSystem.out.println(uf.connected(1, 3));// falseSystem.out.println(uf.connected(4, 5));// 0-1-2 3-4 5uf.union(1, 5);// trueSystem.out.println(uf.connected(2, 5));}
}

3.按秩合并的QuickUnion

public class UnionFind3 {// 在QuickUnion的基础上增加了rank数组来记录int root[];int rank[];// 初始化的时候rank默认都是1public UnionFind3(int size) {root = new int[size];rank = new int[size];for (int i = 0; i < size; i++) {root[i] = i;rank[i] = 1;}}public int find(int x) {while (x != root[x]) {x = root[x];}return x;}// 如果两个元素的秩相同就随机取一个元素为秩更大的 否则就是谁的秩大 谁就是爸爸public void union(int x, int y) {int rootX = find(x);int rootY = find(y);if (rootX != rootY) {if (rank[rootX] > rank[rootY]) {root[rootY] = rootX;} else if (rank[rootX] < rank[rootY]) {root[rootX] = rootY;} else {root[rootY] = rootX;rank[rootX] += 1;}}}public boolean connected(int x, int y) {return find(x) == find(y);}public static void main(String[] args) throws Exception {// 下面的例子就更好理解了,我们先初始化并查集一共6个元素,就是上面图中的元素// 在图中的连接情况 把0,1,2连起来,3,4连起来,并用connected()测试下元素的连通性// 最后我们把元素5加入到1的集合中,测试下2和5的连通性,发现确实2和5也相连了,程序测试完成。UnionFind3 uf = new UnionFind3(6);// 0-1-2 3-4 5uf.union(0, 1);uf.union(0, 2);uf.union(3, 4);// trueSystem.out.println(uf.connected(1, 2));// falseSystem.out.println(uf.connected(1, 3));// falseSystem.out.println(uf.connected(4, 5));// 0-1-2 3-4 5uf.union(1, 5);// trueSystem.out.println(uf.connected(2, 5));}
}

4.路径压缩的QuickUnion

public class UnionFind4 {int root[];public UnionFind4(int size) {root = new int[size];for (int i = 0; i < size; i++) {root[i] = i;}}// 优化点在这里 再查找父节点的同时会把根节点赋值给递归过程的其他元素public int find(int x) {if (x == root[x]) {return x;}return root[x] = find(root[x]);}public void union(int x, int y) {int rootX = find(x);int rootY = find(y);if (rootX != rootY) {root[rootY] = rootX;}};public boolean connected(int x, int y) {return find(x) == find(y);}public static void main(String[] args) throws Exception {// 下面的例子就更好理解了,我们先初始化并查集一共6个元素,就是上面图中的元素// 在图中的连接情况 把0,1,2连起来,3,4连起来,并用connected()测试下元素的连通性// 最后我们把元素5加入到1的集合中,测试下2和5的连通性,发现确实2和5也相连了,程序测试完成。UnionFind4 uf = new UnionFind4(6);// 0-1-2 3-4 5uf.union(0, 1);uf.union(0, 2);uf.union(3, 4);// trueSystem.out.println(uf.connected(1, 2));// falseSystem.out.println(uf.connected(1, 3));// falseSystem.out.println(uf.connected(4, 5));// 0-1-2 3-4 5uf.union(1, 5);// trueSystem.out.println(uf.connected(2, 5));}
}

5.按秩Union并且路径压缩:

这个没有太多要说的,就是3,4模板和合并,可以同时解决查询慢和防止并查集变成链表的情况。

public class UnionFind5 {private int[] root;private int[] rank;public UnionFind5(int size) {root = new int[size];rank = new int[size];for (int i = 0; i < size; i++) {root[i] = i;rank[i] = 1;}}public int find(int x) {if (x == root[x]) {return x;}return root[x] = find(root[x]);}public void union(int x, int y) {int rootX = find(x);int rootY = find(y);if (rootX != rootY) {if (rank[rootX] > rank[rootY]) {root[rootY] = rootX;} else if (rank[rootX] < rank[rootY]) {root[rootX] = rootY;} else {root[rootY] = rootX;rank[rootX] += 1;}}}public boolean connected(int x, int y) {return find(x) == find(y);}public static void main(String[] args) throws Exception {// 下面的例子就更好理解了,我们先初始化并查集一共6个元素,就是上面图中的元素// 在图中的连接情况 把0,1,2连起来,3,4连起来,并用connected()测试下元素的连通性// 最后我们把元素5加入到1的集合中,测试下2和5的连通性,发现确实2和5也相连了,程序测试完成。UnionFind5 uf = new UnionFind5(6);// 0-1-2 3-4 5uf.union(0, 1);uf.union(0, 2);uf.union(3, 4);// trueSystem.out.println(uf.connected(1, 2));// falseSystem.out.println(uf.connected(1, 3));// falseSystem.out.println(uf.connected(4, 5));// 0-1-2 3-4 5uf.union(1, 5);// trueSystem.out.println(uf.connected(2, 5));}
}

用并查集解决问题:

这里我们来看一道用并查集解决的算法题leetcode-200岛屿数量

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。示例 1:输入:grid = [["1","1","1","1","0"],["1","1","0","1","0"],["1","1","0","0","0"],["0","0","0","0","0"]
]
输出:1
示例 2:输入:grid = [["1","1","0","0","0"],["1","1","0","0","0"],["0","0","1","0","0"],["0","0","0","1","1"]
]
输出:3提示:m == grid.length
n == grid[i].length
1 <= m, n <= 300
grid[i][j] 的值为 '0' 或 '1'

这道题目可以用bfs/dfs解决,也可以用并查集,因为我们刚学习完并查集,所以就学以致用。

解题思路:

遍历整个图,将是邻近的岛屿元素全部用并查集连起来,也就是把1连起来,最后统计parent有多少个,就是有多少岛屿(不过这道题m*n比较大,遍历会超时)。当然我们也可以在union的时候判断出岛屿的数量,最后直接返回。

 class UnionFind {public int root[];// 用来统计最后有多少个岛屿int num;public UnionFind(char[][] grid) {int row = grid.length;int col = grid[0].length;root = new int[row * col];for (int r = 0; r < row; r++) {for (int c = 0; c < col; c++) {if (grid[r][c] == '1') {// 如果是岛屿的话 num就增加num++;// 将二维坐标准换为一维的root[r * col + c] = r * col + c;}}}}// 优化点在这里 再查找父节点的同时会把根节点赋值给递归过程的其他元素public int find(int x) {if (x == root[x]) {return x;}return root[x] = find(root[x]);}public void union(int x, int y) {int rootX = find(x);int rootY = find(y);if (rootX != rootY) {root[rootY] = rootX;num--;}}}public int numIslands(char[][] grid) {int row = grid.length;int col = grid[0].length;UnionFind uf = new UnionFind(grid);int sea = 0;for (int r = 0; r < row; r++) {for (int c = 0; c < col; c++) {if (grid[r][c] == '1') {// 处理完之后 赋值为另外一个值 这样下次就不会重复遍历到这个值grid[r][c] = '0';if (r - 1 >= 0 && grid[r - 1][c] == '1') {uf.union(r * col + c, (r - 1) * col + c);}if (r + 1 < row && grid[r + 1][c] == '1') {uf.union(r * col + c, (r + 1) * col + c);}if (c - 1 >= 0 && grid[r][c - 1] == '1') {uf.union(r * col + c, r * col + c - 1);}if (c + 1 < col && grid[r][c + 1] == '1') {uf.union(r * col + c, r * col + c + 1);}}}}return uf.num;}@Testpublic void test1() {Assert.assertEquals(3, numIslands(new char[][]{new char[]{'1', '1', '0', '0', '0'},new char[]{'1', '1', '0', '0', '0'},new char[]{'0', '0', '1', '0', '0'},new char[]{'0', '0', '0', '1', '1'}}));Assert.assertEquals(1, numIslands(new char[][]{new char[]{'1', '1', '1'},new char[]{'0', '1', '0'},new char[]{'1', '1', '1'},}));}

相关文章:

并查集介绍和常用模板

并查集介绍和常用模板 前言&#xff1a; 并查集&#xff08;Union-find set 也叫Disjoint Sets&#xff09;是图论里面一种用来判断节点之间是否连通的数据结构&#xff0c;学会使用它可以处理一些跟节点连通性的问题。它有两个很重要的方法&#xff1a; Find(x)&#xff1a;…...

解决deepspeed框架的bug:不保存调度器状态,模型训练重启时学习率从头开始

deepspeed存在一个bug&#xff0c;即在训练时不保存调度器状态&#xff0c;因此如果训练中断后再重新开始训练&#xff0c;调度器还是会从头开始而不是接着上一个checkpoint的调度器状态来训练。这个bug在deepspeed的github中也有其他人提出&#xff1a;https://github.com/mic…...

Linux ipc通信(消息对列)

前言&#xff1a;消息队列也是linux开发ipc机制中较为重要的一个进程间通信机制。 1.系统创建或获取消息对列 int msgget(key_t key, int mode); 创建消息队列&#xff0c;或者获取消息队列。 参数&#xff1a; key - 使用ftok()获取到的key mode - IPC_CREAT|0666 返回&…...

【计算机网络】 ARP协议和DNS协议

文章目录 数据包在传输过程中的变化过程单播组播和广播ARP协议ARP代理免费ARP路由数据转发过程DNS协议 数据包在传输过程中的变化过程 在说ARP和DNS之前&#xff0c;我们需要知道数据包在传输过程的变化过程 从图片中可以看到&#xff0c;发送方的原数据最开始是在应用层&…...

【逐步剖C++】-第一章-C++类和对象(上)

前言&#xff1a;本文主要介绍有关C入门需掌握的基础知识&#xff0c;包括但不限于以下几个方面&#xff0c;这里是文章导图&#xff1a; 本文较长&#xff0c;内容较多&#xff0c;大家可以根据需求跳转到自己感兴趣的部分&#xff0c;希望能对读者有一些帮助 那么本文也主要…...

索尼 toio™ 应用创意开发征文|探索创新的玩乐世界——索尼 toio™

导语&#xff1a; 在技术的不断进步和发展中&#xff0c;玩具也逐渐融入了智能化的潮流。索尼 toio™作为一款前沿的智能玩具&#xff0c;给孩子和成人带来了全新的游戏体验。本文将介绍索尼 toio™的特点、功能和应用场景&#xff0c;让读者了解这个令人兴奋的创新产品。 1. 了…...

企业架构LNMP学习笔记23

1、隐藏版本号&#xff1a; Nginx对外提供服务&#xff0c;为了避免被针对某个版本的漏洞进行攻击。经常做法是隐藏掉软件的版本信息&#xff0c;提供一定的安全性。 server_tokens off; https和CA&#xff1a; 1&#xff09;基于SSL CA证书的公私钥的安全性。 CA是需要生成…...

第六章 图 五、图的深度优先遍历(DFS算法)

目录 一、定义 深度优先遍历通常用于解决以下问题&#xff1a; 深度优先遍历算法具有以下优点&#xff1a; 深度优先遍历算法的一个缺点是&#xff1a; 二、代码 空间复杂度&#xff1a; 时间复杂度&#xff1a; 邻接矩阵存储&#xff1a; 邻接表存储&#xff1a; 三、…...

React 中的 useLayoutEffect 钩子函数

useLayoutEffect钩子函数的作用跟useEffect钩子函数的作用一样&#xff0c;它们的不同主要是在于&#xff1a; 1、useEffect钩子函数是异步的&#xff0c;因为此函数在执行的时候是先计算出所有的 Dom 节点的改变后再将对应的 Dom 节点渲染到屏幕上&#xff0c;然而在 useEffe…...

upload-labs1-21关文件上传通关手册

upload-labs文件上传漏洞靶场 目录 upload-labs文件上传漏洞靶场第一关pass-01&#xff1a;第二关Pass-02第三关pass-03&#xff1a;第四关pass-04&#xff1a;第五关pass-05&#xff1a;第六关pass-06&#xff1a;第七关Pass-07第八关Pass-08第九关Pass-09第十关Pass-10第十一…...

MATLAB遗传算法求解生鲜货损制冷时间窗碳排放多成本车辆路径规划问题

MATLAB遗传算法求解生鲜货损制冷时间窗碳排放多成本车辆路径规划问题实例 1、问题描述 已知配送中心和需求门店的地理位置,并且已经获得各个门店的需求量。关于送货时间的要求,门店都有规定的时间窗,对于超过规定时间窗外的配送时间会产生相应的惩罚成本。为保持生鲜农产品的…...

界面控件DevExpress .NET应用安全 Web API v23.1亮点:支持Swagger模式

DevExpress拥有.NET开发需要的所有平台控件&#xff0c;包含600多个UI控件、报表平台、DevExpress Dashboard eXpressApp 框架、适用于 Visual Studio的CodeRush等一系列辅助工具。 DevExpress 今年第一个重要版本v23.1日前已正式发布了&#xff0c;该版本拥有众多新产品和数十…...

SpringMVC之CRUD------增删改查

目录 前言 配置文件 pom.xml文件 web.xml文件 spring-context.xml spring-mvc.xml spring-MyBatis.xml jdbc.properties数据库配置文件 generatorConfig.xml log4j2日志文件 后台 PageBaen.java PageTag.java 切面类 biz层 定义一个接口 再写一个实现类 …...

微信小程序开发教学系列(4)- 抖音小程序组件开发

章节四&#xff1a;抖音小程序组件开发 在本章中&#xff0c;我们将深入探讨抖音小程序的组件开发。组件是抖音小程序中的基本构建块&#xff0c;它们负责展示数据和与用户交互。了解组件的开发方法和使用技巧是进行抖音小程序开发的重要一步。 4.1 抖音小程序的基本组件 抖…...

RabbitMQ反序列化失败:Failed to convert message

&#x1f388; 1 参考文档 RabbitMQ消费消息坑&#xff1a;failed to convert serialized Message content | jiuchengi-cnblogs &#x1f50d;2 问题描述 org.springframework.amqp.rabbit.support.ListenerExecutionFailedException: Failed to convert messageat org.sprin…...

CTFSHOW 年CTF

1.除夕 php的弱类型&#xff0c;用小数点绕过 这里后面直接加字母不行 2.初三 error_reporting(0); extract($_GET); include "flag.php"; highlight_file(__FILE__); 这里通过extract将get的参数导入为了变量 $_function($__,$___){return $__$___?$___:$__; }; …...

肖sir__设计测试用例方法之状态迁移法05_(黑盒测试)

设计测试用例方法之状态迁移法 一、状态迁移图 定义&#xff1a;通过描绘系统的状态及引起系统状态转换的事件&#xff0c;来表示系统的行为 案例&#xff1a; &#xff08;1&#xff09; 订机票案例1&#xff1a; l向航空公司打电话预定机票—>此时机票信息处于“完成”状…...

无涯教程-JavaScript - IMPRODUCT函数

描述 IMPRODUCT函数以x yi或x yj文本格式返回1到255个复数的乘积。两个复数的乘积为- $$(A BI)(C DI)(AC-BD)(A B)1 $$ 语法 IMPRODUCT (inumber1, [inumber2] ...)争论 Argument描述Required/OptionalInumber11 to 255 complex numbers to multiply.Required[inumbe…...

yapi以及gitlab的容器化部署

yapi部署&#xff1a; https://blog.csdn.net/Chimengmeng/article/details/132074922 gitlab部署 使用docker-compose.yml version: 3 services: web: image: twang2218/gitlab-ce-zh:10.5 restart: always hostname: 192.168.xx.xx environm…...

TCP、UDP 协议的区别,各自的应用场景

分析&回答 TCP 传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前&#xff0c;必须先在双方之间建立一个TCP连接&#xff0c;之后才能传输数据。TCP提供超时重发&#xff0c;丢弃重复数据&#xff0c;检验数据&#xff0c;流量控制等功能&…...

Java如何权衡是使用无序的数组还是有序的数组

在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

基于服务器使用 apt 安装、配置 Nginx

&#x1f9fe; 一、查看可安装的 Nginx 版本 首先&#xff0c;你可以运行以下命令查看可用版本&#xff1a; apt-cache madison nginx-core输出示例&#xff1a; nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)

一、OpenBCI_GUI 项目概述 &#xff08;一&#xff09;项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台&#xff0c;其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言&#xff0c;首次接触 OpenBCI 设备时&#xff0c;往…...

Rust 开发环境搭建

环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行&#xff1a; rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu ​ 2、Hello World fn main() { println…...

【Post-process】【VBA】ETABS VBA FrameObj.GetNameList and write to EXCEL

ETABS API实战:导出框架元素数据到Excel 在结构工程师的日常工作中,经常需要从ETABS模型中提取框架元素信息进行后续分析。手动复制粘贴不仅耗时,还容易出错。今天我们来用简单的VBA代码实现自动化导出。 🎯 我们要实现什么? 一键点击,就能将ETABS中所有框架元素的基…...

Python训练营-Day26-函数专题1:函数定义与参数

题目1&#xff1a;计算圆的面积 任务&#xff1a; 编写一个名为 calculate_circle_area 的函数&#xff0c;该函数接收圆的半径 radius 作为参数&#xff0c;并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求&#xff1a;函数接收一个位置参数 radi…...

AD学习(3)

1 PCB封装元素组成及简单的PCB封装创建 封装的组成部分&#xff1a; &#xff08;1&#xff09;PCB焊盘&#xff1a;表层的铜 &#xff0c;top层的铜 &#xff08;2&#xff09;管脚序号&#xff1a;用来关联原理图中的管脚的序号&#xff0c;原理图的序号需要和PCB封装一一…...

轻量级Docker管理工具Docker Switchboard

简介 什么是 Docker Switchboard &#xff1f; Docker Switchboard 是一个轻量级的 Web 应用程序&#xff0c;用于管理 Docker 容器。它提供了一个干净、用户友好的界面来启动、停止和监控主机上运行的容器&#xff0c;使其成为本地开发、家庭实验室或小型服务器设置的理想选择…...