并查集_森林_含秩的判断和路径优化

原创 2016年06月01日 18:29:07

注意,不同教材在介绍森林的遍历方法时会有不同的表述,有些教材会将后序遍历称为中序遍历,但实质上遍历方法是一样的。

森林的先序遍历的规则:

访问森林中第一棵树的根结点
先序遍历森林中第一棵树的子树森林
先序遍历森林中,除第一棵树外其余树构成的森林
森林的后序遍历的规则:

后序遍历森林中第一棵树的根结点的各子树所构成的森林
访问森林中第一棵树的根结点
后序遍历森林中除第一棵树外其余树构成的森林

二叉树是树的特例,而树也可以看作森林的特例。在接下来的课程中,会学习一种用森林来表示的高效数据结构——并查集,包括初始化、查询和合并操作,以及两种非常有效的优化。

在计算机科学中,并查集(Merge-Find Set),也被称为不相交集合(Disjoint Set),是用于解决若干的不相交集合的如下几种操作的统称:

MAKE-SET(x):即初始化操作,建立一个只包含元素 x 的集合。
UNION(x, y):即合并操作,将包含 x 和 y 的集合合并为一个新的集合。
FIND-SET(x):即查询操作,计算 x 所在的集合。
并查集通常同时指代不相交集合的数据结构及其对应的算法,其在有些教材中的英文名称也叫做 Disjoint Set Union,表示用于求不相交集合并集的相关算法。

通常我们会用有根树来表示集合,树中的每一个结点都对应集合的一个成员,每棵树表示一个集合。

每个成员都有一条指向父结点的边,整个有根树通过这些指向父结点的边来维护。每棵树的根就是这个集合的代表,并且这个代表的父结点是它自己。

通过这样的表示方法,我们将不相交的集合转化为一个森林,也叫不相交森林。接下来会介绍,如何通过不相交森林实现并查集的初始化、合并和查询操作。

通常并查集初始化操作是对每个元素都建立一个只包含该元素的集合。这意味着每个成员都是自身所在集合的代表,所以我们只需要将所有成员的父结点设为它自己就好了。

在不相交森林中,并查集的查询操作,指的是查找出指定元素所在有根树的根结点是谁。我们可以通过每个指向父结点的边回溯到结点所在有根树的根,也就是对应集合的代表元素。

并查集的合并操作需要用到查询操作的结果。合并两个元素所在的集合,需要首先求出两个元素所在集合的代表元素,也就是结点所在有根树的根结点。接下来将其中一个根结点的父亲设置为另一个根结点。这样我们就把两棵有根树合并成一棵了。

并查集的查询操作最坏情况下的时间复杂度为 O(n)O(n),其中 n 为总元素个数。最坏情况发生时,每次合并对应到森林上都是一个点连到一条链的一端。此时如果每次都查询链的最底端,也就是最远离根的位置的元素时,复杂度便是 O(n)O(n) 了。

为了改善时间效率,可以通过启发式合并方法,将包含较少结点的树接到包含较多结点的树根上,可以防止树退化成一条链。另外,我们也可以通过路径压缩的方法来进一步减少均摊复杂度。同时使用这两种优化方法,可以将每次操作的时间复杂度优化至接近常数级。

含秩优化

#include <iostream>
using namespace std;

class DisjointSet {
private:
    int *father,*rank;//rank保存每个节点为根时,秩的大小
public:
    DisjointSet(int size) {
        father = new int[size];
        rank=new int[size];
        for (int i = 0; i < size; ++i) {
            father[i] = i;
            rank[i]=0;
        }
    }
    ~DisjointSet() {
        delete[] father;
        delete[] rank;
    }
    int find_set(int node) {
        if (father[node] != node) {
            return find_set(father[node]);
        }
        return node;
    }
    bool merge(int node1, int node2) {
        int ancestor1 = find_set(node1);
        int ancestor2 = find_set(node2);
        if (ancestor1 != ancestor2) {
            if(rank[ancestor1] > rank[ancestor2]){
                swap(ancestor1 , ancestor2);
            }
            father[ancestor1] = ancestor2;
            rank[ancestor2]=max(rank[ancestor1]+1,rank[ancestor2]);
            return true;
        }
        return false;
    }
};

int main() {
    DisjointSet dsu(100);
    int m, x, y;
    cin >> m;
    for (int i = 0; i < m; ++i) {
        cin >> x >> y;
        bool ans = dsu.merge(x, y);
        if (ans) {
            cout << "success" << endl;
        } else {
            cout << "failed" << endl;
        }
    }
    return 0;
}

