Consolidation and collection of special topics Hang Dian OJ1232 Smart merge and path compression

Union search set is also called disjoint set, which can describe the process of dividing a set into several equivalence classes through equivalence relations (satisfying reflexivity, symmetry, and transitivity). There are only two operations in union search, find and union: find returns the equivalence class to which an element belongs, and union merges the equivalence class in which two elements are located.

Take Hangdian OJ1232 question as an example to illustrate the two realizations of union search and the two optimization methods of the latter realization.

Topic link

In the first implementation, each element of the array stores the name of its equivalence class. The find process only needs to return the value of the element coordinate; while the union process traverses the array to modify.

For N consecutive find operations or union operations, the time required for this realization is O(N) and O(N^2) respectively

#include<bits/stdc++.h>
using namespace std;
int bjset[1010];
int bjset_find(int x)
{
    
    
    return bjset[x];
}
void bjset_union(int x, int y,int n)
{
    
    
    int classx = bjset_find(x);
    int classy = bjset_find(y);
    if (classx != classy) {
    
    
        for (int i = 1; i <= n; ++i) {
    
    
            if (bjset[i] == classx)
                bjset[i] = classy;
        }
    }
}
int main()
{
    
    
#ifdef LOCAL
    freopen("input.txt", "r", stdin);
#endif
    int n;
    while (cin >> n, n) {
    
    
        for (int i = 1; i <= n; ++i) bjset[i] = i;//初始化并查集
        int m; cin >> m;
        while (m--) {
    
    
            int x, y; cin >> x >> y;
            bjset_union(x, y, n);
        }
        int ctr = 0;
        for (int i = 1; i <= n; ++i) {
    
    
            if (bjset[i] == i) ++ctr;
        }
        cout << ctr - 1 << endl;
    }
    return 0;
}

The second implementation uses a tree structure with the root node pointing to itself. The find process only needs to iterate to return to the root node, and the union process only needs to merge the root node.

For N consecutive find operations or union operations, the worst-case time required for this implementation is O(N^2) and O(N), respectively, but it is rare for the tree to degenerate into a linked list, so this implementation It is faster than the previous implementation, and we have two more ways to optimize this implementation.

#include<bits/stdc++.h>
using namespace std;
int bjset[1010];
int bjset_find(int x)
{
    
    
    while (bjset[x] != x) x = bjset[x];
    return bjset[x];
}
void bjset_union(int x, int y) 
{
    
    
    int rootx = bjset_find(x);
    int rooty = bjset_find(y);
    if (rootx != rooty) {
    
    
        bjset[rootx] = rooty;
    }
}
int main()
{
    
    
#ifdef LOCAL
    freopen("input.txt", "r", stdin);
#endif
    int n;
    while (cin >> n, n) {
    
    
        for (int i = 1; i <= n; ++i) bjset[i] = i;
        int m; cin >> m;
        while (m--) {
    
    
            int x, y; cin >> x >> y;
            bjset_union(x, y);
        }
        int ctr = 0;
        for (int i = 1; i <= n; ++i) {
    
    
            bjset[i] == i ? (++ctr) : 0;
        }
        cout << ctr - 1 << endl;
    }
}

The first optimization method is called flexible merge, which optimizes the merging of trees in the union process, so that the height of the trees is controlled. We can make the node point to a negative value to indicate that the node is the root node, and use the number of negatives to indicate the number of elements in the tree or the height of the tree (the two strategies are called flexible summation by size and height/rank Flexible union), always let the root of the tree with the smaller absolute value point to the root of the larger tree during each union operation.

The following code uses a strategy where the root node points to a negative value to make it possible to store more information.

#include<bits/stdc++.h>
using namespace std;
int bjset[1010];
int bjset_find(int x)
{
    
    
    while (bjset[x] >= 0) x = bjset[x];//find的写法有变化
    return x;
}
void bjset_union(int x, int y) 
{
    
    
    int rootx = bjset_find(x);
    int rooty = bjset_find(y);
    if (rootx != rooty) {
    
    
        bjset[rootx] = rooty;
    }
}
int main()
{
    
    
#ifdef LOCAL
    freopen("input.txt", "r", stdin);
#endif
    int n;
    while (cin >> n, n) {
    
    
        for (int i = 1; i <= n; ++i) bjset[i] = -1;//初始化的方法不同
        int m; cin >> m;
        while (m--) {
    
    
            int x, y; cin >> x >> y;
            bjset_union(x, y);
        }
        int ctr = 0;
        for (int i = 1; i <= n; ++i) {
    
    
            bjset[i] == -1 ? (++ctr) : 0;
        }
        cout << ctr - 1 << endl;
    }
}

