Consolidación y recopilación de temas especiales Hang Dian OJ1232 Combinación inteligente y compresión de ruta

El conjunto de búsqueda de unión también se denomina conjunto disjunto, que puede describir el proceso de dividir un conjunto en varias clases de equivalencia a través de relaciones de equivalencia (satisfaciendo la reflexividad, la simetría y la transitividad). Solo hay dos operaciones en la búsqueda de unión, buscar y unión: buscar devuelve la clase de equivalencia a la que pertenece un elemento y unión fusiona la clase de equivalencia en la que se encuentran dos elementos.

Tome la pregunta Hangdian OJ1232 como ejemplo para ilustrar las dos realizaciones de la búsqueda de unión y los dos métodos de optimización de la última realización.

Enlace de tema

En la primera implementación, cada elemento de la matriz almacena el nombre de su clase de equivalencia. El proceso de búsqueda solo necesita devolver el valor de la coordenada del elemento, mientras que el proceso de unión atraviesa la matriz para modificar.

Para N operaciones de búsqueda u operaciones de unión consecutivas, el tiempo requerido para esta realización es O (N) y O (N ^ 2) respectivamente

#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;
}

La segunda implementación usa una estructura de árbol con el nodo raíz apuntando a sí mismo. El proceso de búsqueda solo necesita iterar para regresar al nodo raíz, y el proceso de unión solo necesita fusionar el nodo raíz.

Para N operaciones de búsqueda consecutivas u operaciones de unión, el peor tiempo requerido para esta realización es O (N ^ 2) y O (N), respectivamente, pero es raro que el árbol degenere en una lista vinculada, por lo que esta realización Es más rápido que la implementación anterior y tenemos dos formas más de optimizar esta implementación.

#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;
    }
}

El primer método de optimización se llama fusión flexible, que optimiza la fusión de árboles en el proceso de unión, de modo que se controle la altura de los árboles. Podemos hacer que el nodo apunte a un valor negativo para indicar que el nodo es el nodo raíz, y usar el número de negativos para indicar el número de elementos del árbol o la altura del árbol (las dos estrategias se denominan suma flexible por tamaño y altura / rango Unión flexible), deje siempre que la raíz del árbol con el valor absoluto más pequeño apunte a la raíz del árbol más grande durante cada operación de unión.

El siguiente código utiliza una estrategia en la que el nodo raíz apunta a un valor negativo para que sea posible almacenar más información.

#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;
    }
}

Fusiones flexibles, principalmente cambios en las rutinas sindicales

#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;
    }
}

El segundo método de optimización se llama compresión de ruta, la idea es hacer que el nodo en la ruta apunte al nodo raíz cada vez que se ejecuta la rutina de búsqueda, de modo que se reduzca la altura del árbol. Los siguientes dos fragmentos de código combinan la compresión de ruta y el cálculo inteligente juntos, que son respectivamente la realización iterativa y recursiva de la compresión de ruta.

#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;
    }
}

Referencias
"Análisis de algoritmos y estructura de datos-Descripción del lenguaje C ++" (Cuarta edición) Mark Allen Weiss Capítulo 8 Clases de conjuntos disjuntos

Supongo que te gusta

Origin blog.csdn.net/u011917745/article/details/113949853
Recomendado
Clasificación