【算法模板】 并查集

1,522 阅读3分钟

并查集

1 概念介绍

在计算机科学中,并查集是一种树形结构,用来处理不交集(Disjoint Sets)得合并和查询问题[1]。并查集主要是有两个操作,依次是:

  • Find : 查找
  • Union :合并

其中Find是判断给定元素属于哪一类别,常用一组类别中某个元素作为Root元素,表征这一类别,那么Find操作其实就是这个元素对应的Root值为多少。两个元素的Root值相同,则该两个元素为同一类别。 在构建并查集时,会遇到将两个类别合并为一个类别,即为Union。按上述对于Find操作的描述,即将原两个类别中所有元素对应的Root值改成同一值。

2 代码模板

2.1 朴素并查集

不考虑效率,从定义上实现并查集Find和Union两个操作。核心点就是用一个p数组维护该元素的父节点元素值或Root值(路径压缩),当一个元素的p值为其本身时,则该节点为Root节点。 其中p值为元素对应的Root时并查集效率更高,以下模板给出了这种情况下的算法实现。

void MakeSet(int size)
{
    // 初始化并查集,将每个元素的p值设置为其本身
    // 假设节点值是1~size(含size)
    for (int i = 1; i <= size; i++) {
        p[i] = i;
    }
}

int Find(int n)
{
    // 判断元素n的类别,即查找元素n的Root元素值
    if (p[n] != n) {
        p[n] = Find(p[n]); // 非Root节点,继续向上查找,并赋值
    }
    return p[n];
}

void Union(int n, int m)
{
    // 将元素n和元素m所在类别合并
    // 只需将n和m中的一个元素的Root指向另一个元素的Root
    int rootA = Find(n);
    int rootB = Find(m);
    p[rootA] = rootB;  // n的Root指向m的Root
}

2.2 维护元素集Size的并查集

与朴素并查集算法实现不用的是,这里维护了一个size数组保存每个类别中的当前的元素数量,预设只有Root节点的size数组值才有效。

void MakeSet(int size)
{
    // 初始化并查集,将每个元素的p值设置为其本身
    // 假设节点值是1~size(含size)
    for (int i = 1; i <= size; i++) {
        p[i] = i;
        // ! 增加size数组
        size[i] = 1;
    }
}

int Find(int n)
{
    // 判断元素n的类别,即查找元素n的Root元素值
    // ! Find操作是不需更新size数组
    if (p[n] != n) {
        p[n] = Find(p[n]); // 非Root节点,继续向上查找,并赋值
    }
    return p[n];
}

void Union(int n, int m)
{
    // 将元素n和元素m所在类别合并
    // 只需将n和m中的一个元素的Root指向另一个元素的Root
    int rootA = Find(n);
    int rootB = Find(m);
    p[rootA] = rootB;  // n的Root指向m的Root
    // ! rootB为新的Root, 只需更新rootB的size值
    size[rootB] += size[rootA];

}

2.3 维护元素到root节点距离的并查集

用一个数组维护当前元素到root节点的距离。

void MakeSet(int size)
{
    // 初始化并查集,将每个元素的p值设置为其本身
    // 假设节点值是1~size(含size)
    for (int i = 1; i <= size; i++) {
        p[i] = i;
        w[i] = 0; // 默认每个节点的root节点为其本身,故w值默认为0
    }
}

int Find(int n)
{
    // 判断元素n的类别,即查找元素n的Root元素值
    if (p[n] != n) {
        int num = Find(p[n]);
        w[n] += w[num]; // 更新当前节点的w值
        p[n] = num; // 非Root节点,继续向上查找,并赋值
    }
    return p[n];
}

void Union(int n, int m)
{
    // 将元素n和元素m所在类别合并
    // 只需将n和m中的一个元素的Root指向另一个元素的Root
    int rootA = Find(n);
    int rootB = Find(m);
    p[rootA] = rootB;  // n的Root指向m的Root
    w[rootA] = distnace; // 具体情况具体分析
}

相关leetcode例题

LeetCode并查集专题

[1] 并查集-维基百科

[2] 并查集 OI Wiki