MC3305 Algoritmos e Estruturas de Dados II Aula 19 – Conjuntos disjuntos (Union-find) Prof. Jesús P. Mena-Chalco [email protected] 2Q-2015 1 Números de Ackermann 2 3 Ackermann A função de Ackermann é definida recursivamente para números inteiros não negativos, m e n, como: 4 Ackermann 5 Ackermann Fonte: https://helloacm.com/ackermann-function/ 6 7 8 Geralmente a função de Ackermann é utilizada para testar um compilador, quando considerada a capacidade de otimização da recursão. Recursão extrema! 9 Função inversa de Ackermann 10 Conjuntos disjuntos 11 Estruturas para conjuntos disjuntos Union-Find Coleção {S1,..., Sk} de conjuntos disjuntos dinâmicos (que variam ao longo do tempo) Cada conjunto Sk é identificado por um representante, que é um membro do conjunto. Geralmente não importa quem é o representante 8 7 3 2 1 9 0 6 5 4 0 1 2 3 4 5 6 7 8 9 C 5 1 1 35 5 5 3 35 12 Operações Make_Set(x): Cria um novo conjunto cujo único elemento é apontado por x. – x não pode pertencer a outro conjunto da coleção. Union(x, y): Executa a união dos conjuntos que contêm x e y, digamos Sx e Sy, em um conjunto único. – Sx ∩ Sy é vazio (conjuntos disjuntos). – O representante de S = Sx ∪ Sy é um elemento de S. Find(x): Devolve um ponteiro para o representante (único) do conjunto que contém x. 13 Aplicações Algumas aplicações envolvem o agrupamento de N elementos em uma coleção de conjuntos disjuntos, ou seja, um particionamento dos elementos em conjuntos. Alguns usos – – Problemas de grafos E.g. árvore geradora mínima, componentes conexas Agrupamento (clustering) Envolvido em mineração de dados e reconhecimento de padrões 14 Componentes conexas Determinação das componentes conexas de um grafo G(V,E) 15 Componentes conexas Componentes_Conexas (G(V,E)) Para cada vértice v ∈ V Make_Set(v); Para cada aresta (u,v) ∈ E Se (Find_Set(u) ≠ Find_Set(v)) Union(u,v); 16 Componentes conexas Mesma_Componente (u,v) Se (Find_Set(u) = Find_Set(v)) devolve verdadeiro; Senão devolve falso; 17 Componentes conexas 18 Componentes conexas 19 Representação de conjuntos disjuntos A maneira mais simples de implementar uma estrutura de dados para conjuntos disjuntos consiste em representá-los como um vetor onde o índice de cada elemento contém o representante do conjunto ao qual ele pertence. 8 7 3 2 1 9 0 6 5 4 0 1 2 3 4 5 6 7 8 9 C 5 1 1 35 5 5 3 35 Find_Set (O(1)) Make_Set (O(1)) Union: (O(n)) (todos os elementos de um dos conjuntos deverão ter seus representantes alterados) 20 Representação de conjuntos disjuntos Outra maneira simples de implementar uma estrutura de dados para conjuntos disjuntos consiste em representar cada conjunto como uma lista encadeada. – O primeiro elemento da lista é o representante Exemplo: S = {a,d,e} head tail Find_Set (O(1)) Make_Set (O(1)) Union? 21 Conjuntos disjuntos com listas encadeadas Podemos implementar Union(x,y) adicionando a lista de x no final de y – y vira o representante do conjunto x y 22 Conjuntos disjuntos com listas encadeadas Podemos implementar Union(x,y) adicionando a lista de x no final de y – y vira o representante do conjunto x y y x 23 Conjuntos disjuntos com listas encadeadas Podemos implementar Union(x,y) adicionando a lista de x no final de y – y vira o representante do conjunto x y y x O número de trocas de ponteiros para o representante pode ser quadratica em relação ao número de elementos Problema: Todos os ponteiros dos elementos da lista de x devem ser apontados para y. 24 Conjuntos disjuntos com listas encadeadas Exercício: forneça uma sequência de operações Union em que o número de troca de ponteiros para o representante seja de ordem quadrática em relação ao número de elementos (n2) 25 Conjuntos disjuntos com listas encadeadas Exercício: forneça uma sequência de operações Union em que o número de troca de ponteiros para o representante seja de ordem quadrática em relação ao número de elementos (n2) 26 Conjuntos disjuntos com listas encadeadas Instância – – – – exemplo Seja n o numero de operações Make_Set. Seja m o numero total de operações Union e Make_Set Suponha que tenhamos X1,...,Xn objetos Então executamos uma sequência de n operações Make_Set seguidas por n-1 operações Union, de forma que m = 2n-1. 27 Conjuntos disjuntos com listas encadeadas n operações Make_Set (θ(n)) Pelo fato da i-ésima operação Union atualizar i objetos, o número total de objetos atualizados por todas as n-1 operações Union é θ(n2) n-1 Número total de operações = θ(n + ∑ i) = θ(n+n2) = θ(n2) i=1 28 Conjuntos disjuntos com listas encadeadas n operações Make_Set (θ(n)) Pelo fato da i-ésima operação Union atualizar i objetos, o número total de objetos atualizados por todas as n-1 operações Union é θ(n2) n-1 Número total de operações = θ(n + ∑ i) = θ(n+n2) = θ(n2) i=1 Como resolver o problema da União? Heurística da união ponderada 29 Heurística da união ponderada 30 Heurística da união ponderada De acordo com a implementação anterior, cada operação Union leva tempo médio θ(n) pela possibilidade de inserir uma lista mais longa em uma lista pequena. Uma alternativa para melhorar a complexidade é adotar a heurística da união ponderada: – – Cada representante armazena o comprimento da lista de elementos do seu conjunto A inserção é sempre feita da lista menor na lista maior 31 Heurística da união ponderada De acordo com a implementação anterior, cada operação Union leva tempo médio θ(n) pela possibilidade de inserir uma lista mais longa em uma lista pequena. Uma alternativa para melhorar a complexidade é adotar a heurística da união ponderada: – – Cada representante armazena o comprimento da lista de elementos do seu conjunto A inserção é sempre feita da lista menor na lista maior Exercício: pensem em uma sequência de operações Union de melhor caso e em outra de pior caso. 32 Heurística da união ponderada Melhor caso: um dos conjuntos tem sempre tamanho 1 – – Cada operação envolve apenas a atualização de ponteiros de um único elemento. n operações union: θ(1)*n = θ(n) 33 Heurística da união ponderada Melhor caso: um dos conjuntos tem sempre tamanho 1 – – Cada operação envolve apenas a atualização de ponteiros de um único elemento. n operações union: θ(1)*n = θ(n) Pior caso: os dois conjuntos tem sempre tamanhos idênticos. ... 4*(n/8) 2*(n/4) 1*(n/2) h ∑(n/2) i=1 h = log2n θ(n log n) 34 Heurística da união ponderada Pergunta: É possível fazer com que a união seja mais eficiente usando outra estrutura de dados? 35 Heurística da união ponderada Pergunta: É possível fazer com que a união seja mais eficiente usando outra estrutura de dados? Resposta: Sim! Floresta de conjuntos disjuntos 36 Conjuntos disjuntos 37 Floresta de conjuntos disjuntos Representação por meio de árvores A raiz da árvore contém o representante do conjunto O filho aponta para o pai União 38 Floresta de conjuntos disjuntos Na forma apresentada, a estrutura é tão lenta quanto aquela que utiliza listas encadeadas. Make_Set: cria uma árvore com um único vértice (θ(1)) Find_Set: segue os ponteiros para os pais até atingir a raiz (θ(n)) Union: faz a raiz de uma árvore apontar para a raiz da outra – – exige saber quem são as raizes das árvores dos dois elementos, ou seja, envolve 2 operações Find_Set (θ(n)) Ou seja, no total, n operações Union tem complexidade (θ(n2)) 39 http://www.cs.usfca.edu/~galles/JavascriptVisual/DisjointSets.html 40 Heurística para floresta de conjuntos disjuntos União ponderada (union by rank): faça a raiz da árvore com menor rank (altura) apontar para a raiz da árvore com maior rank. 41 Heurística para floresta de conjuntos disjuntos União ponderada (union by rank): faça a raiz da árvore com menor rank (altura) apontar para a raiz da árvore com maior rank. Compressão de caminhos (path compression): durante uma busca (Find_Set) faça os nós do caminho apontar para a raiz Find_Set(a) 42 Heurística para floresta de conjuntos disjuntos Compressão de caminhos (path compression): durante uma busca (Find_Set) faça os nós do caminho apontar para a raiz 0 4 1 2 5 6 0 3 7 8 9 4 10 1 2 3 5 6 7 8 9 10 Find_Set(9) 43 Implementação envolvendo união ponderada e compressão de caminhos Make_Set (x) p[x] = x; rank[x] = 0; Find_Set (x) se x ≠ p[x] p[x] = Find_Set(p[x]); devolve p[x]; Union (x, y) x = Find_Set(x); y = Find_Set(y); se rank[x] > rank[y] p[y] = x; senão p[x] = y; se rank[x] == rank[y] rank[y]++; 44 Heurística para floresta de conjuntos disjuntos Separadamente, ambas as heurísticas melhoram o tempo de execução das operações Sozinha, a heurística da union by rank induz o mesmo tempo de execução da heurística de união ponderada usada na representação por listas ligadas (θ(n log n)) Sozinha, a heurística de compressão de caminho induz tempo de execução (n + f log(n)) para f < n onde f é o número de operações Find_Set 45 Heurística para floresta de conjuntos disjuntos Quando ambas as heurísticas são aplicadas, o tempo de execução se torna θ(n α(n)), em que α(n) é o inverso da função de Ackermann Função – de Ackermann Para k ≥ 0 e j ≥ 1 inteiros, define-se Ak(j) como: 46 Heurística para floresta de conjuntos disjuntos Função de Ackermann – A0(j) = j+1 – Ai+1(j) = Ai(Ai(Ai...(j))), sendo Ai aplicado j vezes – A1(j) = 2j+1 – A2(j) = A1(A1(A1...(j))) = 2j+1 – A3(j) = A2(A2(A2...(j))) = 2^(2^(2^...(j))) j+1 Função de Ackermann inversa α(n) = min{k : Ak(1) ≥ n} = 0 para 0 ≤ n ≤ 2 1 para n = 3 2 para 4 ≤ n ≤ 7 3 para 8 ≤ n ≤ 2047 4 para 2048 ≤ n ≤ 22048 47 Heurística para floresta de conjuntos disjuntos Quando ambas as heurísticas são aplicadas, o tempo de execução se torna θ(n α(n)), em que α(n) é o inverso da função de Ackermann Para todos os fins práticos, podemos considerar α(n) ≤ 4 – θ(n) 48 Heurística para floresta de conjuntos disjuntos 49 http://www.cs.usfca.edu/~galles/JavascriptVisual/DisjointSets.html 50 Exercício Exercício (21.2-2 do Cormen 2nd Ed): Mostre a estrutura de dados resultante e as respostas devolvidas pelas operações Find_Set no seguinte programa, usando a) lista ligada com heurística de união ponderada b) floresta de conjuntos disjuntos com união ponderada e compressão de caminhos – (assuma que, caso os conjuntos contendo i e j sejam do mesmo tamanho, Union(i,j) concatena a lista de j na lista de i) for (i = 1; i <= 16; i++) Make_Set(i); for (i = 1; i <= 15; i = i + 2) Union(i,i+1); for (i = 1; i <= 13; i = i + 4) Union(i,i+2); Find_Set(2); Find_Set(9); 51 Árvore geradora mínima (Kruskal) 52 53