• Nenhum resultado encontrado

Algoritmos Dividir-e-Conquistar

Algoritmo 5.9 Algoritmo de Prim para construir uma

5.2.3 Algoritmos Dividir-e-Conquistar

A versão recursiva da busca binária (Algoritmo 5.4) funciona dividindo a lista ao meio e chamando a si mesma recursivamente em cada parte. Depois de divisões suficientes, a lista é pequena o bastante (um

elemento) para o caso base se aplicar. Esse é um exemplo de urn algoritmo dividir-e-conquistar. Esse tipo de algoritmo recursivo divide o problema dado (geralmente ao meio) e tenta resolver cada parte. Uma vez que as sucessivas divisões rapidamente se tornam pequenas, os algoritmos dividir-e-conquistar tendem a funcionar rapidamente.

A seguir, uma alternativa recursiva para o Exemplo 4.54 da Seção 4.5. Se você mantiver o paradigma dividir-e-conquistar em mente, é fácil ver corno definir a função.

A lgoritm o 5.10 Encontrando o maior elemento em

uma lista.

Condições prévias: X = {xx, ..., xn} é um conjunto de elementos no qual ^ define uma ordenação total. Condições posteriores: BuscarMax(A) = m a x {x1,

..., z j . função BuscarMax(A) se X = {a;} então retornar x senão a «— BuscarMax ({rq, :q, ..., a j^ j} ) b <— BuscarMax {{xin/2\ + i, ..., £„}) se a -< b então retornar b senão retornar a

Uma árvore binária é um modelo natural para traçarmos a execução de um algoritmo dividir-e- conquistar. Cada vértice representa uma chamada da função, e os filhos de um vértice X representam as chamadas recursivas feitas por X. Os rótulos em cada aresta representam o valor retornado do vértice de baixo. Quando desenhamos uma árvore como essa, começamos com a chamada original na raiz e vamos descendo. Uma vez desenhados todos os vértices, podemos começar a preencher os rótulos (valores retornados) de baixo para cima.

B uscarM ax( {5, 3, 2,4})

BuscarMax({5}) BuscarMax({3}) BuscarMax({2}) BuscarMax({4})

A Figura 5.8 mostra uma árvore como essa. A primeira chamada para BuscarMax ({5, 3, 2, 4}) dá

origem a duas chamadas recursivas: BuscarMax ({5, 3})

e BuscarMax ({2, 4}). Já estas fazem duas chamadas cada para BuscarMax{n} para algum n, e, uma vez que

cada um desses valores de parâmetro é um conjunto da forma {n}, cada chamada retorna n. Quando 5 e 3 são retornados para a sub-rotina BuscarMax ({5, 3}), ela

?

faz a comparação 5 3 e retorna 5. De forma similar, ?

BuscarMax ({2, 4}) faz a comparação 2 A 4 e retorna 4.

Finalmente, a chamada original BuscarMax ({5, 3, 2, 4})

?

compara 5 A 4 e retorna 5, o valor máximo no conjunto. Note que a abordagem dividir-e-conquistar para encon­ trar o elemento máximo faz comparações diferentes da versão iterativa no Exemplo 4.54.

O paradigma dividir-e-conquistar conduz a um bom algoritmo para ordenar os elementos q , q , ..., xn em um vetor. Na Seção 4.5, estudamos a ordenação por bolhas. Esse algoritmo simples funciona bem para vetores pequenos, mas pode ser um tanto lento para conjuntos grandes de dados. A ordenação por fusão* funciona de forma mais eficiente dividindo e conquistando.

A ideia principal por trás da ordenação por fusão é simples: dividir o vetor em dois vetores menores, ordenar os vetores menores (recursivamente) e juntar de volta os vetores menores. A parte complicada é o processo da fusão, que é feito pelo Algoritmo 5.11.

Veremos esse algoritmo com mais cuidado na próxima seção; ele realmente não é tão complicado quanto parece à primeira vista. Por ora, devemos apenas aceitar a ideia de que ele pega dois vetores ordenados e os coloca juntos para fazer um grande vetor ordenado. Uma vez feito esse procedimento para fundir dois vetores ordenados, a ordenação por fusão é simples de ser escrita usando o paradigma dividir-e-conquistar. Veja o Algoritmo 5.12.

A lgoritm o 5.11 Sub-rotina de fusão.

Condições prévias: yu y2, ..., yh q, q, ..., zm G U, U é totalmente ordenado por <,

í/i < l/2 < ... < yi e ^ ^ < ... < zm.

Condições posteriores: Retornar xl, q , ..., xn, em que

n = l + m, X! 2= q < ... < xM e {q, q , ..., q} = {&,

&» •■•» Vb A» •••> Ai}•

função Fundir (yu y2, ..., yb q, q, ..., zm G U)

i 1, j <- 1, k <- 1

enquanto k ^ l + m fazer r se i > l então

*Em inglês, merge sort. (N.T.)

Xk <r- Z} L- j j + 1 senão se j > m então r xk <- Vi L Í <— Í + 1 senão se y{ ^ q então r a* <- y{ L Í <— Í + 1 senão r xk <- Zj >- 3 j + 1 l k <— k + 1 retornar xu x2, ..., xl+m

Podemos traçar a ordenação por fusão com uma avaliação de cima para baixo. Para tornar as coisas mais interessantes, vamos ver o que ela faz com um vetor de sete elementos com os valores 12, 3, 5, 17, 2, 8, 9.

OrdF (12, 3, 5, 17, 2, 8, 9)

= Fundir (OrdF (12, 3, 5) , OrdF (17, 2, 8, 9) )

= Fu n d i r (F undir(O r d F (12) , O r d F (3, 5) ), Fundir (OrdF (17, 2), OrdF (8, 9) ) )

= Fundir(Fundir(12,

Fundir(OrdF(3), O r d F (5) ) ) , Fundir(Fundir(OrdF(17) , O r d F (2)),

Fundir (OrdF (8) , OrdF (9))))

= Fundir (Fundir (12, Fundir (3, 5) ),

Fundir (Fundir (17, 2), Fundir (8, 9) ) )

= Fundir (Fundir (12, (3, 5) ) , Fundir ((2, 17) , (8,

9) ) )

= Fundir ( (3, 5, 12), (2, 8, 9, 17) )

= 2, 3, 5, 8, 9, 12, 17

A lgoritm o 5.12 Ordenação por Fusão.

Condições prévias: xu ..., xnG U, um conjunto que é totalmente ordenado por <.

Condições posteriores: Retornar x i, £2, . . . ,x n, quan­ do { f i , x 2, .. . , £n} = {rq, X2, ..., xn } e ^1 < x 2 < • ■• < £ « • função OrdF(q, q, ..., xn G U) se n = 1 então retornar q senão l <— nf 2

retornar Fusão (OrdF (q, q, ..., q ) , OrdF (q+/, q+2, ..., xn) )

Na próxima seção iremos mostrar que a ordenação por fusão é mais eficiente que a ordenação por bolhas. De fato, em um certo sentido, é imposível encontrar um algoritmo de ordenação mais rápido. Veremos uma razão matemática para isso na Seção 5.4.

E x e rc íc io s 5.2

1. Liste a ordem na qual os vértices são visitados para (a) um percurso em pré-ordem, (b) um percurso em pós-ordem e (c) um percurso em ordem na árvore da Figura 5.9.

2. Faça três cópias da árvore na Figura 5.10 e desenhe caminhos que indiquem a ordem na qual os vértices são visitados por (a) um percurso em pré-ordem, (b) um percurso em pós-ordem e (c) um percurso em ordem.

3. Recorra à arvore na Figura 5.10. Suponha que cada vértice da árvore representa uma tarefa e cada vértice filho representa uma tarefa que deve ser feita antes da tarefa do vértice pai. (As vezes, chamamos uma árvore como essa de árvore da

dependência.) Qual método de percurso nos dá

uma ordem apropriada na qual podemos fazer essas tarefas?

4. Suponha que a árvore na Figura 5.10 modele as relações evolutivas entre um conjunto de espécies de animais, em que cada vértice representa uma espécie e os descendentes de um vértice repre­ sentam seus descendentes biológicos. Qual método de percurso de árvore (pré-ordem, pós-ordem ou em ordem) dá uma possível ordem cronológica para quando essas espécies podem ter se originado? Essa ordem é única? Explique.

5. Desenhe e rotule uma única árvore binária com seis vértices (A, ..., F) de forma que um percurso em ordem dê A, B, (7, D, E, F e um percurso em pré-ordem dê (7, B, A, E, D, F.

6. Desenhe e rotule uma árvore binária com seis vértices (A , ..., F) de forma que um percurso em ordem e um percurso em pós-ordem deem ambos A, B, (7, D, E, F.

7. Os três algoritmos de percurso de árvore (Algo­ ritmos 5.5, 5.6 e 5.7) também podem ser consi-

A

Figura 5.9 A árvore binária para o Exercício 1.

s

F ig u ra 5.10 A árvore binária para os Exercícios 2, 3 e 4.

derados algoritmos dividir-e-conquistar. Explique como o paradigma dividir-e-conquistar pode ser aplicado a esses algoritmos.

8. A solução para o Exemplo 5.7 inclui um traço detalhado do algoritmo de percurso em pré-ordem usando indentação para mostrar todas as chamadas recursivas. Escreva um traço similar para (a) o percurso em pós-ordem e (b) o percurso em ordem da árvore binária na Figura 5.3.

9. Sejam X = {xly a*, ..., x j, Y = {ylt y2, ..., ym) e

Z = {^, ^2, ..., zn} conjuntos finitos. Escreva um

algoritmo que percorra o conjunto X X Y X Z

usando laços-par a encaixados.

10. Recorra à Definição 3.5 no Capítulo 3. Escreva um algoritmo para percorrer uma SLista.

11. Seja G um grafo conexo no qual cada vértice tem grau 4. Escreva em pseudocódigo um algoritmo recursivo de percurso que visite todos os vértices de G, começando por algum vértice específico v. Inclua as condições prévias e posteriores.

12. Use o algoritmo de Prim (Algoritmo 5.9) para cons­ truir uma árvore espalhada mínima para a rede na Figura 5.11. Qual é o peso total dessa árvore espalhada mínima?

13. Use o algoritmo de Prim para constuir uma árvore espalhada mínima para a rede na Figura 5.12. Qual é o peso total dessa árvore espalhada mínima? Existe uma única árvore espalhada mínima? Expli­ que. 1 3 5 8 1 , 2 , 2 5 5 3 5 10 8 3 4 7 1 7 5 5 7 3

z

F i g u r a 5 .1 2 Rede para os Exercícios 13, 14c e 15.

14. O algoritmo de Kruskal nos dá uma outra maneira para encontrarmos uma árvore espalhada mínima para uma rede.

A lg o ritm o 5.13 Algoritmo de Kruskal para construir uma árvore espalhada mínima.

Condições prévias: J\f é uma rede conexa com n > 2 vértices.

Condições posteriores: T é uma árvore espalhada mínima para Aí.

ei

T <— •--- •, em que ex e a aresta mais curta de Aí.

para i £ {2, ..., n — 1} fazer

r ex <— a aresta mais curta que adicio­ nada a Tnão forma um circuito

l Adicionar aresta ex (e seus vértices)

a T

(a) O algoritmo de Kruskal é um algoritmo guloso? Explique.

(b) Use o algoritmo de Kruskal para construir uma árvore espalhada mínima para a rede na Figura 5.11.

(c) Use o algoritmo de Kruskal para construir uma árvore espalhada mínima para a rede na Figura 5.12.

15. Algumas vezes os algoritmos gulosos não funcionam. Considere a tarefa de encontrar o caminho mais curto entre dois vértices em uma rede. Escreva um

algoritmo guloso que tome como entrada uma rede, um ponto de partida A, e um ponto de chegada

Z, e tente encontrar o menor caminho de A para Z. Teste o seu algoritmo no grafo da Figura 5.12.

Mostre que ele falha em encontrar o caminho mais curto.

16. Lembre que uma coloração de um grafo é uma atribuição de cores aos vértices de tal forma que dois vértices da mesma cor nunca estejam conec­ tados por uma aresta. 0 algoritmo a seguir tenta produzir uma coloração para os vértices de um grafo

G usando o menor número possível de cores.

enquanto ( G tem vértices sobrando para colorir) fazer

Pegar uma nova cor x T- C. C C U {x}

Atribuir a cor X para o maior número possível de vértices de G, de modo que nenhuma aresta conecte vértices da mesma cor.

(a) Que tipo de algoritmo é esse?

(b) Encontre um grafo para o qual esse algoritmo falha em produzir uma coloração com o menor número possível de cores.

17. Seja T uma árvore binária cujos vértices são elementos de algum conjunto U. O algoritmo a seguir busca em T um valor-alvo t £ U e retorna verdadeiro se e somente se t é um vértice em T

função Buscar ( í £ U, TE. {árvores binárias}) se T e s t á vazio então retornar falso senão se t = a raiz de T então retornar verdadeiro senão

retornar (Buscar ( í, subárvore da esquerda de T) v Buscar {t, subárvore da direita de T) )

(a) Escreva uma avaliação de cima para baixo de Busca(17, T), em que T é a árvore na Figura 5.13.

(b) Que tipo de algoritmo é este? Explique.

18. Confronte as comparações feitas pela versão dividir- e-conquistar de BuscarMax (Algoritmo 5.10) com as comparações feitas pela versão de BuscarMax

que usa um simples laço-para (Exemplo 4.54). Use os dados x1, x2, aq, x4 = 5, 3, 2, 4.

19. Há uma avaliação de cima para baixo de OrdF (12, 3, 5, 17, 2, 8, 9) logo após o Algoritmo 5.11. Escreva esse traço na forma de uma árvore binária, como na Figura 5.8.

20. Faça um traço de OrdF (23, 5, 7, 13, 43, 21, 17, 2) usando uma avaliação de cima para baixo.

21. Escreva um algoritmo dividir-e-conquistar que calcule a soma de todos os elementos de um conjunto finito K = {Aq, /q, ..., kn} de números inteiros.

*22. Escreva um algoritmo guloso que construa a expansão de base dois de um número natural dado. (Dica: isto é como dar troco usando moedas de valores 1, 2, 4, 8, 16, ...)