O problema é representar dinamicamente uma família de subconjuntos disjuntos de um universo de modo que cada conjunto é identificado por um representante que é um elemento do próprio conjunto e que não tem nenhuma outra propriedade especial, o importante é que, dado que o conjunto não mudou, toda vez que se pergunta pelo representante a resposta deve ser a mesma. Para as nossas aplicações podemos assumir, sem perda de generalidade, que .
Essas estruturas devem comportar as operações , e sobre :
Queremos uma representação que seja eficiente, ou seja, o objetivo é a análise da complexidade das operações em algumas estruturas de dados. Para tal, suporemos que serão executadas operações e em uma ordem não conhecida a partir da criação de conjuntos unitários, o que chamamos de configuração inicial.
Representação usando vetor
Usamos um vetor indexado por e a idéia é que dois elementos do conjunto universo e estão no mesmo conjunto se e somente se as posições e do vetor têm o mesmo conteúdo, assim . Escolhemos como representante do conjunto o menor elemento. Dessa forma, os algoritmos para as operações , e ficam da seguinte forma
cria(x) C[x] ← x. busca(x) devolva(C[x]). união(x,y) k ← min{busca(x),busca(y)}; Para Cada i de 1 até n Se ( C[i]=C[x] ou C[i]=C[y] ) C[i]← k;
Claramente, cada união tem custo .
Teorema 1 A complexidade de operações num universo com elementos é .
Observamos que a complexidade de pior caso não está superestimada como mostra a seguinte sequência de operações depois de: , que estabelece a configuração inicial
Representação usando listas ligadas
Cada conjunto é dado por uma lista ligada. A cada elemento do universo está associado um nó com os atributos que aponta para o próximo elemento da lista e que aponta para o representante do conjunto.
Por exemplo, se por ora consideramos o universo , então um esquema da representação dos subconjuntos , e é dada na figura:
As funções e operam ligeiramente diferente da que vimos:
Um é feito em tempo constante e o custo de é basicamente o custo das atualizações dos ponteiros. Dessa forma, a complexidade de tempo das operações nessa representação é da mesma ordem de grandeza da representação por vetor. A mesma sequência de operações após , ,
em que atualiza ponteiros dos elementos do conjunto do e, portanto,
resulta em operações.
Adotando a seguinte heurística, a complexidade melhora substancialmente:
Atualizar sempre a menor lista: pressupondo um atributo que armazena o número de elementos de , numa união de duas listas concatenamos a menor lista no final da maior lista.
Para um elemento do universo, a primeira vez que é atualizado resulta numa lista de tamanho pelo menos . A segunda vez que é atualizado resulta numa lista de tamanho pelo menos e assim por diante. Logo, o número de atualizações que [ ] de um elemento qualquer é atualizado é no máximo .
Proposição 2 Supondo a heurística se o representante de muda vezes, então o tamanho da lista que contém é pelo menos .
Demonstração: A prova é por indução em que vale: se o representante de muda vezes, então o tamanho da lista que contém é pelo menos , para qualquer elemento do universo. Para , é o único elemento na lista dele e portanto temos a base da indução. Para assumimos que se atualizações foram feitas então a lista de tem tamanho pelo menos . Na próxima atualização do representante de ocorre numa união com uma lista que tem pelo menos o mesmo número de elementos da lista de , resultando numa lista com pelo menos elementos. O resultado segue do Princípio da Indução Finita.
Após uma sequência de uniões, a partir da configuração inicial, o maior conjunto tem no máximo elementos, portanto o representante de um elemento pode ter mudado no máximo vezes.
Corolário 3 O representante de um elemento qualquer do universo muda no máximo vezes.
Dessa forma o custo total para atualização dos ponteiros dos objetos é no máximo .
Teorema 4 O tempo para operações num universo com elementos é .
— Representação por floresta —
A idéia é que cada subconjunto seja representado por uma árvore de modo que cada elemento do subconjunto seja representado por um nó da árvore. Nessa árvore cada nó não-raiz aponta para um pai e a raiz é o representante; o pai da raiz é ela mesma (e só raiz tem essa propriedade). Dessa forma, cria em tempo constante uma nova árvore que contém somente a raiz, é feita em tempo constante mudando-se o pai de uma das raízes e , que tem tempo dependente da profundidade do elemento na árvore, retorna um ponteiro para uma raiz.
Uma representação com árvores dos subconjuntos , e e o resultado de .
Desa forma agora é barato enquanto que é caro, tem custo da ordem da altura da árvore. Claramente, essa estrutura pode ser muito ruim se a árvore formada for uma lista linear entretanto, com algumas heurísticas bastante simples o resultado final melhora substancialmente. Começamos com a seguinte heurística para união de dois subconjuntos, onde tamanho de uma árvore é o número de nós
União por tamanho: árvore de menor tamanho é colocada como sub-árvore da árvore de maior tamanho.
Definimos a altura de uma árvore como a maior distância (número de ponteiros percorridos) de um nó até a raiz e temos a seguinte relação entre tamanho e altura resultantes de uma sequência de uniões com a heurística de união por tamanho.
Lema 6 Num universo com elementos e usando união por tamanho, após uma sequência qualquer de uniões a partir da configuração inicial vale que o número de nós de cada árvore é pelo menos , onde é a altura da árvore.
Demonstração: Vamos denotar por o número de nós na árvore que contém e por a altura da árvore que contém . Vamos provar o lema por indução no número de uniões: para todo , após uniões para todo .
Com uniões estamos na configuração inicial, ou seja, e para todo , ou seja, a base da indução é verdadeira. Para , vamos assumir que após uniões temos , para todo . Se a -ésima união é então para todo com , ou seja, a relação entre tamanho e altura não muda para as árvores que não estão envolvidas na união. Vamos denotar por e o tamanho e a altura da árvore resultante da união e , , e os tamanhos e as alturas antes da -ésima união.
Se então . Agora, portanto .
Podemos assumir, sem perda de generalidade, que e dessa forma ou . A demonstração segue em dois casos:
se então e pela hipótese de indução , logo ;
se então (por quê?) e pela hipótese de indução , logo .
Em ambos os casos temos que após uniões para todo . A proposição segue pelo Princípio da Indução Finita.
Corolário 7 O tempo de operações num universo de tamanho é .
Demonstração: Cada toma tempo , resultando em . Cada é feito em tempo resultando em .
Assumimos que cada nó tem um atributo que armazena o número de elementos na árvore enraizada em os algoritmos ficam da seguinte forma
cria(x) pai[x] ← x; t[x] ← 1. busca(x) enquanto ( x não é pai[x] ) x ← pai[x]; devolva(pai[x]). link(x,y) t[x] ← t[x]+t[y]; pai[y] ← x. uniao(x,y) Se ( t[busca(x)] ≥ t[busca(y)] ) link (busca(x),busca(y)); Senão link(busca(y),busca(x)).
Como o tempo das operações são proporcionais a altura parece ser mais natural uma heurística que leva em conta a altura e não o tamanho da árvore.
União por altura: usamos a altura, ao invés do tamanho pendurando a árvore mais baixa na raiz da árvore mais alta.
Com essa heurística o Lema 6 continua valendo, e consequentemente o corolário. Os algoritmos para , e são os algoritmos , e com trocado por , que é iniciado com . Em só precisamos atualizar se as árvore envolvidas na união têm a mesma altura,
cria(x) pai[x] ← x. h[x] ← 0 link(x,y) Se ( h[x]=h[y] ) h[x] ← h[x]+1; pai[y] ← x.
Busca com compressão de caminhos e união por rank: quando fazemos um aproveitamos o percurso de até a raiz da árvore e desviamos os ponteiros , de todo nesse caminho, para a raiz; na união penduramos a árvore de menor rank na raiz da de maior rank.
Notemos que se estivermos usando união por altura, a compressão de caminhos modifica a altura da árvore e a atualização custa muito caro, portanto não a atualizamos e usamos como uma estimativa superior para a altura, que chamaremos de rank.
cria(x) pai[x] ← x. RANK[x] ← 0 busca(x) Se ( x não é pai[x] ) pai[x] ← busca(pai[x]); devolva(pai[x]). link(x,y) Se ( RANK[x] = RANK[y] ) RANK[y] = RANK[y] + 1 pai[x] ← y uniao(x,y) rx ← busca(x); ry ← busca(y); Se ( RANK[rx] ≤ RANK[ry] ) link (rx,ry); Senão link(ry,rx).
Essa duas heurísticas juntas, a união por rank e a compressão de caminhos é bastante eficiente. O próximo resultado, sobre a complexidade de operações num universo de tamanho , envolve uma função que cresce muito vagarosamente; na prática podemos assumir que .
Teorema 8 (Tarjan, 1975) O tempo gasto com operações num universo de tamanho é .
Não demonstraremos esse teorema, o resultado que mostraremos a seguir é um pouco menos preciso.
Observação A função é uma função que cresce muito vagarosamente.
É, num certo sentido, a inversa da função de Ackermann ligeiramente modificada, dada por
conhecida por crescer muito rapidamente.Por exemplo , , , com ocorrências de . Ainda
(maior que o número estimado de átomos no universo observável), e
Para efeitos práticos podemos assumir .
Por exemplo
— — — — A(4,1)
Denotamos por o número de vezes que temos que iterar até que o valor obtido seja menor ou igual a , por exemplo,
notemos que .
Começamos a análise com as seguintes propriedades de rank.
Proposição 9 Quando um nó deixa de ser raiz o seu rank não muda nas próximas operações.
Proposição 10 para todo que não é raiz.
Segue por indução no número de operações executadas a partir da configuração inicial que essa propriedade é invariante pelas operações de e .
Proposição 11 Se um elemento tem rank então o tamanho da subárvore enraizada nele é pelo menos .
A prova é similar ao do Lema 6, por indução. Se então o elemento é raiz da árvore que contém pelo menos ele proóprio. Se tem rank () então em algum momento teve rank e foi raiz de uma árvore que foi unida a outra de rank , portanto da união resulta uma árvore com pelo menos elementos.
Agora, particionamos os elementos de em partes de acordo com o rank final do nó associado: o Grupo tem os elementos de rank e ; o Grupo tem os elementos de rank ; o Grupo tem os elementos de rank ; o Grupo tem os elementos de rank ; o Grupo tem os elementos de rank ; o Grupo tem os elementos de rank ; de um modo geral para o Grupo tem os elementos de rank em com .
Proposição 12 A quantia de nós cujo rank está no intervalo é no máximo .
De fato, pela Proposição 11, não pode haver mais que elementos de rank , portanto o número de elementos no intervalo mencionado é no máximo
Num busca(x) os apontadores no caminho de até a raiz serão redirecionados para a raiz. O custo da operação busca(x) é porporcional ao número de ponteiros percorridos de até a raiz. Classificamos esses apontadores () em 2 tipos:
- Tipo 1 são os apontadores com e em grupos diferentes ou é raiz, e
- Tipo 2 são os apontadores com e no mesmo grupo.
O número de ponteiros Tipo 1 que cada operação percorre é no máximo , uma vez que existem apenas grupos e, pela Proposição 10, o percurso incrementa o rank. Portanto esses contribuem com
Para os ponteiros do Tipo 2, o ponto é que cada vez que uma operação passa pelo elemento então passará a aopontar para a raiz da árvore corrente, portanto o rank de aumenta de pelo menos Se está no grupo então pode ser incrementado menos que vezes antes que passe para o Tipo 1. Esses contribuem com
Assim, o custo total é
Teorema 13 O tempo gasto com operações num universo com elementos é .