Implementación simple de búsqueda de unión

1. El principio de búsqueda sindical

La búsqueda de unión se utiliza principalmente para resolver algunos problemas de agrupación de elementos . Maneja una serie de conjuntos disjuntos y admite dos operaciones:

  1. Unión : combina dos conjuntos disjuntos en un solo conjunto
  2. Consulta (Buscar) : consulta si dos elementos están en el mismo conjunto

Por supuesto, tal definición es demasiado académica, y después de leerla, me temo que no puedo entender para qué se usa. Así que echemos un vistazo y verifiquemos el escenario de aplicación más directo: el problema relativo

Descripción

Si un miembro de la familia es demasiado grande, realmente no es fácil juzgar si dos personas son parientes.Dado un diagrama de relaciones relativas, pregunte si dos personas dadas al azar están relacionadas.
Regla: x e y son relativos, y y z son relativos, luego x y z son relativos también. Si x e y son relativos, entonces todos los relativos de x son relativos de y, y todos los relativos de y son relativos de x.

entrada (entrada)

La primera línea: tres números enteros n, m, p, (n < = 5000, m < = 5000, p < = 5000), que indican respectivamente que hay n personas y m parientes, y preguntan por la relación relativa de p. Las siguientes m líneas: cada línea tiene dos números Mi, Mj, 1<=Mi, Mj<=N, lo que indica que Mi y Mj están relacionados. Siguientes p líneas: cada línea tiene dos números Pi, Pj, preguntando si Pi y Pj están relacionados

Salida (Salida)

Líneas P, un 'Sí' o 'No' por línea. Indica que la respuesta a la i-ésima consulta es "tiene" o "no tiene" parentesco.

El análisis preliminar piensa que este problema es un problema de juzgar si dos puntos están en el mismo subgrafo conectado en la teoría de grafos. Podemos construir modelos que dividan a todas las personas en conjuntos inconexos, donde las personas de cada conjunto sean parientes entre sí. Para saber si dos personas son parientes, basta con mirar si pertenecen al mismo conjunto. Por lo tanto, aquí puede considerar usar el conjunto de verificación de unión para el mantenimiento.


2. La introducción de la búsqueda sindical

La idea importante de la búsqueda de unión es representar el conjunto con un elemento del conjunto. He visto una metáfora interesante que compara una colección con una pandilla, y el elemento representativo es el líder de la pandilla. Usemos esta analogía a continuación para ver cómo funciona el conjunto de combinación.

inserte la descripción de la imagen aquí
Al principio, todos los héroes lucharon a su manera. Sus respectivos líderes son naturalmente ellos mismos. (Para un conjunto con un solo elemento, el elemento representativo es naturalmente el único)

Ahora el No. 1 y el No. 3 compiten, suponiendo que el No. 1 gane (no importa quién gane aquí), luego el No. 3 reconoce al No. 1 como el líder (fusionando los conjuntos donde el No. 1 y el No. 3 son ubicado, el No. 1 es el elemento representativo)

inserte la descripción de la imagen aquí
Ahora el No. 2 quiere competir con el No. 3 (fusionar el conjunto donde se encuentran el No. 3 y el No. 2), pero el No. 3 dijo, no pelees conmigo, déjame ayudar al Señor a limpiarte ( fusionar los elementos representativos). Supongamos que el No. 1 gana de nuevo esta vez, entonces el No. 2 también reconoce al No. 1 como el líder.
inserte la descripción de la imagen aquí
Ahora supongamos que el N° 4, el N° 5 y el N° 6 también tienen algunas fusiones de pandillas, y la situación en los ríos y lagos es la siguiente:
inserte la descripción de la imagen aquí
Ahora digamos que el N° 2 quiere compararse con el N° 6, al igual que lo que acabo de decir, llamar a los líderes de pandillas No. 1 y No. 4 para que salgan y luchen contra Frame (ayudar a dominar realmente duro, ah). Después de la victoria del No. 1, el No. 4 reconoció al No. 1 como líder y, por supuesto, sus subordinados también se rindieron.
inserte la descripción de la imagen aquí
Bueno, la metáfora ha terminado. Si tienes un poco de fundamento de la teoría de grafos, creo que ya te habrás dado cuenta de que esta es una estructura en forma de árbol. Para encontrar el elemento representativo del conjunto, solo necesitas visitar el nodo padre (el círculo señalado por la flecha en la figura) capa por capa Vaya directamente al nodo raíz del árbol (el círculo naranja en la figura). El padre del nodo raíz es él mismo. Podemos dibujarlo directamente como un árbol:

inserte la descripción de la imagen aquí

De esta forma, podemos escribir la versión más simple del código union-find.


