Estructura de datos --- búsqueda de unión

¿Por qué existe un conjunto de búsqueda sindical?

Aquí podemos usar un ejemplo de la vida para ayudarle a comprender y recopilar información. Definitivamente encontrará un fenómeno cuando vaya a la escuela. Antes de que comiencen las clases, nadie conoce a nadie. Todos somos un grupo pequeño. Pero después de que comiencen las clases, porque hay Si hay un compañero de escritorio al lado de tu asiento, tú y tu compañero de escritorio se conocerán y jugarán felices juntos poco después de que comiencen las clases. En este momento, dos pequeños grupos de una persona se fusionan en un pequeño grupo de dos personas. Más tarde, tú A menudo puedes girar la cabeza hacia atrás para conocer a las personas que están detrás de ti. Después de conocerse entre sí, conoces a las personas en la mesa detrás de ti y traes a tus compañeros de mesa para jugar con ellos. Entonces, en este momento, dos niños pequeños de dos años se conocerán. El grupo se fusionará en un pequeño grupo de 4 personas. A medida que pasa el tiempo, grupos de diferentes tamaños se fusionarán entre sí. Con el tiempo, las personas de una clase cambiarán de una grupo pequeño con todos a un grupo grande con todos juntos. Luego, para describir el proceso de fusión de diferentes grupos, existe la estructura de datos del conjunto de unión de control.

El principio de búsqueda sindical.

Primero echemos un vistazo a la definición más oficial de búsqueda de unión: en algunos problemas de aplicación, es necesario dividir n elementos diferentes en algunos conjuntos disjuntos. Al principio, cada elemento forma su propio conjunto de un solo elemento y luego los conjuntos que pertenecen al mismo grupo de elementos se fusionan de acuerdo con ciertas reglas. En este proceso, se utiliza repetidamente la operación de consultar a qué conjunto pertenece un determinado elemento. Un tipo de datos abstracto adecuado para describir este tipo de problema se llama conjunto de búsqueda de unión. Bingchaji es un bosque. El llamado bosque se refiere a un bosque compuesto por varios árboles. Por ejemplo, una empresa contrata a un total de 10 personas de todo el país este año. Xi'an contrata a 4 personas, Chengdu contrata a 3 personas, Wuhan recluta a 3 personas, y 10 personas provienen de diferentes lugares. La escuela no se conoce al principio y cada estudiante es un grupo pequeño independiente. Ahora estos estudiantes están numerados: {0, 1, 2, 3,4, 5 , 6, 7, 8, 9}; Dé lo siguiente La matriz se utiliza para almacenar el grupo pequeño. El valor absoluto de los números en la matriz representa: el número de miembros en el grupo pequeño. Entonces la matriz es la siguiente: Después de graduarse, los estudiantes irán a trabajar en la empresa, y los estudiantes en cada
Insertar descripción de la imagen aquí
lugar se organizan espontáneamente en pequeños grupos, los equipos viajan juntos y luego, en este momento, se fusionan de una persona y un grupo a varias personas y un grupo. entonces equipo de estudiantes de Xi'an s1={0,6,7,8}, equipo de estudiantes de Chengdu s2={1,4,9}, equipo de estudiantes de Wuhan s3={2,3,5}, 10 personas formaron tres grupos pequeños . Suponiendo que los tres 0, 1 y 2 de la derecha sirvan como capitanes, entonces la estructura actual debería quedar como sigue:
Insertar descripción de la imagen aquí
Tres grupos son tres árboles, y estos tres árboles constituyen un bosque, por lo que surge una pregunta: ¿cómo combinamos estos tres árboles en un bosque? La respuesta es usar una matriz para fusionar estos árboles. El subíndice de la matriz corresponde al elemento y el valor correspondiente al subíndice representa la relación entre diferentes elementos. Si usted es el nodo raíz, entonces el valor correspondiente a su subíndice representa su El número de nodos en el árbol. Si usted es el nodo raíz o el nodo secundario de un subárbol, entonces el valor correspondiente a su subíndice es la posición de su nodo padre en la matriz. Por ejemplo, el elemento 0 es el nodo raíz del árbol en Xi'an. 0 corresponde al subíndice 0 en la matriz, entonces los datos registrados en el subíndice 0 son -4, lo que significa que hay 4 nodos en el árbol en Xi'an. Por ejemplo, si el subíndice correspondiente al elemento 5 es 5, entonces el subíndice en la matriz Los datos registrados en la marca 5 son 2, lo que significa que el nodo actual es un nodo secundario, la posición del nodo padre de este nodo en la matriz es 5, y así sucesivamente para otros nodos, la matriz anterior se verá así: Entendido
Insertar descripción de la imagen aquí
Después de cómo representar múltiples árboles, echemos un vistazo a cómo fusionar dos árboles en uno. Por ejemplo, si fusionamos Xi'an y Chengdu arriba, Podemos modificar el nodo padre del nodo raíz en el árbol de Chengdu para que apunte a Xi'an. El nodo raíz del árbol está implementado, entonces la imagen aquí queda como sigue: Luego, en la
Insertar descripción de la imagen aquí
matriz tenemos que modificar los valores de los subíndices. Correspondiente a los nodos raíz de los dos árboles. Primero, modifique el valor del subíndice de 1 a 0 para representar el nodo. El nodo padre de 1 es 0. Cambie el valor del subíndice 0 a -7. Porque un nuevo árbol es fusionados, el número de nodos en el árbol actual aumenta. Luego, el contenido de la matriz actual se convierte en el siguiente, el fondo rojo representa los nodos raíz de diferentes árboles, el color rojo ha cambiado del 3 original al 2, entonces esto significa que el bosque actual solo tiene dos árboles, después de conocer el
Insertar descripción de la imagen aquí
método de representación del bosque y el principio de fusión de árboles, podemos simular la implementación y encontrar el conjunto.

