Disjoint-set - Detailed algorithms and examples (minimum spanning tree problem)

First, check and set of concepts:

Disjoint-set (Union-find Sets) is a very compact and practical data structure, it is mainly used to treat a number of disjoint sets of merger. Some common uses Kruskal's algorithm are asking connected subgraph, minimum spanning tree, and seeking common ancestor (Least Common Ancestors, LCA) and the like.

When using disjoint-set, first there will be a set of dynamic set S disjoint = {S1, S2, ⋯, Sk} S = {S1, S2, ⋯, Sk}, usually is an integer that an element in the set . Each collection may contain one or more elements, and select an element in the collection as a representative. Each set contains a specific element which is not concerned with the specific choice of which element is not as a representative of the general interest. We are concerned that, for a given element, can quickly find (on behalf of) a collection of this element is located, as well as a collection of merging two elements are located, and the time complexity of these operations is a constant level.

Above to see a ballpark on the line, introduction to disjoint-set, and has a very vivid explanation, is said to be in our laboratory from generation to generation? The following excerpt from ppt.

 

Second, the analysis example:

Combined with a simple example of the code about ppt supplement. Some features that I have not written as a function, because function is relatively simple to implement and the code coupling is not high, but not written as a function convenient and easy to understand.

Title: Smooth Traffic Project: http://acm.nefu.edu.cn/JudgeOnline/problemShow.php?problem_id=210

Talk about ideas: First, open an array a, represent each path of the boss, we let the boss of his own, that every road is now not connected to other road. Each input a set of data, we find that they are the boss, the boss if they are not the same, then we let the boss to become one of the younger brother of another boss. Finally, it will form a tree structure, the tree path are connected, and not on the path in the tree is not connected, according to the array, it is a [i] is not equal to its own connection is described. Then through the array, calculating the number of a [i] = i, i.e. the answer.

Look at the code (without compression path):

#include <bits/stdc++.h>

using namespace std;
int a[1005];
int main()
{
    int n,m,i,num;
    int city1,city2;
    while(scanf("%d",&n)!=-1)
    {
        if(n==0) break;
        scanf("%d",&m);

        for(i=1;i<=n;i++) a[i]=i;    //让每条路的老大都是自己

        for(i=1;i<=m;i++)
        {
            scanf("%d %d",&city1,&city2);

            while(a[city1]!=city1)   //找第一条路的老大
                city1=a[city1];

            while(a[city2]!=city2)   //找第二条路的老大
                city2=a[city2];

            if(city1!=city2)        //此时的city1与city2已经分别是它们的老大,看它们是否相等,否则就让一个成为其中一个的小弟
                a[city1]=city2;
        }
        num=-1;    //因为连通的树的老大也等于自己
        for(i=1;i<=n;i++)
            if(a[i]==i) num++;
        printf("%d\n",num);
    }
}

while the equivalent portion of find ppt () function, if part of the equivalent join () function.

Code (path compressor):

以题中样例一为例,最后生成的树状结构如图一,我们假设再生成如图2所示的路径,然后让2和3相连,就形成如图3的样子,发现这种写法是没有进行路径压缩的。

那么如何进行路径压缩呢?一种是如ppt中的递归写法,另一种是while循环写法。

先看while循环写法:直接让两棵树归到一棵上。

#include <bits/stdc++.h>

using namespace std;

int main()
{
    int a[1005];
    int n,m;
    int city1,city2,father,tmp1,tmp2;
    while(scanf("%d",&n)!=-1)
    {
        if(n==0) break;
        scanf("%d",&m);

        for(int i=1;i<=n;i++) a[i]=i;

        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&city1,&city2);

            tmp1=city1;

            while(a[city1]!=city1)
                city1=a[city1];

            father=city1;    //也可以是city2

            city1=tmp1;

            while(a[city1]!=father)    
            {
                int tmp=a[city1];
                a[city1]=father;
                city1=tmp;
            }
            while(a[city2]!=father)
            {
                int tmp=a[city2];
                a[city2]=father;
                city2=tmp;
            }
        }
        int num=-1;
        for(int i=1;i<=n;i++) if(a[i]==i) num++;
        printf("%d\n",num);
    }

}

递归写法:分别压缩两棵树,然后让一棵树接到另一棵树下。

#include <bits/stdc++.h>

using namespace std;

int a[1005];

int findn(int x)    //递归的神奇之处,找完以后可以倒回来改变
{
    if(a[x]!=x) return a[x]=findn(a[x]);
    return a[x];
}
void join(int x,int y)
{
    int father1=findn(x);
    int father2=findn(y);
    if(father1!=father2) a[father1]=father2;
}
int main()
{
    int n,m;
    int city1,city2,father,tmp1,tmp2;
    while(scanf("%d",&n)!=-1)
    {
        if(n==0) break;
        scanf("%d",&m);

        for(int i=1;i<=n;i++) a[i]=i;

        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&city1,&city2);
            join(city1,city2);
        }
        int num=-1;
        for(int i=1;i<=n;i++) if(a[i]==i) num++;
        printf("%d\n",num);
    }

}

两种方法可以根据实际情况组合使用。

三、实战(最小生成树问题):