3. Compresión de ruta

La eficiencia de búsqueda y unión más simple es relativamente baja. Por ejemplo, considere el siguiente escenario:

inserte la descripción de la imagen aquí
Ahora queremos fusionar (2,3), por lo que encontramos 1 de 2, fa[1]=3, por lo que queda así:
inserte la descripción de la imagen aquí
Luego encontramos otro elemento 4, y necesitamos ejecutar fusionar (2,4):
inserte la descripción de la imagen aquí
from 2 Encuentra 1, luego encuentra 3, luego fa[3]=4, por lo que queda así:
inserte la descripción de la imagen aquí
todos deben tener un sentimiento, esto puede formar una cadena larga, a medida que la cadena se hace más y más larga, queremos comenzar desde el bottom Cada vez es más difícil encontrar el nodo raíz.

¿Cómo resolverlo? Podemos usar el método de compresión de ruta. Dado que solo nos importa el nodo raíz correspondiente a un elemento, queremos que la ruta desde cada elemento hasta el nodo raíz sea lo más corta posible, preferiblemente solo un paso, así:

inserte la descripción de la imagen aquí
De hecho, esto también es muy bueno para lograrlo. Siempre que estemos en el proceso de consulta, podemos establecer el nodo principal de cada nodo en el camino como el nodo raíz. Podemos ahorrarnos muchos problemas la próxima vez que miremos hacia arriba.

Pero, de hecho, dado que la compresión de ruta solo se realiza en el momento de la consulta y solo se comprime una ruta , la estructura final de la búsqueda de unión aún puede ser complicada.

Por ejemplo, ahora tenemos un árbol más complejo que debe fusionarse con un conjunto de un elemento:
inserte la descripción de la imagen aquí

Si queremos fusionar (7,8) en este momento, si podemos elegir, ¿deberíamos configurar el nodo principal de 7 a 8, o configurar el nodo principal de 8 a 7?

Por supuesto esto último. Porque si el nodo principal de 7 se establece en 8, la profundidad del árbol (la longitud de la cadena más larga del árbol) será más profunda, y la distancia desde cada elemento del árbol original hasta el nodo raíz será mayor, y luego encontraremos el nodo raíz.La ruta será correspondientemente más larga. Aunque tenemos compresión de rutas, la compresión de rutas también consume tiempo. Y establecer el nodo principal de 8 a 7 no tendrá este problema, porque no afecta a los nodos no relacionados.

inserte la descripción de la imagen aquí

Esto nos inspira: debemos fusionar árboles simples en árboles complejos, no al revés. Porque después de fusionarse de esta manera, la cantidad de nodos cuya distancia al nodo raíz se vuelve más larga es relativamente pequeña.


4. Realización general

#pragma once
#include<vector>
#include<algorithm>
using namespace std;
class UnionFindSet
{
    
    
public:
	UnionFindSet(size_t n)
		:_ufs(n, -1)
	{
    
    }

	//合并
	void Union(int x1, int x2)
	{
    
    
		int root1 = FindRoot(x1);
		int root2 = FindRoot(x2);

		//数据量小的集合往数据量大的集合合并
		//if (abs(_ufs[root1]) < abs(_ufs[root2]))
		//{
    
    
		//	swap(root1, root2);
		//}

		//如果root1和root2相等,说明在一个集合,就没必要进行合并了	
		if (root1 != root2)
		{
    
    
			//将root2合并到root1,root1集合的个数需要加上root2集合的个数
			_ufs[root1] += _ufs[root2];
			//将root1作为root2的根	
			_ufs[root2] = root1;
		}
	}

	//找根节点
	int FindRoot(int x)
	{
    
    
		int root = x;
		while (_ufs[root] >= 0)
		{
    
    
			root = _ufs[root];
		}

		//路径压缩
		//当前值到根路径上的所有值都进行压缩
		while (_ufs[x] >= 0)
		{
    
    
			//保存当前数值的父亲
			int parent = _ufs[x];
			_ufs[x] = root;
			x = parent;
		}
		return root;
	}
	
	bool Inset(int x1, int x2)
	{
    
    
		return FindRoot(x1) == FindRoot(x2);
	}

	//获取元素个数
	size_t SetSize()
	{
    
    
		int count = 0;
		for (size_t i = 0; i < _ufs.size(); ++i)
		{
    
    
			if (_ufs[i] < 0)
			{
    
    
				count++;
			}
		}
		return count;
	}
private:
	vector<int> _ufs;
};

Ver también: Cheque de unión

Supongo que te gusta

Origin blog.csdn.net/qq_56044032/article/details/127172115
Recomendado
Clasificación