Simular implementación y búsqueda

Preparación

Primero, debe haber una matriz de números enteros en la clase para representar la relación entre cada elemento:

class UnionFindSet
{
    
    
public:

private:
	vector<int> _ufs;//用来表示元素之间的关系
};

Pero hay un problema al hacer esto: ¿cómo sabemos a qué elemento corresponde el subíndice de la matriz? Entonces tenemos que crear un contenedor de vectores para facilitarnos encontrar el elemento correspondiente al subíndice, y como los elementos pueden ser de varios tipos, tenemos que agregar una plantilla de clase aquí, hay un parámetro en la plantilla para indicar qué elemento Actualmente está siendo procesado por el conjunto de consultas. Existe una relación entre diferentes tipos de datos. Con este contenedor podemos ver los elementos correspondientes a los subíndices. Entonces, ¿qué debemos hacer si queremos ver los subíndices correspondientes a los elementos? Entonces tenemos que crear un contenedor de mapa para registrar el subíndice correspondiente a cada elemento, luego el código actual queda de la siguiente manera:

template<class T>
class UnionFindSet
{
    
    
public:

private:
	vector<int> _ufs;//用来表示元素之间的关系
	vector<T> _a;//根据下标找元素
	map<pair<T, int>> _indexmap;//根据元素找到下标
};

Constructor

El constructor requiere dos parámetros, un parámetro recibe la matriz de datos que el contenedor actual necesita procesar y el otro parámetro indica la cantidad de datos actualmente procesados:

UnionFindSet(const T* sorce, size_t num)
{
    
    

}

Luego creamos un bucle, tomamos el valor de la matriz de parámetros en el bucle y lo insertamos en la matriz _a, porque el bucle comienza desde 0 y la matriz _a también se inserta desde la posición donde el subíndice es 0, por lo que en el bucle Por cierto, podemos insertar datos en el contenedor _indexmap, por lo que el código aquí es el siguiente:

UnionFindSet(const T* sorce, size_t num)
{
    
    
	for (size_t i = 0; i < num; i++)
	{
    
    
		_a.push_back(sorce[i]);
		_indexmap[sorce[i]] = i;
	}
}

Finalmente, expanda la longitud del contenedor _ufs a num e inicialice el valor de cada elemento a -1. El código completo es el siguiente:

