C++ 图论之并查集

首先,我们定义一个数组,用双亲表示法来表示各棵树(所有的集合元素个数总和为N):

int Tree[N];

用Tree[i]来表示结点i的双亲结点,若Tree[i]为-1则表示该结点不存在双亲结点,即结点i为其所在树的根结点。

那么,为了查找结点x所在树的根结点,我们定义以下函数:

int findRoot(int x){
    int ret;    
    while (Tree[x]!=1){
        x=Tree[x];        // 若当前结点为非根结点则一直查找其双亲结点
    }
    ret=x;    //  返回根结点编号
    return ret;
}

另外若需要在查找过程中添加路径压缩的优化,我们修改以上两个函数为:

int findRoot(int x){
    int ret; 
    int temp=x;   
    while (Tree[x]!=1){
        x=Tree[x];       
    }
    ret=x;    
    x=temp;        // 再做一次从结点x到根结点的遍历
    while(Tree[x]!=1){
        int t=Tree[x];
        Tree[x]=ret;
        x=t;                // 遍历过程中将这些结点的双亲结点都设为已经查找得到的根结点编号
    }
    return ret;
}

有了这些函数,结合并查集的工作原理和集合合并方法,我们就可以开始试着解决并查集问题了。


应用一: 连通分量

附C++代码:

//
//  1012.cpp
//  畅通工程
//
//  Created by chenmeiqi on 2019/4/2.
//  Copyright © 2019年 chenmeiqi. All rights reserved.
//

#include <iostream>
using namespace std;
int Tree[1000];
int findRoot(int x){
    int temp=x;
    int ret;
    while (Tree[x]!=0){
        x=Tree[x];        // 若当前结点为非根结点则一直查找其双亲结点
    }
    ret=x;    //  根结点编号
    x=temp;        // 再做一次从结点x到根结点的遍历
    while(Tree[x]!=0){
        int t=Tree[x];
        Tree[x]=ret;
        x=t;                // 遍历过程中将这些结点的双亲结点都设为已经查找得到的根结点编号
    }
    return ret;
}

int main(int argc, const char * argv[]) {
    int n,m;
    int temp_m;
    int city_1,city_2;
    while (cin>>n) {
        if(n==0){
            break;
        }
        cin>>m;
        for(int i=0;i<1000;i++){        // 初始化
            Tree[i]=0;
        }
        int res=0;
        temp_m=m;
        while (temp_m--) {
            cin>>city_1>>city_2;
            city_1=findRoot(city_1);        // 找到第一个城市的根结点
            city_2=findRoot(city_2);        // 找到第二个城市的根结点
            if (city_1!=city_2) {           // 不相等就合并根结点
                Tree[city_1]=city_2;
            }
        }
        for (int j=1; j<=n; j++) {
            if (Tree[j]==0) {
                res++;
            }
        }
        res-=1;                 // 减1就是连通分量的值
        cout<<res<<endl;
    }
    return 0;
}

应用二:也是连通分量

先放一个我写的第一版C++代码:


//
//  1444.cpp
//  More is Better
//
//  Created by chenmeiqi on 2019/4/3.
//  Copyright © 2019年 chenmeiqi. All rights reserved.
//

#include <iostream>
using namespace std;
int Tree[10000000];
int res[10000000];
int findRoot(int x){
    int res;
    while (Tree[x]!=0) {
        x=Tree[x];
    }
    res=x;
    return res;
}


int cmp(int a,int b){
    return a>b;
}


int main(int argc, const char * argv[]) {
    int n;
    int temp_n;
    int boy_1,boy_2;
    int temp_boy_1;
    while (cin>>n) {
        temp_n=n;
        for (int i=0; i<10000000; i++) {
            Tree[i]=0;
            res[i]=0;
        }
        while (temp_n--) {
            cin>>boy_1>>boy_2;
            temp_boy_1=boy_1;
            boy_1=findRoot(boy_1);
            boy_2=findRoot(boy_2);
            if(boy_1!=boy_2){
                Tree[boy_1]=boy_2;
                while (Tree[temp_boy_1]!=0) {           // 更新根结点
                    int t=Tree[temp_boy_1];
                    Tree[temp_boy_1]=boy_2;
                    temp_boy_1=t;
                }
            }
        }
        for (int i=1; i<=10000000; i++) {           // 计算每个集合里的个数
            if(Tree[i]!=0){
                res[Tree[i]]++;
            }
        }
        sort(res, res+10000000,cmp);            // 排序,找到最多的那个
        cout<<res[0]+1<<endl;
    }
    return 0;
}

 不是很满意,时间复杂度过高。

写完后结合答案修正了一下:


//
//  1444.cpp
//  More is Better
//
//  Created by chenmeiqi on 2019/4/3.
//  Copyright © 2019年 chenmeiqi. All rights reserved.
//

