并查集的优化——路径压缩和按秩排序

本文介绍了并查集的两种优化方法:路径压缩和按秩合并。通过这两种方法,可以显著提高并查集查找根节点的效率,减少递归深度,并避免栈溢出等问题。

1.路径压缩

如果我们合并的树很深,存放父节点的数组已经嵌套了n层,按照传统的做法,我们从最下面的节点去找n节点就要寻找n次,这种做法效率很低。这时候,我们就可以引入路径压缩的概念,路径压缩就是在递归找到根节点的时候,把当前节点到根节点之间所有节点的父节点都设置为根节点。

举个例子:

经过路径压缩之后,树的形态就变成了下面的样子:

我们可以看到经过路径压缩的节点及其子树到根节点的深度减小了很多,所以在以后的操作中,查找根节点的速度会快很多

路径压缩代码:

int find_set(intx) 

 {/*带路径压缩的查找*/ 

    if(x != parent[x])  /*如果不是根节点,让父节点等于父节点的父节点*/

         parent[x] = find_set(parent[x]); 

     return parent[x];  //返回父节点的值

 }

如果看了注释代码还不是很懂的话,可以用一组数据模拟一下,加深理解,上面那种是采用递归路径压缩的方法查找元素,但是,递归压缩路径可能会造成溢出栈,会发生运行错误。

下面是非递归方式进行的路径压缩代码:

int find(int x)

{

    int k, j, r;

    r = x;

    while(r != parent[r])     //查找跟节点

        r = parent[r];      //找到根节点,用r记录下

    k = x;       

    while(k != r)             //非递归路径压缩操作

    {

        j = parent[k];         //用j暂存parent[k]的父节点

        parent[k] = r;        //parent[x]指向跟节点

        k = j;                    //k移到父节点

    }

    return r;         //返回根节点的值            

}

2.按秩合并(秩在这里可以理解为树的深度)

按秩合并是两个树合并在选取根节点的时候,选择秩比较大的作为根节点。如下图,5和7有联系:

左边树的秩比右边树的秩大,所以我们让左边的作为根节点。

按秩合并代码:(rank数组存放的是结点的秩)

/*按秩合并x,y所在的集合*/ 

 void union_set(int x, int y) 

 { 

     x = find_set(x); 

     y = find_set(y); 

     if(rank[x] > rank[y])/*让rank比较高的作为父结点*/ 

         parent[y] = x; 

     else  

     { 

         parent[x] = y; 

        if(rank[x] == rank[y])

             rank[y]++; 

     } 

 } 

为什么秩相等的时候才加1,看下图:

连接后:

 

在初始化数组的时候,也要注意rank数组的初始化!

代码:

void Init()

{

       for(int i=0;i<=n;i++)

       {

              parent[i]=i;

rank[i]=0;

       }

}

### 使用并查集解决最短路径问题 尽管并查集通常用于处理连通性最小生成树等问题,但它本身并不直接适用于计算最短路径。然而,在某些特定情况下,可以通过结合其他技术间接利用并查集来辅助解决问题。 #### 并查集与Kruskal算法的关系 Kruskal算法是一种经典的最小生成树算法,其核心思想是按照边的权重从小到大排序,并逐步加入不形成环路的边[^3]。在此过程中,可以使用并查集高效地检测两个顶点是否已经属于同一个集合(即是否存在环)。虽然Kruskal算法的目标不是找到最短路径,但在构建最小生成树的过程中,它实际上隐含了一些关于全局最优路径的信息。 #### 实现示例:借助Kruskal算法的思想模拟最短路径过程 下面是一个简单的Python代码示例,展示如何通过Kruskal算法并查集的概念来帮助理解图中的连接关系: ```python class UnionFind: def __init__(self, n): self.parent = list(range(n)) def find(self, u): if self.parent[u] != u: self.parent[u] = self.find(self.parent[u]) # 路径压缩 return self.parent[u] def union(self, u, v): pu, pv = self.find(u), self.find(v) if pu != pv: self.parent[pu] = pv def kruskal(edges, n): edges.sort(key=lambda x: x[2]) # 按照权重升序排列 uf = UnionFind(n) mst_edges = [] for edge in edges: u, v, w = edge if uf.find(u) != uf.find(v): # 如果不在同一集合,则不会成环 uf.union(u, v) mst_edges.append(edge) return mst_edges # 示例输入 n = 4 # 图中有4个节点 edges = [ (0, 1, 1), (1, 2, 2), (2, 3, 5), (0, 2, 6), ] mst_result = kruskal(edges, n) print("MST Edges:", mst_result) ``` 上述代码实现了Kruskal算法以及支持它的并查集类`UnionFind`。此程序的主要目的是找出无向加权图的最小生成树(MST),而不是严格意义上的最短路径。但是,这种做法有助于分析哪些边对于整体网络优化至关重要。 #### 解析 在这个例子中,我们并没有真正求解两节点之间的具体距离;而是展示了如何挑选一组关键性的低代价链接以覆盖整个图形——这是接近于“全局”意义下的某种形式的最佳路由方案之一。需要注意的是,这种方法并不能替代Dijkstra或Floyd-Warshall这样的专门针对最短路径设计的技术[^2]。 #### 结论 因此,单独依靠并查集无法完成传统定义上的最短路径查找任务。不过,当与其他方法相结合时,比如应用于Kruskal算法里作为工具判断新增某条边是否会引发循环现象,从而间接影响到了最终形成的拓扑结构特性上有所体现出来的一些潜在规律性特征,则可能带来新的启发视角。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值