有向图中获取所有联通分量
业务需求
某个无向图如上,我们的目的是找出所有联通分量,如上图中有两个联通分量,可以看成是两张子网。
并查集(Union-Find-Set)
参考博客:
并查集——求无向图的所有连通子图_wangyibo0201的博客-CSDN博客_连通子图
时间复杂度O(S),相比原博客,笔者做出了一些定制版的改变,简化了算法步骤,性能也有所提高。
以上图为例,我们能够拿到的资源,应该是一条条具备连接关系的线,遍历所有线(无序),形成具有聚合关系的集合,规则如下:
- 一条边有两个节点,选择一条边任意一个节点编号当做这条边上两个节点的组号;
- 如果集合中已经有了节点的组号,那么选择集合中的,如果没有,则按照上面规则选择节点组号;
- 如果一条边两个节点都已经有了组号,那么就将集合中两个组号变为其中一个组号
对于上述的图,我们首先拿到一条条的线,然后扫描进行上述操作:
定义一个集合,存储节点的聚合关系。
- c1 - c2: 两个节点都没有组号,任意一个节点作为组号
{(c1,c2),(c2,c2)} - b1 - b2: 两个节点都没有组号,任意一个节点作为组号
{(c1,c2),(c2,c2),(b1,b2),(b2,b2)} - c3 - c2: c2具有组号,那么c3与c2在同组c2
{(c1,c2),(c2,c2),(b1,b2),(b2,b2),(c3,c2)} - c4 - c1: c1具有组号,那么c4与c1在同组c2
{(c1,c2),(c2,c2),(b1,b2),(b2,b2),(c3,c2)(c4,c2)} - b3 - b2: b2具有组号,那么b3与b2在同组b2
{(c1,c2),(c2,c2),(b1,b2),(b2,b2),(b3,b2),(c3,c2)(c4,c2)}
在原生并查集算法中,分步骤计算,第一次字典操作中,存在多个相同key的情况,所以无法使用map的数据结构进行计算,增加了编程实现的难度,且对于非map的数据结构,查询操作的时间复杂度并不低。
而我们这里讨论的算法模型,则可以使用map实现(更推荐各位以map为底层新写一个方便业务处理的数据结构)我们分析上面的步骤,使用map,那么三个基本操作get,put,foreach就能实现上面的所有步骤。
最终得到集合,抽象为关系图:
上面的情景过于简单,现在增加几个点,形成更复杂的情况:
同样进行遍历线时,相比上文多了两条线:
- c5 - c6:两个节点都没有出现过,任意节点当组号
{(c1,c2),(c2,c2),(b1,b2),(b2,b2),(b3,b2),(c3,c2)(c4,c2),(c5,c5),c6,c5)} - c1 - c6:c1与c6都有各自的组号,那么它们同属于一组,
取出c6的组号c5,将集合中的组号c5全部更换为c1的组号c2
{(c1,c2),(c2,c2),(b1,b2),(b2,b2),(b3,b2),(c3,c2)(c4,c2),(c5,c2),c6,c2)}
在遍历取出组号的操作时,还可以对组号进行一个记录,即c2:4个,c5:2个,b2,3个 那么我们将节点更少的组号c5改成c2, 而不是反过来,保证需要修改组号的,永远都是一个小子网,如此提高时间复杂度。
最终得到的集合,抽象为关系图:
代码实现
代码实现,有资源如下:
java实现:
package caculateTrain;
import lombok.Data;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Wyhao
* @date 2021/7/13
**/
public class UnionFindSetTest {
@Test
public void test() {
ArrayList<NetworkElement> networkElements = new ArrayList<>();
networkElements.add(new NetworkElement("1", "5", UUID.randomUUID().toString()));
networkElements.add(new NetworkElement(<