含路径优化

#include <iostream>
using namespace std;

class DisjointSet {
private:
    int *father, *rank;
public:
    DisjointSet(int size) {
        father = new int[size];
        rank = new int[size];
        for (int i = 0; i < size; ++i) {
            father[i] = i;
            rank[i] = 0;
        }
    }
    ~DisjointSet() {
        delete[] father;
        delete[] rank;
    }
    int find_set(int node) {
        //路径优化了
        if (father[node] != node) {
            //根不是father的情况
            father[node]=find_set(father[node]);
            //已经将node的根复制给node的father了
        }
         return father[node];
    }
    bool merge(int node1, int node2) {
        int ancestor1 = find_set(node1);
        int ancestor2 = find_set(node2);
        if (ancestor1 != ancestor2) {
            if (rank[ancestor1] > rank[ancestor2]) {
                swap(ancestor1, ancestor2);
            }
            father[ancestor1] = ancestor2;
            rank[ancestor2] = max(rank[ancestor1] + 1, rank[ancestor2]);
            return true;
        }
        return false;
    }
};

int main() {
    DisjointSet dsu(100);
    int m, x, y;
    cin >> m;
    for (int i = 0; i < m; ++i) {
        cin >> x >> y;
        bool ans = dsu.merge(x, y);
        if (ans) {
            cout << "success" << endl;
        } else {
            cout << "failed" << endl;
        }
    }
    return 0;
}
版权声明:ShirleyPaul原创,未经博主允许不得转载

相关文章推荐

树结构练习——判断给定森林中有多少棵树 (并查集)

树结构练习——判断给定森林中有多少棵树 Time Limit: 1000MS Memory limit: 65536K 题目描述  众人皆知,在编程领域中,C++是一门非常重...

暑假集训 8.11-2 树结构练习——判断给定森林中有多少棵树 sdut2198 并查集

树结构练习——判断给定森林中有多少棵树 Time Limit: 1000MS Memory limit: 65536K 题目描述 众人皆知,在编程领域中,C++是一门非常...

并查集(两个优化—按秩合并、路径压缩) poj2492

并查集有两个优化。 一、按秩合并 描述:就是在对两个不同子集连接时,按照rank来连,也就是rank低的连在rank高的下面。rank高的做父亲节点。 作用,这样类似维护了一棵树,树是rank高...

优化并查集

  • 2013-05-04 18:41
  • 899B
  • 下载

十、森林与并查集---(5)并查集按秩合并优化

摘自:http://www.jisuanke.com/course/35/7069 并查集的查询操作最坏情况下的时间复杂度为O(n), 其中n为总元素个数。极端情况下,有根树会退化成一条链,而查询操...

重学数据结构系列之——森林之并查集(Disjoint set)

1.森林 森林就是有很多的树组成的 2.森林(并查集)的初始化 每个元素都是各自构成一个集合,比如说自然数1到3,初始化时就3个集合{1},{2},{3},之后我们会实现合并操作,将两集合合并 ...

20131030: 森林结构的运用(poj: 树的转换,电话号码,物质分解记录);带权并查集(食物链);C++输入;map的基本使用

本来是应该昨晚写这篇总结的,但无奈昨晚断网了,坑爹的校园网!!!~ 昨天成功刷掉了树与森林的4道题,加上前天做掉的并查集入门题, 树与森林宣告终结!不过这一章的确学到很多东西,尤其是编程技巧和一些细节...

算法导论之不相交集合森林——并查集

最近写一些算法题总是碰到同一段代码: int find(x) { if(father[x]!=x) { father[x]=find(father[x]) } return father[x]; }...
  • GS_MY
  • GS_MY
  • 2014-03-09 17:28
  • 650

暑假集训 8.17 数据结构实验:连通分量个数(并查集判断连通分量个数 路径压缩)sdutoj1488

数据结构实验:连通分量个数 Time Limit: 1000MS Memory limit: 65536K 题目描述  在无向图中,如果从顶点vi到顶点vj有路径,则称vi和vj连通。...

nyoj 单词拼接(并查集判断连通性+欧拉路径)

这题还是比较难的。 首先建图方面,如果单纯的把单词作为点,能拼接的关系作为边,那么就是哈密顿图(每个点仅能走一次),难度比较大。 换一种思路,就是把每个单词看成一条有向边,由该单词的首字母指向尾字母。...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)