Flexible mergers, mainly changes in union routines

#include<bits/stdc++.h>
using namespace std;
int bjset[1010];
int bjset_find(int x)
{
    
    
    while (bjset[x] >= 0) x = bjset[x];
    return x;
}
void bjset_union(int x, int y) 
{
    
    
    int rootx = bjset_find(x);
    int rooty = bjset_find(y);
    if (rootx == rooty) return;
    if (bjset[rootx] > bjset[rooty]) {
    
    
        bjset[rootx] = rooty;
        (bjset[rooty])--;
    }
    else if (bjset[rootx] <= bjset[rooty]) {
    
    
        bjset[rooty] = rootx;
        (bjset[rootx])--;
    }
}
int main()
{
    
    
#ifdef LOCAL
    freopen("input.txt", "r", stdin);
#endif
    int n;
    while (cin >> n, n) {
    
    
        for (int i = 1; i <= n; ++i) bjset[i] = -1;
        int m; cin >> m;
        while (m--) {
    
    
            int x, y; cin >> x >> y;
            bjset_union(x, y);
        }
        int ctr = 0;
        for (int i = 1; i <= n; ++i) {
    
    
            bjset[i] < 0 ? (++ctr) : 0;
        }
        cout << ctr - 1 << endl;
    }
}

The second optimization method is called path compression. The idea is to make the node on the path point to the root node every time the find routine is executed, so that the height of the tree is reduced. The following two pieces of code combine path compression and smart calculation together, which are respectively the iterative and recursive realization of path compression.

#include<bits/stdc++.h>
using namespace std;
int bjset[1010];
int bjset_find(int x)//递归实现
{
    
    
    if (bjset[x] > 0) bjset[x] = bjset_find(bjset[x]);
    if (bjset[x] < 0) return x;
    else return bjset[x];
}
void bjset_union(int x, int y) 
{
    
    
    int rootx = bjset_find(x);
    int rooty = bjset_find(y);
    if (rootx == rooty) return;
    if (bjset[rootx] > bjset[rooty]) {
    
    
        bjset[rootx] = rooty;
        (bjset[rooty])--;
    }
    else if (bjset[rootx] <= bjset[rooty]) {
    
    
        bjset[rooty] = rootx;
        (bjset[rootx])--;
    }
}
int main()
{
    
    
#ifdef LOCAL
    freopen("input.txt", "r", stdin);
#endif
    int n;
    while (cin >> n, n) {
    
    
        for (int i = 1; i <= n; ++i) bjset[i] = -1;
        int m; cin >> m;
        while (m--) {
    
    
            int x, y; cin >> x >> y;
            bjset_union(x, y);
        }
        int ctr = 0;
        for (int i = 1; i <= n; ++i) {
    
    
            bjset[i] < 0 ? (++ctr) : 0;
        }
        cout << ctr - 1 << endl;
    }
}

#include<bits/stdc++.h>
using namespace std;
int bjset[1010];
int bjset_find(int x)//迭代实现
{
    
    
    int r = x;
    while (bjset[r] >= 0) r = bjset[r];
    while (bjset[x] >= 0) {
    
    
        int tmp = x;
        x = bjset[x];
        bjset[tmp] = r;
    }
    return r;
}
void bjset_union(int x, int y) 
{
    
    
    int rootx = bjset_find(x);
    int rooty = bjset_find(y);
    if (rootx == rooty) return;
    if (bjset[rootx] > bjset[rooty]) {
    
    
        bjset[rootx] = rooty;
        (bjset[rooty])--;
    }
    else if (bjset[rootx] <= bjset[rooty]) {
    
    
        bjset[rooty] = rootx;
        (bjset[rootx])--;
    }
}
int main()
{
    
    
#ifdef LOCAL
    freopen("input.txt", "r", stdin);
#endif
    int n;
    while (cin >> n, n) {
    
    
        for (int i = 1; i <= n; ++i) bjset[i] = -1;
        int m; cin >> m;
        while (m--) {
    
    
            int x, y; cin >> x >> y;
            bjset_union(x, y);
        }
        int ctr = 0;
        for (int i = 1; i <= n; ++i) {
    
    
            bjset[i] < 0 ? (++ctr) : 0;
        }
        cout << ctr - 1 << endl;
    }
}

References
"Data Structure and Algorithm Analysis-C++ Language Description" (Fourth Edition) Mark Allen Weiss Chapter 8 Disjoint Set Classes

Guess you like

Origin blog.csdn.net/u011917745/article/details/113949853