并查集

来源:互联网 发布:直播软件apk 编辑:程序博客网 时间:2024/05/28 16:19

并查集

一、并查集是处理什么问题的:

并查集,是一种用来管理元素分组情况的数据结构,可以处理一些不相交集合的合并与查询问题;它可以进行合并操作,但不能进行分割操作。



二、两大操作:


(1)查询元素a和元素b是否属于同一集合;


(2)合并元素a和元素b所在的集合;


三、主要的步骤:

初始化:
把每个点所在集合初始化为其自身。
(通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为O(N)。)

查找:
查找元素所在的集合,即根节点。

合并:
将两个元素所在的集合合并为一个集合。





基本思想:
在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中,最终就形成了多个(可能一个)不同的集合,其中每一个集合都具有不同于其它集合的属性,例如:每一个集合可以表示一个连通分支,则属于同一个集合的元素就表示这个连通分支的顶点,这个集合中的每一个顶点都是相互可达的,这样就可以就可以解决任意输入的两个元素是否可达的问题了。

四、处理并查集问题需要解决了的几个难点:

(1)如何合并两个不相交集合;
(2)如何判断两个元素是否属于同一个集合;
(3)路径压缩,优化时间。

优化方法:1、路径压缩:对每一个节点,一旦向上走到了一次根节点,就把这个节点到父亲的边改为直接连向根的边;
                  2、启发式合并:让让深度较小的树成为深度较大的树的子树。


五、并查集的结构:
并查集是使用树形结构实现的,不过,不是二叉树。每个元素对应一个节点,每个集合对应一棵树。



六、并查集实现中的注意点:(在使用路径压缩时,为了方便起见,即使数的高度发生了变化,我们也不修改rank的值)

在树形数据结构里,如果发生了退化的情况,那么复杂度就会变得很高。因此,有必要想办法避免退化的发生。在并查集中,只要按照如下方法就可以避免退化。

(1)对每棵树,记录这棵树的高度(rank).

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

我做的第一题并查集   HDU 1213;

点击打开链接

以下是代码部分:

#include <iostream>#include <stdio.h>#include <string.h>#define N 1000using namespace std;int father[N];int num;void set_mark(int n){    int i;    for(i = 1; i <= n; i++)    {        father[i] = i;    }    return;}int getfather(int v){    int geng = v;    int temp;    while(geng != father[geng])    {        geng = father[geng];    }    while(geng != v)    {        temp = father[v];        father[v] = geng;        v = temp;    }    return geng;}void Merge(const int &i, const int &j){    int x = getfather(i);    int y = getfather(j);    if(x != y)    {        father[x] = y;        num -= 1;    }    return;}int main(){    int T;    int n, m;    cin >> T;    while(T--)    {        int a, b;        cin >> n;        num = n;        set_mark(n);        cin >> m;        for(int i = 0; i < m; i++)        {            cin >> a;            cin >> b;            Merge(a, b);        }        cout << num << endl;    }    return 0;}


以下每一块是一个模板(分四个函数标记、找根、合并、查找)

int father[maxn];int n;void Init() {    for (int i = 0; i < n; ++i) {        father[i] = i;    }}// 递归法int getFather(const int &v) {    if (father[v] == v)        return v;    else        return getFather(father[v]);}// 递归另一种写法。int getFather(const int &v) {    if (father[v] != v) {        int root = getFather(father[v]);        return father[v] = root;    }    else        return v;}// 非递归,且不带路径压缩。int getFather(const int &v) {    int r = v;    while(r != father[r])        r = father[r];    return r;}*/// 非递归,路径压缩int getFather(const int &v) {    int t1 = v, t2;    while (v != father[v])        v = father[v];    while (t1 != father[t1]) { // 沿途上所有结点的父亲改成根。这一步是顺便的,不增加时间复杂度,却使得今后的操作比较快。这个优化称为路径压缩。        t2 = father[t1];        father[t1] = v;        t1 = t2;    }    return v;}// 归并:把节点i、节点j放到同一个根底下。void merge(const int &i, const int &j) {    int x = getFather(i);    int y = getFather(j);    if (x != y) // 可选,主要是为了防止getFather()路径压缩的时候出现死循环。        father[x] = y; // 有向图注意顺序,该行代码含义:a->b。}// 查询:查询节点i跟节点j是否在同一根下。bool judge(const int &i, const int &j) {    if (getFather(i) == getFather(j))        return true;    else        return false;}

优化思路:

 merge函数可以采用启发式合并,思路就是把深度较小的那颗子树并到深度较大的那颗子树上。int father[maxn];int n;void Init() {    for (int i = 0; i <= n; ++i) {        rank[i] = 0;        father[i] = i;    }}int getFather(const int &x) {    int px = x , i ;    while ( px != father[px])   // find root        px = father[px];     while ( x != px ) {         // path compression        i = father [ x ];        father [ x ] = px ;        x = i;    }    return px ;}void merge(const int &x, const int &y) { // 下面还有一种写法    x = getFather(x);    y = getFather(y);    if (rank[x] > rank[y])        father[y] = x;    else {        father[x] = y;        if (rank[x] == rank[y])            rank[y]++;    }    }bool judge(const int &i, const int &j) {    if (getFather(i) == getFather(j))        return true;    else        return false;}

int father[maxn], rank[maxn];void Init(const int &v) {    father[v] = -1;    rank[v] = 0;}int getFather(const int &v) {    int t1 = v;    while (father[t1] != -1)    t1 = father[t1];    while (v!=t1) {        int t2 = father[v];        father[v] = t1;        v = t2;    }    return t1;      }    void merge(const int &a, const int &b) {    int t1 = getFathet1(a);    int t2 = getFathet1(b);    if(rank[t1] > rank[t2])        father[t2] = t1;    else        father[t1] = t2;    if(rank[t1] == rank[t2])        ++rank[t2];     }bool judge(const int &i, const int &j) {    if (getFather(i) == getFather(j))        return true;    else        return false;}
/*    另一种写法:*/int f[maxn], rank[maxn], num[maxn];void Init() {    for (int i = 0; i <= n; ++i) {    rank[i] = 1;    num[i] = 1;    father[i] = i;    }}// f[]数组存放根节点,rank[]数组来存放根节点的深度,num[]数组来存放节点个数,rank[]数组和num[]数组的初始化都应为1// 启发式合并:void merge(int x, int y){    fx = getFather(x);    fy = getFather(y);    if (fx == fy) return;    if (rank[fx] > rank[fy]) {        father[fy] = fx;        num[fx] += num[fy];    }    else {        father[fx] = fy;        num[fy] += num[fx];        if (rank[fx] == rank[fy]) {            ++rank[fy];        }    }}// 路径压缩:int getFather(int x) {    if(father[x] == x)        return x;    else        return father[x] = getFather(father[x]);}


// 仍有一种写法:int father[maxn];void Init() {    for(int i = 0; i < n; ++i)        father[i] = -1;}int getFather(int x) {    if (father[x] < 0)        return x;    father[x] = getFather(father[x]);    return father[x];}int getFather(int x) {    int p = x, t;    while (father[p] >= 0)        p = father[p];    while (x != p) {        t = father[x];        father[x] = p;        x = t;    }    return x;}void merge(int x, int y) {    x = getFather(x);    y = getFather(y)    if (x == y) return;    if (father[x] < father[y]) {        father[x] += father[y];        father[y] = x;    } else {        father[y] += father[x];        father[x] = y;    }}bool judge(const int &i, const int &j) {    if (getFather(i) == getFather(j))        return true;    else        return false;}


                  

1 0
原创粉丝点击