UnionFindSet(const T* sorce, size_t num)
	:_ufs(num,-1)
{
    
    
	for (size_t i = 0; i < num; i++)
	{
    
    
		_a.push_back(sorce[i]);
		_indexmap[sorce[i]] = i;
	}
}

Podemos utilizar el siguiente código para realizar las siguientes pruebas:

int main()
{
    
    
	string s1[] = {
    
     "张三","李四","王五","赵六" };
	UnionFindSet<string> uf(s1, 4);
	return 0;
}

A través de la depuración, podemos ver que el contenido de este contenedor es el siguiente:
Insertar descripción de la imagen aquí
debido a que no hemos realizado ninguna operación de fusión, el valor de cada elemento en la matriz ufs es -1 y la matriz _a registra los elementos correspondientes al subíndice. 0 corresponde a Zhang San, 1 corresponde a Li Si, 2 corresponde a Wang Wu, 3 corresponde a Zhao Liu y luego _indexmap registra el subíndice correspondiente al elemento. Después de una comparación cuidadosa, podemos ver que El contenido grabado corresponde al contenido en la matriz, entonces nuestro constructor está completo. A continuación, veamos la función de búsqueda.

Buscar raíz

La función FindRoot busca el nodo raíz de un elemento. Si un nodo no es el nodo raíz, almacena el subíndice de su nodo padre en la matriz. Si un nodo es el nodo raíz, almacena el número de nodos contenidos en el árbol actual Asignación de números, por lo que en la función, podemos crear un bucle while para extraer el subíndice registrado en la matriz hasta que el valor correspondiente al subíndice sea negativo, entonces el código aquí es el siguiente:

size_t FindRoot(T tmp)
{
    
    
	int x = _indexmap.find(tmp)->second;
	while (_ufs[x] >= 0)
	{
    
    
		x = _ufs[x];
	}
	return x;
}

Unión

Pase dos elementos a la función Unión, luego la función puede fusionar los árboles donde se encuentran los dos elementos. Al comienzo de la función, primero determinamos si los nodos raíz de los árboles donde se encuentran los dos elementos son los mismos. Si son iguales, si no son iguales, simplemente los fusionamos, entonces el código aquí es el siguiente:

void Union(T tmp1, T tmp2)
{
    
    
	size_t x1 = FindRoot(tmp1);
	size_t x2 = FindRoot(tmp2);
	if (x1 != x2)
	{
    
    
	//根节点不相等才进行合并
	}
}

El proceso de fusión es muy simple: agregue el valor de un nodo raíz a otro nodo raíz y luego cambie el valor al subíndice de otro nodo raíz, luego el código aquí es el siguiente:

void Union(T tmp1, T tmp2)
{
    
    
	size_t x1 = FindRoot(tmp1);
	size_t x2 = FindRoot(tmp2);
	if (x1 != x2)
	{
    
    
	//根节点不相等才进行合并
		_ufs[x1] += _ufs[x2];
		_ufs[x2] = x1;
	}
}

Establecer recuento

La función de esta función es contar cuántos árboles existen en el contenedor actual. Entonces aquí atravesamos directamente la matriz _ufs a través de un bucle. La presencia de varios nodos con elementos negativos indica cuántos árboles existen en el contenedor actual. El código aquí es el siguiente:

size_t SetCount()
{
    
    
	size_t num = 0;
	for (auto ch : _ufs)
	{
    
    
		if (ch < 0)
		{
    
    
			num++;
		}
	}
	return num;
}

Y comprueba el combate real.

Tema 1: Número de provincias

Detalles de la pregunta:
Insertar descripción de la imagen aquí
Enlace de la pregunta-> Haga clic aquí para probar la pregunta

Análisis de preguntas

