并查集(Union Find)

并查集简介

并查集主要用于处理连接问题,如:
网络节点的连接状态
数学的集合问题

并查集是一种树状的数据结构,并查集算法也较为高效。主要由两部分Union(p, q)和Find(p)组成。

并查集算法及优化

下面就以leetcode 547. Friend Circles为例进行讲解。
547. Friend Circles 题目链接

There are N students in a class. Some of them are friends, while some are not. Their friendship is transitive in nature. For example, if A is a direct friend of B, and B is a direct friend of C, then A is an indirect friend of C. And we defined a friend circle is a group of students who are direct or indirect friends.
Given a N*N matrix M representing the friend relationship between students in the class. If M[i][j] = 1, then the ith and jth students are direct friends with each other, otherwise not. And you have to output the total number of friend circles among all the students.

Example 1:
Input:
[[1,1,0],
[1,1,0],
[0,0,1]]
Output: 2
Explanation:The 0th and 1st students are direct friends, so they are in a friend circle.
The 2nd student himself is in a friend circle. So return 2.
Example 2:
Input:
[[1,1,0],
[1,1,1],
[0,1,1]]
Output: 1
Explanation:The 0th and 1st students are direct friends, the 1st and 2nd students are direct friends,
so the 0th and 2nd students are indirect friends. All of them are in the same friend circle, so return 1.
Note:
N is in range [1,200].
M[i][i] = 1 for all students.
If M[i][j] = 1, then M[j][i] = 1.

这道题要将有直接朋友或间接朋友的人算入一个朋友圈,因此使用并查集。

未经过优化的并查集算法

直接上代码:

class Solution {
public:
int*s;

void init_union(int n)
{
    s = (int*)malloc(230 * sizeof(int));
    
    for(int i = 0;i < n;i ++)
    {
        s[i] = i;
    }
}
    
int find(int p)
{
    while(p != s[p])
        p = s[p];//不断追溯根节点
    return p;
}

//union 时间复杂度为o(n)
void change_union(int p, int q)
{
    int p1 = find(p);
    int q1 = find(q);
    if(q1 == p1)
        return ;
    s[p1] = q1;//使两个元素的根节点合并
}

 int findCircleNum(vector<vector<int>>& M)
{
    init_union(M.size());
    int cou = 0, i, j;
    for(i = 0;i < M.size();i ++)
        for(j = 0; j < M.size();j ++)
            if(i != j && M[i][j] == 1)
                change_union(i, j);

    for(i = 0;i < M.size();i ++)
    {
        if(s[i] == i)
            cou ++;
    }
    return cou;
}
};

运行结果的评价如下图:

对于题目中所给规模的数据,这样的运行时长不是非常理想,所以下面对原来的算法进行rank上的优化。

基于rank的并查集优化

进行这种优化的原因是并查集实际上为一种树状的数据结构,元素的查找速度与树的深度有关,一般情况下树的深度都会小于并查集的元素个数,因此减小树的深度(rank)可提高Union(p, q)运行速度。

优化后的代码如下:

class Solution {
public:
int*s;
int*rank1;//rank数组

void init_union(int n)
{    
    s = (int*)malloc(230 * sizeof(int));
    rank1 = (int*)malloc(230 * sizeof(int));
    for(int i = 0;i < n;i ++)
    {
        s[i] = i;
        rank1[i] = 1;//初始化时所有元素的深度都是1
    }
}

int find(int p)
{
    while(p != s[p])
        p = s[p];
    return p;
}

void change_union(int p, int q)
{
    int p1 = find(p);
    int q1 = find(q);
    if(q1 == p1)
        return ;
    if(rank1[p1] < rank1[q1])
    {
        s[p1] = q1;
    }
    else if(rank1[p1] > rank1[q1])
    {
        s[q1] = p1;
    }
    else
    {
        s[p1] = q1;
        rank1[p1] ++;//因为此时要合并的两树深度相同,所有不仅要改变根节点指向,还要将深度加1。
    }
}

 int findCircleNum(vector<vector<int>>& M)
{
    init_union(M.size());
    int cou = 0, i, j;
    for(i = 0;i < M.size();i ++)
        for(j = 0; j < M.size();j ++)
            if(i != j && M[i][j] == 1)
                change_union(i, j);

    for(i = 0;i < M.size();i ++)
    {
        if(s[i] == i)
            cou ++;
    }
    return cou;
}
};

运行结果的评价如下图:
在这里插入图片描述
本次的运行速度相比上次得到了显著提高。

用路径压缩(Path Compression)进行优化

可使用路径压缩的原因是在并查集中一个根节点可以连接多个叶子节点。

路径压缩的具体过程就是将叶子节点的根节点(parent)更改为它的根节点的根节点,即改变parent指针的指向,从而使树的层数变小。循环或递归的终止条件为找到一个节点,发现它的根节点的parent指针指向这个根节点(根节点自身)。

因为在初始化时最上面根节点的parent指针指向的是自己,所以不会有访问越界问题。

再次修改后的代码如下:

//因为只改变了find()函数,所以只挂一下find()的代码
//代码中的s[]数组相当于parent数组
int find(int p)
{
    if(p != s[p])
        s[p] = find(s[p]);
    return s[p];//采用递归算法
}

运行结果的评价如下图:
在这里插入图片描述
除了使用递归进行路径压缩之外,还可以在find()函数中用循环进行压缩,从结果来看,运行时间差不多,空间占的略多一点。

代码:

int find(int p)
{
    
    while(p != s[p])
    {
        s[p] = s[s[p]];//路径压缩的核心,使叶子节点指向根的指针指向其根节点的根节点。
        p = s[p];
    }
        p = s[p];
    return p;
   
}

结果:
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值