Union Find / Kruskal's Algorithm

并查集可用于求图中连通分量的数量,利用一个pre数组,记录节点的上级是谁,形成树状结构,每一棵树构成一个连通分量

在Kruskal算法中,需要判断当前待加入的边是否属于同一个连通分量,故需要利用并查集

【题目1】
LeetCode 547. Friend Circles
领扣 547. 朋友圈
求图中连通分量的数量

class Solution {
public:
    int findCircleNum(vector<vector<int>>& M) {
        int n = M.size();
        vector<int> pre( n, 0 );

        // 初始化pre数组,每个节点的pre设置为自己
        for( int i = 0; i < n; i++ )
            pre[i] = i;

        // 遍历所有edge,将该edge的两个节点合并
        for( int i = 0; i < n; i++ )
            for( int j = i + 1; j < n; j++ )
                if( M[i][j] )
                    merge( i, j, pre );

        // 统计连通分量数量
        int result = 0;

        for( int i = 0; i < n; i++ )
            if( pre[i] == i )
                result++;

        return result;
    }

    int find_captain( int x, vector<int>& pre )
    {   
        int cur = x;

        while( pre[cur] != cur )
            cur = pre[cur];

        // 路径压缩:把沿路的节点的pre直接指向cur
        int temp = x;

        while( temp != cur )
        {
            pre[temp] = cur;
            temp = pre[temp];
        }

        return cur;
    }

    void merge( int x, int y, vector<int>& pre )
    {
        int c1 = find_captain( x, pre );
        int c2 = find_captain( y, pre );

        if( c1 != c2 )
            pre[c1] = c2;
    }
};

【题目2】poj 1287 Networking
Kruskal算法求图的最小生成树

第1行为节点个数n和边数e,节点编号为1n
接下来e行,分别表示相互连接的两个节点以及边的权重
n0时,输入结束

#include <stdio.h>
#include <string.h>
#include <vector>
#include <algorithm>
#define MAXN 50
using namespace std;



class Edge
{
public:
    int p1, p2, cost;

    Edge( int p1, int p2, int cost )
    {
        this->p1 = p1;
        this->p2 = p2;
        this->cost = cost;
    }
};

bool cmp( Edge a, Edge b )
{
    return a.cost < b.cost; // 升序
}



int n, e;
int pre[MAXN + 10];
int p1, p2, cost;



// 查询x所在队伍的队长
int find_captain( int x )
{   
    int cur = x;

    while( pre[cur] != cur )
        cur = pre[cur]; // cur为x的队长

    // 路径优化:把x到cur的沿路每个节点的pre直接设置为cur
    int temp = x;

    while( temp != cur )
    {
        pre[temp] = cur;
        temp = pre[temp];
    }

    return cur;
}

// 合并x和y所在的队伍 
void merge( int x, int y )
{
    int c1 = find_captain(x);
    int c2 = find_captain(y);

    if( c1 != c2 )
        pre[c1] = c2;   // 指定新队伍的队长
}

int Kruskal( vector<Edge>& vec )
{
    // 对所有边从小到大排序
    sort( vec.begin(), vec.end(), cmp );

    // 每个节点自成一个小队
    for( int i = 0; i < n; i++ )
        pre[i] = i;

    int result = 0;

    for( int i = 0; i < vec.size(); i++ )
        // 如果节点p1和p2属于不同的“队伍”(p1和p2属于不同的连通分量)
        // 则合并p1和p2的队伍,选择该条边
        if( find_captain( vec[i].p1 ) != find_captain( vec[i].p2 ) )
        {
            merge( vec[i].p1, vec[i].p2 );
            result += vec[i].cost;
        }

    return result;
}



int main()
{
    while( scanf( "%d", &n ) == 1 && n )
    {
        scanf( "%d", &e );

        vector<Edge> vec;

        for( int i = 0; i < e; i++ )
        {
            scanf( "%d%d%d", &p1, &p2, &cost );
            vec.push_back( Edge( p1 - 1, p2 - 1, cost ) );  // 规定节点编号从0开始
        }

        printf( "%d\n", Kruskal( vec ) );
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值