#include <iostream>
using namespace std;
#define N 10000001                  // 后面没有分号!!
int Tree[N];
int sum[N];
int findRoot(int x){
    int res;
    while (Tree[x]!=0) {
        x=Tree[x];
    }
    res=x;
    return res;
}


int cmp(int a,int b){
    return a>b;
}


int main(int argc, const char * argv[]) {
    int n;
    int temp_n;
    int boy_1,boy_2;
    int res;
    while (cin>>n) {
        temp_n=n;
        res=1;
        for (int i=1; i<N; i++) {
            Tree[i]=0;
            sum[i]=1;               // 初始时都是1
        }
        while (temp_n--) {
            cin>>boy_1>>boy_2;
            boy_1=findRoot(boy_1);
            boy_2=findRoot(boy_2);
            if(boy_1!=boy_2){
                Tree[boy_1]=boy_2;
                sum[boy_2]+=sum[boy_1];
            }
        }
        for(int i=1;i<N;i++){
            if(Tree[i]==0 && res<sum[i]){
                res=sum[i];
            }
        }
        cout<<res<<endl;
    }
    return 0;
}

嗯,感觉简洁了很多。。


应用三:Kruskal 算法(最小生成树)

附代码:

//
//  1017.cpp
//  还是畅通工程
//
//  Created by chenmeiqi on 2019/4/8.
//  Copyright © 2019年 chenmeiqi. All rights reserved.
//

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int Tree[100];
struct Edge
{
    int begin;      // 起始顶点
    int end;        // 终点
    int weight;     // 该边权值
    bool operator < (const Edge& a) const{      // 重载 < 运算符
        return weight<a.weight;
    };
};
int findRoot(int x){            // 找根结点
    int res=0;
    while(Tree[x]!=0){
        x=Tree[x];
    }
    res=x;
    return res;
}
int main(int argc, const char * argv[]) {
    int n,a,b,temp;
    while(cin>>n){
        for (int i=0; i<100; i++) {
            Tree[i]=0;                  // 初始化
        }
        if(n==0){
            break;
        }
        int res=0;
        int number=n*(n-1)/2;
        vector<Edge> edges;
        while(number--){
            Edge e;
            cin>>e.begin>>e.end>>e.weight;
            edges.push_back(e);
        }
        sort(edges.begin(),edges.end());            // 排序,以从权值小的边开始遍历
        int count=edges.size();
        for(int i=0;i<count;i++)
        {
            a=findRoot(edges[i].begin);
            b=findRoot(edges[i].end);
            temp=edges[i].begin;
            if(a!=b){
                Tree[a]=b;
                while(Tree[temp]!=0){               // 路径压缩优化
                    int t=Tree[temp];
                    Tree[temp]=b;
                    temp=t;
                }
                res+=edges[i].weight;               // 累加该边权值
            }
        }
        cout<<res<<endl;
    }
    return 0;
}

应用四:还是Kruskal算法

附代码:

//
//  1144.cpp
//  Freckles
//
//  Created by chenmeiqi on 2019/4/8.
//  Copyright © 2019年 chenmeiqi. All rights reserved.
//

#include <iostream>
#include <cmath>
#include <algorithm>
#include <iomanip>
using namespace std;
#define N 101
#define M 5050          // n(n-1)/2

int Tree[N];

struct Vertex{          // 顶点集(坐标)
    float x;
    float y;
}v[N];

struct Edge             // 边集
{
    int vertex_1;
    int vertex_2;
    float weight;
    bool operator < (const Edge& a) const{
        return weight<a.weight;
    }
}e[M];

float getPosition(Vertex a, Vertex b){              // 求两点间距离
    float res;
    res=sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
    return res;
}

int findRoot(int x){                // 并查集
    while(Tree[x]!=0){
        x=Tree[x];
    }
    return x;
}

int main(int argc, const char * argv[]) {
    int n,temp_n;
    int a,b;
    float res;
    while(cin>>n){
        res=0;
        for(int i=0;i<N;i++){
            Tree[i]=0;
        }
        temp_n=n;
        int t=0;
        while(n--){
            cin>>v[t].x>>v[t].y;
            t++;
        }
        int k=0;
        for(int i=0;i<temp_n;i++){
            for(int j=i+1;j<temp_n;j++){
                e[k].vertex_1=i;
                e[k].vertex_2=j;
                e[k].weight=getPosition(v[i],v[j]);
                 k++;
            }
        }
        sort(e,e+k);                // 注意这里数量是 k 不是 n
        for(int i=0;i<k;i++){
            a=findRoot(e[i].vertex_1);
            b=findRoot(e[i].vertex_2);
            if(a!=b){
                Tree[a]=b;
                res+=e[i].weight;
            }
        }
        cout<<setiosflags(ios::fixed)<<setprecision(2)<<res<<endl;      // 保留两位小数
    }
    
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值