1. P3366 【模板】最小生成树:https://www.luogu.org/problemnew/show/P3366

最小生成树问题的概念和解题思路离散上已经讲过了,我就不重复了,我们直接看一下代码上如何利用并查集实现。

#include <bits/stdc++.h>

using namespace std;

int a[5005];

struct node{        //节点,用于记录边和权值
    int from;
    int to;
    int value;
}b[200005];

bool cmp(node x,node y)    //按权值从小到大对b进行排序
{
    return x.value<y.value;
}

int findn(int x)            //找祖先,顺便进行路径压缩
{
    if(a[x]!=x) return a[x]=findn(a[x]);
    return a[x];
}

int main()
{
    int n,m;
    int ans,cnt;    //ans用于记录答案,cnt用于计算边数
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) a[i]=i;    //初始化并查集,所有边都未连通
    for(int i=0;i<m;i++)
    {
        cin>>b[i].from>>b[i].to>>b[i].value;
    }
    sort(b,b+m,cmp);    //按权值从小到大排序
    ans=0;
    cnt=0;    
    for(int i=0;i<m;i++)
    {
        int node1=b[i].from;        //看两个点是否连通
        int node2=b[i].to;
        node1=findn(node1);
        node2=findn(node2);

        if(node1==node2) continue;  //连通了就继续,跳过这一条边

        ans+=b[i].value;            //没有连通就让它们连通,并记录权值
        a[node1]=node2;
        
        cnt++;                
        if(cnt==n-1) break;    //当边数等于节点数-1时跳出循环
    }
    printf("%d\n",ans);
}

 2. P2820 局域网:https://www.luogu.org/problemnew/show/P2820

也是最小生成树问题,不过这回记的是多余边的权值,注意不能if(cnt==n-1)然后break,因为我们要遍历完所有边。

#include <bits/stdc++.h>

using namespace std;

int a[105];

struct node{
    int from;
    int to;
    int value;
}b[2000005];

bool cmp(node x,node y)
{
    return x.value<y.value;
}

int findn(int x)
{
    if(a[x]!=x) return a[x]=findn(a[x]);
    return a[x];
}

int main()
{
    int n,k;
    int ans;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) a[i]=i;
    for(int i=0;i<k;i++) cin>>b[i].from>>b[i].to>>b[i].value;
    sort(b,b+k,cmp);
    ans=0;
    for(int i=0;i<k;i++)
    {
        int node1=b[i].from;
        int node2=b[i].to;
        node1=findn(node1);
        node2=findn(node2);
        if(node1==node2)
        {
             ans+=b[i].value;
             continue;
        }
        a[node1]=node2;
    }
    printf("%d\n",ans);
}

3. P1195 口袋的天空:https://www.luogu.org/problemnew/show/P1195

这题题目说得不清不楚的,总的意思来说就是给你n个节点,怎么让这n个节点通过边的连接分成k个树(包括单个节点)。

要想连出k棵树,就需要连n-k条边,且不能有环和平行边。

#include <bits/stdc++.h>

using namespace std;

int a[1005];

struct node{
    int from;
    int to;
    int value;
}b[10005];

bool cmp(node x,node y)
{
    return x.value<y.value;
}

int findn(int x)
{
    if(a[x]!=x) return a[x]=findn(a[x]);
    return a[x];
}

int main()
{
    int n,m,k;
    int ans,cnt;
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++) a[i]=i;
    for(int i=0;i<m;i++) cin>>b[i].from>>b[i].to>>b[i].value;
    sort(b,b+m,cmp);
    ans=cnt=0;
    for(int i=0;i<m;i++)
    {
        int node1=b[i].from;
        int node2=b[i].to;
        node1=findn(node1);
        node2=findn(node2);
        if(node1==node2) continue;    //防止环和平行边出现
        a[node1]=node2;
        ans+=b[i].value;
        if(++cnt==n-k) break;
    }
    if(cnt<n-k) printf("NO Answer\n");
    else printf("%d\n",ans);
}

4. P1547 Out of Hay:https://www.luogu.org/problemnew/show/P1547

模板题了,练练手。

#include <bits/stdc++.h>

using namespace std;

int a[2005];

struct node{
    int from;
    int to;
    int value;
}b[10005];

bool cmp(node x,node y)
{
    return x.value<y.value;
}

int findn(int x)
{
    if(a[x]!=x) return a[x]=findn(a[x]);
    return a[x];
}

int main()
{
    int n,m;
    cin>>n>>m;
    int maxn,cnt;
    for(int i=1;i<=n;i++) a[i]=i;
    for(int i=0;i<m;i++) cin>>b[i].from>>b[i].to>>b[i].value;
    sort(b,b+m,cmp);
    maxn=-1;
    cnt=0;
    for(int i=0;i<m;i++)
    {
        int node1=findn(b[i].from);
        int node2=findn(b[i].to);
        if(node1==node2) continue;
        a[node1]=node2;
        maxn=max(maxn,b[i].value);
        if(++cnt==n-1) break;
    }
    printf("%d\n",maxn);
}

四、进阶问题:待补充。。。

发布了34 篇原创文章 · 获赞 26 · 访问量 1万+

Guess you like

Origin blog.csdn.net/sinat_40471574/article/details/90722896