zoukankan      html  css  js  c++  java
  • 并查集(union-find set)与Kruskal算法

    并查集

    并查集处理的是集合之间的关系,即‘union' , 'find' 。在这种数据类型中,N个不同元素被分成若干个组,每组是一个集合,这种集合叫做分离集合。并查集支持查找一个元素所属的集合和两个元素分别所属的集合的合并。

    并查集支持以下操作:

    MAKE(X):建立一个仅有成员X的新集合。

    UNION(X,Y):将包含X和Y的动态集合合并为一个新集合S,此后该二元素处于同一集合。

    FIND(X):返回一个包含X的集合。

    注意:并查集只能进行合并操作,不能进行分割操作。

    并查集的实现原理

    并查集是使用树结构实现的:

    1.初始化:准备N个节点来表示N个元素,最开始没有边。

    2.合并:从一个组的根向另一个组的根连边,这样两棵树就变为了一棵树,也就把两个组合并为一个组了。

    3.查询:查询两个节点是否在同一个组,只需要查询他们是否具有相同的根。

    注意:

    (1) 为避免树的退化,对于每棵树,记录其高度rank。

    (2) 如果合并时两棵树高度不同,那么从rank小的向rank大的连边。

    (3) 对于每个节点,一旦向上走到了一次根节点,就把这个节点到父亲的边改为直接连向根。不仅指查询的节点,也包括查询过程中经过的节点。使用这种简化的方法时,即使树的高度发生了改变,我们也不修改rank值。

    并查集的复杂度

    对N个元素并查集进行的一次操作的复杂度是O(α(N)),α(N)是阿克曼函数的反函数。这个复杂度是比O(LogN)还要快的。

    并查集的具体C++实现:

    #pragma once
    #include <cstring>
    const int MAX_N = 100000;
    class unite_find_set
    {
    private:
        int par[MAX_N];
        int rank[MAX_N];//增加rank变量来防止树的退化(尽量让层数低的树连接到层数高的树上)
    public:
        unite_find_set(int n = 0)
        {
            init(n);
        }
        void init(int n)
        {
            for (int i = 0;i < n;++i)
            {
                par[i] = i;
                rank[i] = 0;
            }
        }
        int find(int x)
        {
            if (par[x] == x) return x;
            //此部分为两部分,find(par[x])为回溯寻找根节点,par[x]=回溯结果是把
            //叶子节点直接连接到根节点上以实现路径压缩,为简化起见,路径压缩后
            //我们没有更改rank值
            else return par[x] = find(par[x]);
        }
        void unite(int x, int y)
        {
            x = find(x);
            y = find(y);
            if (x == y) return;
            if (rank[x] < rank[y]) par[x] = y;
            else
            {
                par[y] = x;
                if (rank[x] == rank[y]) rank[x]++;
            }
        }
        bool same(int x, int y)
        {
            return find(x) == find(y);
        }
    }

    并查集的应用实例

    (因为本人太懒,以下代码并未输入数据测试,但我有迷之自信没有问题)

    更新:果然是迷之自信,已经发现问题了,已修复。

    Kruskal最小生成树生成法:

    Kruskal算法的简述见Prime算法的简述这篇文章有简单说明。

    #include <algorithm>
    #include "union_find_set.h"
    const int MAX_V = 100;
    const int INF = 1000000;
    int cost[MAX_V][MAX_V];
    int d[MAX_V], V;
    struct Edge
    {
        int from, to, cost;
    } edge[MAX_N];//用结构体表示边
    bool compare(const Edge &a, const Edge &b)
    {
        return a.cost < b.cost;
    }
    bool path[MAX_V][MAX_V];//记录结果
    int res;
    void Kruskal()
    {
        res = 0;
        union_find_set set(V);//初始化并查集
        int p = 0;//初始化edge数组游标
        for (int i = 0;i < V;++i)
        {
            for (int j = 0;j < V;++j)
            {
                path[i][j] = false;//给结果数组赋值
                if (cost[i][j] != INF)
                {
                    edge[p++] = { i,j,cost[i][j] };//给edge数组赋值
                }
            }
        }
        std::sort(edge, edge + p, compare);//按边权从小到大排序edge数组    
        for (int i = 0;i < p;++i)
        {
            if (!set.same(edge[i].from, edge[i].to))//如果边的首尾节点没有在一个生成树中
            {
                path[edge[i].from][edge[i].to] = true;//添加这条边进入生成树
                set.unite(edge[i].from, edge[i].to);//让首尾节点连接
                res += edge[i].cost;
            }
        }
  • 相关阅读:
    汽车驾驶盲区 无论新手老手都要看看
    看看大货车到底有多少盲区,肯定用得到!救命的!
    大货车的盲区很大的,所以在大货车周围 超车 并线的时候 最好鸣喇叭提示一下...
    换挡时机
    新手眼中的葵花宝典,手把手教你成为一名老司机!
    后视镜什么时候看?老司机也不一定知道
    【调查】开车时,你多长时间看一下后视镜?(安全驾驶)
    究竟什么时候该看哪个后视镜?老司机用经验告诉你答案
    java selenium (五) 元素定位大全
    java selenium (八) Selenium IDE 用法
  • 原文地址:https://www.cnblogs.com/cielosun/p/5654539.html
Copyright © 2011-2022 走看看