当前位置: 首页 > 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;流量控制等功能&…...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

边缘计算医疗风险自查APP开发方案

核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

练习(含atoi的模拟实现,自定义类型等练习)

一、结构体大小的计算及位段 &#xff08;结构体大小计算及位段 详解请看&#xff1a;自定义类型&#xff1a;结构体进阶-CSDN博客&#xff09; 1.在32位系统环境&#xff0c;编译选项为4字节对齐&#xff0c;那么sizeof(A)和sizeof(B)是多少&#xff1f; #pragma pack(4)st…...

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

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

Opencv中的addweighted函数

一.addweighted函数作用 addweighted&#xff08;&#xff09;是OpenCV库中用于图像处理的函数&#xff0c;主要功能是将两个输入图像&#xff08;尺寸和类型相同&#xff09;按照指定的权重进行加权叠加&#xff08;图像融合&#xff09;&#xff0c;并添加一个标量值&#x…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

汇编常见指令

汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX&#xff08;不访问内存&#xff09;XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...

C++八股 —— 单例模式

文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全&#xff08;Thread Safety&#xff09; 线程安全是指在多线程环境下&#xff0c;某个函数、类或代码片段能够被多个线程同时调用时&#xff0c;仍能保证数据的一致性和逻辑的正确性&#xf…...

python执行测试用例,allure报乱码且未成功生成报告

allure执行测试用例时显示乱码&#xff1a;‘allure’ &#xfffd;&#xfffd;&#xfffd;&#xfffd;&#xfffd;ڲ&#xfffd;&#xfffd;&#xfffd;&#xfffd;ⲿ&#xfffd;&#xfffd;&#xfffd;Ҳ&#xfffd;&#xfffd;&#xfffd;ǿ&#xfffd;&am…...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...