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

原创 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原创,未经博主允许不得转载

并查集的优化:按秩合并和路径压缩

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

并查集的两种优化(按秩合并,路径压缩)

并查集是建立在对不相交集合进行的两种基本操作的基础之上的。操作之一:检索某元素属于哪个集合;操作之二:合并两个集合。黑书上说了,这种结构显然可以用链表或森林实现,显然用链表进行查询时间复杂度应该是O(...
  • u010255534
  • u010255534
  • 2013年08月04日 22:14
  • 1426

并查集(按秩合并、路径压缩)

算法分类: 数据结构 算法原理: 通过find函数找出该节点的根节点, 通过UNION函数将两棵树合并。 加入rank[N]来记录每个节点的秩(即树的高度),并按秩进行合并,可避免合...
  • jokes000
  • jokes000
  • 2012年04月30日 11:15
  • 8600

并查集的两个优化(秩优化+路径压缩)

路径压缩 寻找祖先时采用递归,但是一旦元素一多起来,或退化成一条链,每次GetFather都将会使用O(n)的复杂度,这显然不是我们想要的。对此,我们必须要进行路径压缩,即我们找到最久远...
  • kalilili
  • kalilili
  • 2015年01月22日 08:44
  • 1398

十、森林与并查集---(1)什么是并查集

摘自计蒜客:http://www.jisuanke.com/course/35/7052 在计算机科学中,并查集(Merge-Find Set),也被称为不相交集合(Disjoint Set),是用于...
  • firetreeSF
  • firetreeSF
  • 2016年04月30日 09:46
  • 594

并查集的“并优化”(leader合并)和“查优化”(路径压缩)

在博文http://blog.csdn.net/stpeace/article/details/46506861中, 我们已经详细地了解了并查集, 不过, 那个程序略显粗糙, 下面我们考虑来优化一下。...
  • stpeace
  • stpeace
  • 2015年06月22日 16:45
  • 3198

并查集-按秩合并

1239: 中山学院 ACM小组 Time Limit: 1 Sec  Memory Limit: 64 MB Submit: 79  Solved: 21 [Submit][Status][W...
  • qq_24489717
  • qq_24489717
  • 2015年06月16日 21:40
  • 1095

UVA 11354 - Bond(并查集-按秩合并)

题目链接:点击打开链接 题意:给出一张n个点m条边的无向图, 每条边有一个危险度,有q个询问, 每次给出两个点s、t,找一条路, 使得路径上的最大危险度最小。 思路:首先,我们可以发现,如果求一个...
  • weizhuwyzc000
  • weizhuwyzc000
  • 2016年02月15日 16:14
  • 2164

20131029: 并查集; 树与森林入门

今天开始学习树与森林(虽说老师早就开始讲了), 森林就是多棵树的集合,当然,可以为空。 而树也就是在二叉树的基础上允许多分叉, 必须要掌握的是树与森林的转换, 二叉树与森林的转换, 树的性质(基本上...
  • u010644404
  • u010644404
  • 2013年10月29日 00:45
  • 1470

并查集优化

按秩合并 矮树合并到高树 void Union(int a, int b) { if(tree[a].weight == tree[b].weight) { //树高一样 ...
  • u014525348
  • u014525348
  • 2014年04月03日 18:00
  • 680
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:并查集_森林_含秩的判断和路径优化
举报原因:
原因补充:

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