Implementação simples da pesquisa de união

1. O princípio da busca sindical

A busca de união é usada principalmente para resolver alguns problemas de agrupamento de elementos . Ele gerencia uma série de conjuntos disjuntos e suporta duas operações:

  1. União : Combina dois conjuntos disjuntos em um conjunto
  2. Query (Find) : Consulta se dois elementos estão no mesmo conjunto

É claro que essa definição é muito acadêmica e, depois de lê-la, receio não poder entender para que ela é usada. Então vamos dar uma olhada e verificar o cenário de aplicação mais direto: o problema relativo

Descrição

Se um membro da família é muito grande, realmente não é fácil julgar se duas pessoas são parentes.Dado um diagrama de relacionamento relativo, pergunte se duas pessoas dadas ao acaso são parentes.
Regra: x e y são relativos, y e z são relativos, então x e z também são relativos. Se x e y são parentes, então todos os parentes de x são parentes de y, e todos os parentes de y são parentes de x.

Entrada (entrada)

A primeira linha: três inteiros n, m, p, (n < = 5000, m < = 5000, p < = 5000), que indicam respectivamente que existem n pessoas e m parentes, e perguntam sobre a relação relativa de p. As seguintes m linhas: cada linha tem dois números Mi, Mj, 1<=Mi, Mj<=N, indicando que Mi e Mj estão relacionados. Próximas linhas p: cada linha tem dois números Pi, Pj, perguntando se Pi e Pj estão relacionados

Saída (Saída)

Linhas P, um 'Sim' ou 'Não' por linha. Indica que a resposta à pergunta i é "tem" ou "não tem" parentesco.

A análise preliminar pensa que este problema é um problema de julgar se dois pontos estão no mesmo subgrafo conectado na teoria dos grafos. Podemos construir modelos que dividem todas as pessoas em conjuntos disjuntos, onde as pessoas em cada conjunto são parentes umas das outras. Para saber se duas pessoas são parentes, basta observar se elas pertencem ao mesmo conjunto. Portanto, aqui você pode considerar o uso do conjunto de verificação de união para manutenção.


2. A introdução da busca sindical

A ideia importante da busca de união é representar o conjunto com um elemento no conjunto. Já vi uma metáfora interessante que compara uma coleção a uma gangue, e o elemento representativo é o líder da gangue. Vamos usar esta analogia a seguir para ver como funciona o conjunto de mesclagem.

insira a descrição da imagem aqui
No início, todos os heróis lutavam à sua maneira. Seus respectivos líderes são naturalmente eles mesmos. (Para um conjunto com apenas um elemento, o elemento representativo é naturalmente o único)

Agora o nº 1 e o nº 3 competem, supondo que o nº 1 vença (não importa quem ganhe aqui), então o nº 3 reconhece o nº 1 como líder (fundindo os conjuntos onde o nº 1 e o nº 3 são localizado, o nº 1 é o elemento representativo)

insira a descrição da imagem aqui
Agora o nº 2 quer competir com o nº 3 (fundir o conjunto onde o nº 3 e o nº 2 estão localizados), mas o nº 3 disse, não brigue comigo, deixe-me ajudar o Senhor a limpar você ( mesclar os elementos representativos). Vamos supor que o nº 1 vença novamente desta vez, então o nº 2 também reconhece o nº 1 como líder.
insira a descrição da imagem aqui
Agora vamos supor que o nº 4, nº 5 e nº 6 também tenham algumas fusões de gangues, e a situação nos rios e lagos se torne a seguinte:
insira a descrição da imagem aqui
Agora vamos dizer que o nº 2 quer comparar com o nº 6, assim como o que eu acabei de dizer, chame os líderes de gangues No. 1 e No. 4 para sair e lutar com Frame (ajudar a dominar ah muito difícil). Após a vitória do nº 1, o nº 4 reconheceu o nº 1 como líder e, é claro, seus subordinados também se renderam.
insira a descrição da imagem aqui
Bem, a metáfora acabou. Se você tem um pouco de fundamento em teoria dos grafos, acredito que já tenha notado que esta é uma estrutura em forma de árvore. Para encontrar o elemento representativo do conjunto, você só precisa visitar o nó pai (o círculo apontado pela seta no a figura) camada por camada, vá direto para o nó raiz da árvore (o círculo laranja na figura). O pai do nó raiz é ele mesmo. Podemos desenhá-lo diretamente como uma árvore:

insira a descrição da imagem aqui

Dessa forma, podemos escrever a versão mais simples do código union-find.


3. Compressão de caminho

A união mais simples e a eficiência de busca são relativamente baixas. Por exemplo, considere o seguinte cenário:

insira a descrição da imagem aqui
Agora queremos merge(2,3), então encontramos 1 de 2, fa[1]=3, então fica assim:
insira a descrição da imagem aqui
Então encontramos outro elemento 4, e precisamos executar merge(2,4):
insira a descrição da imagem aqui
de 2 Encontre 1, depois encontre 3, então fa[3]=4, então fica assim:
insira a descrição da imagem aqui
todos devem ter um sentimento, isso pode formar uma longa cadeia, à medida que a cadeia fica cada vez mais longa, queremos começar do inferior Fica cada vez mais difícil encontrar o nó raiz.

Como resolvê-lo? Podemos usar o método de compactação de caminho. Como nos preocupamos apenas com o nó raiz correspondente a um elemento, queremos que o caminho de cada elemento até o nó raiz seja o mais curto possível, de preferência apenas um passo, assim:

insira a descrição da imagem aqui
Na verdade, isso também é muito bom para conseguir. Enquanto estivermos no processo de consulta, podemos definir o nó pai de cada nó ao longo do caminho como o nó raiz. Podemos evitar muitos problemas na próxima vez que olharmos para cima.

Mas, na verdade, como a compactação de caminho é realizada apenas no momento da consulta e apenas um caminho é compactado , a estrutura final da pesquisa de união ainda pode ser complicada.

Por exemplo, agora temos uma árvore mais complexa que precisa ser mesclada com um conjunto de um elemento:
insira a descrição da imagem aqui

Se quisermos mesclar(7,8) neste momento, se pudermos escolher, devemos definir o nó pai de 7 para 8 ou definir o nó pai de 8 para 7?

Claro que o último. Porque se o nó pai de 7 for definido como 8, a profundidade da árvore (o comprimento da cadeia mais longa na árvore) será aprofundada e a distância de cada elemento na árvore original até o nó raiz se tornará maior, e então encontraremos o nó raiz.O caminho será correspondentemente mais longo. Embora tenhamos compactação de caminho, a compactação de caminho também consome tempo. E definir o nó pai de 8 para 7 não terá esse problema, pois não afeta nós não relacionados.

insira a descrição da imagem aqui

Isso nos inspira: devemos mesclar árvores simples em árvores complexas, e não o contrário. Porque após a fusão dessa maneira, o número de nós cuja distância até o nó raiz se torna maior é relativamente pequeno.


4. Realização geral

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

Veja também: Cheque Sindical

Acho que você gosta

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