并查集总结(路径压缩+启发式合并)

并查集

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

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



二、两大操作:


(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 1000

using 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值