Con el conjunto de búsqueda de unión, es muy fácil hacer este tipo de preguntas. En primer lugar, la pregunta nos proporciona una matriz bidimensional. Esta matriz representa las conexiones entre ciudades. Si isConnected[0][1] es igual a 1, entonces esto significa que la Ciudad No. 1 y la Ciudad No. 2 están conectadas, y luego un grupo de ciudades interconectadas se llaman provincias. La pregunta final requiere que determinemos cuántas provincias existen actualmente en función de una matriz bidimensional. , Entonces aquí podemos crear primero una unión para encontrar objetos y luego crear un bucle for en línea para determinar las ciudades que están vinculadas entre sí en la matriz bidimensional. Si una ciudad i y una ciudad j están conectadas entre sí, use union-find para fusionar las dos ciudades y recorrerlas. Después de completar, puede regresar a la función SetCount del conjunto de búsqueda de unión para finalizar esta pregunta, porque el parámetro pasado en la pregunta es un contenedor de vector<vector<int>>, y el constructor del conjunto de búsqueda de unión que implementamos anteriormente requiere una matriz, por lo que, por conveniencia, simplificamos la clase anterior y dejamos que sirva específicamente datos de tipo int. Entonces el código aquí es el siguiente:

class UnionFindSet
{
    
    
public:
	UnionFindSet(int size)
		: _set(size, -1)
	{
    
    }

	size_t FindRoot(int x)
	{
    
    
		while(_set[x] >= 0)
			x = _set[x];

		return x;
	}

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

		if(root1 != root2)
		{
    
    
			_set[root1] += _set[root2];
			_set[root2] = root1;
		}
	}

	size_t SetCount()
	{
    
    
		size_t count = 0;
		for(size_t i = 0; i < _set.size(); ++i)
		{
    
    
			if(_set[i] < 0)
				count++;
		}

		return count;
	}

private:
	std::vector<int> _set;
};

Entonces el código para esta pregunta es el siguiente:

int findCircleNum(vector<vector<int>>& isConnected) {
    
    
    UnionFindSet uf(isConnected.size());
    for(int i=0;i<isConnected.size();i++)
    {
    
    
        for(int j=0;j<isConnected[0].size();j++)
        {
    
    
            if(i!=j&&isConnected[i][j]==1)
            {
    
    
                uf.Union(i, j);
            }
        }
    }
    return uf.SetCount();
}

Los resultados de la prueba son los siguientes:
Insertar descripción de la imagen aquí
Puede ver que los resultados de la ejecución son correctos.

Tema 2: Satisfacibilidad de ecuaciones

Insertar descripción de la imagen aquí
Enlace de pregunta-> Haga clic aquí para probar la pregunta

Análisis de preguntas

Esta pregunta obviamente se resuelve mediante la búsqueda de unión. Se proporcionan muchas expresiones en la matriz, por lo que primero creamos un bucle for para fusionar todos los elementos iguales utilizados en la expresión y luego creamos un bucle para juzgar cada expresión desigual. Los elementos de los dos tiempos pertenecen al mismo árbol. Si es un árbol, devolverá falso directamente. Si no pertenece al mismo árbol, continuará juzgando. Si se han juzgado todos los elementos y no hay error. , devolverá verdadero. , el formato del parámetro proporcionado en la pregunta es el siguiente:

bool equationsPossible(vector<string>& equations) {
    
    

}

No sabemos el número de elementos, por lo que aquí estiramos directamente el espacio a un máximo de 26 letras, por lo que aquí abrimos 26 tamaños y luego usamos el método de mapeo relativo para fusionar. El carácter a corresponde a 0 y el El carácter b corresponde a 1. Continúe así, entonces el código aquí es el siguiente:

bool equationsPossible(vector<string>& equations) {
    
    
	UnionFindSet uf(26);
	for(auto ch:equations)
	{
    
    
	    if(ch[1]=='=')
	    {
    
    
	        uf.Union(ch[0]-'a', ch[3]-'a');
	    }
	}
	for(auto ch:equations)
	{
    
    
	    if(ch[1]=='!')
	    {
    
    
	        if(uf.FindRoot(ch[0]-'a')==uf.FindRoot(ch[3]-'a'))
	        {
    
    
	            return false;
	        }
	    }
	}
	return true;
	}

Los resultados de ejecución del código son los siguientes:
Insertar descripción de la imagen aquí
Puede ver que los resultados de ejecución son normales.

Supongo que te gusta

Origin blog.csdn.net/qq_68695298/article/details/131965569
Recomendado
Clasificación