并查集与最小生成树

并查集:

查找多个元素是否属于同一集合,或将它们合并到同一集合中。

思想:

利用数组的下标和值进行连接,下标代表元素,值是该元素所属的集合。例:n[2] = 2, n[3] = 2,数字2和3属于同一集合。

题目:

hdoj:http://acm.hdu.edu.cn/showproblem.php?pid=1232

思路:

利用并查集来查询有多少个集合,每个集合间建一条路,最后答案就是集合数 - 1。

AC代码:

#include<cstdio>
#include<iostream>
#include<set>
#include<algorithm>
using namespace std;
const int M = 1000 + 10;

set<int>s;
set<int>::iterator it;
int nums[M];//建一个数组来储存数据
void inti( int a)//将每个元素的所属集合初始化为自身
{
    for(int i = 1; i <= a; i++)
        nums[i] = i;
}

int findelm(int a)//查询元素所属的集合
{
    if(nums[a] == a)
        return a;
    return nums[a] = findelm(nums[a]);
}

void mix(int a, int b)//将不是同一集合的元素合并,合并后的集合以较小的数为准。需要注意的是这样合并只是修改顶点元素,并不修改子元素,所以在计算子元素时需要再进行一次查询,来更新它们的所属集合
{
    int tmp1 = findelm(a), tmp2 = findelm(b);
    if(tmp1 > tmp2)
        nums[tmp1] = tmp2;
    else
        nums[tmp2] = tmp1;
}

main()
{
    int n, m;
    while(scanf("%d", &n) && n)
    {
        s.clear();
        scanf("%d", &m);
        inti(n);
        while(m--)
        {
            int a, b;
            scanf("%d %d", &a, &b);
            if(findelm(a) != findelm(b))
                mix(a, b);
        }
        for(int i = 1; i <= n; i++)
        {
            findelm(i);//更新所属集合
            s.insert(nums[i]);
        }
        printf("%d\n", s.size() - 1);
    }
}
最小生成树:

元素之间相连有消耗,将它们变成一种树状结构(生成树)后,消耗最少就是最小生成树。

思想(kruskal算法):

利用并查集+贪心的思想,从消耗开始升序排列,如果两点是同一集合就跳过,如果不是就将消耗累加,最后结果就是最少消耗。

题目:

hdoj:http://acm.hdu.edu.cn/showproblem.php?pid=1233

思路:同思想。

AC代码:

#include<cstdio>
#include<set>
#include<algorithm>
using namespace std;

const int M = 100 + 10;
const int S = 5000 + 10;
int nums[M];
struct Road
{
    int a, b, l;
    bool operator < (const Road &i)const//升序排列重载运算符
    {
        return l < i.l;
    }
}road[S];

void inti( int a)
{
    for(int i = 1; i <= a; i++)
        nums[i] = i;
}

int findelm(int a)
{
    if(nums[a] == a)
        return a;
    return nums[a] = findelm(nums[a]);
}

void mix(int a, int b)
{
    int tmp1 = findelm(a), tmp2 = findelm(b);
    if(tmp1 > tmp2)
        nums[tmp1] = tmp2;
    else
        nums[tmp2] = tmp1;
}

set<int>s;
set<int>::iterator it;
main()
{
    int tCase;
    while(scanf("%d", &tCase) && tCase)
    {
        inti(tCase);
        s.clear();
        int n = tCase * (tCase - 1) / 2;
        for(int i = 0; i < n; i++)
            scanf("%d %d %d", &road[i].a, &road[i].b, &road[i].l);
        sort(road, road + n);
        int res = 0;
        for(int i = 0; i < n; i++)
        {
            if(findelm(road[i].a) != findelm(road[i].b))//如果不在同一集合,就将它们合并并累加消耗
            {
                mix(road[i].a, road[i].b);
                res += road[i].l;
            }
        }
        printf("%d\n", res);
    }
}

然而,当我们遇到过大的数据或非数字数据(比如10^9, 字符串)时,我们就难以用数组来实现并查集,这就需要c++的一种stl:map,利用它的对应关系来实现并查集(感觉比开数组好用多了)。

题目:

hdoj:http://acm.hdu.edu.cn/showproblem.php?pid=1856

思路:

利用map来实现并查集,并计数,求最多的朋友数。

AC代码:

#include<cstdio>
#include<map>
#include<algorithm>
using namespace std;
const int M = 100000 + 20;
map<int, int>m;
map<int, int>::iterator it;
map<int, int>mm;
int findele(int a)
{
    if(m[a] == a)
        return a;
    else
        return m[a] = findele(m[a]);
}

void mix(int a, int b)
{
    int tmp1 = findele(a), tmp2 = findele(b);
    if(tmp1 > tmp2)
        m[tmp1] = tmp2;
    else
        m[tmp2] = tmp1;
}

main()
{
    int tCase;
    while(~scanf("%d", &tCase))
    {
        if(tCase == 0)
        {
            puts("1");
            continue;
        }
        m.clear();
        mm.clear();
        while(tCase--)
        {
            int a, b;
            scanf("%d %d", &a, &b);
            if(m.find(a) == m.end())
                m[a] = a;
            if(m.find(b) == m.end())
                m[b] = b;
            if(m[a] != m[b])
                mix(a, b);
        }
        for(it = m.begin(); it != m.end(); it++)
        {
            findele(it ->first);
            if(mm.find(it ->second) == mm.end())
                mm[it->second] = 1;
            else
                mm[it->second]++;
        }
        int maxn = 0;
        for(it = mm.begin(); it != mm.end(); it++)
        {
            if(maxn < it ->second)
                maxn = it ->second;
        }
        printf("%d\n", maxn);
    }
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值