CT-234 Estruturas de Dados, Aálise de Algoritmos e Complexidade Estrutural Carlos Alberto Aloso Saches
CT-234 5) Ordeação Resoluções simples, Lower boud, MergeSort, RadixSort
Algus algoritmos de ordeação A ordeação é o problema mais clássico da computação. Iicialmete, veremos algumas das suas resoluções mais simples: Ordeação pelo método da bolha (BubbleSort) Ordeação por seleção (SelectioSort) Ordeação por iserção (IsertioSort) Cosideraremos sempre a ordeação de um vetor v de ídices [1..].
Método da bolha (BubbleSort) É um dos algoritmos mais simples e cohecidos. Pricípio: Os elemetos vizihos são comparados e, caso estejam fora de ordem, são trocados. A propagação dessas comparações permite isolar o maior (ou o meor) elemeto do vetor. Repetido-se esse processo com as demais posições do vetor, é possível ordeá-lo completamete. Este método recebe o ome de bolha, pois os elemetos sobem até a sua posição fial, de modo semelhate a uma bolha em um tubo com água.
Exemplo para =8 No esquema abaixo, a bolha desce (como se o tubo estivesse de pota-cabeça) 1 44 44 12 12 12 12 6 6 2 55 12 42 42 18 6 12 12 3 12 42 44 18 6 18 18 18 4 42 55 18 6 42 42 42 42 5 94 18 6 44 44 44 44 44 6 18 6 55 55 55 55 55 55 7 6 67 67 67 67 67 67 67 8 67 94 94 94 94 94 94 94
Algoritmo BubbleSort(){ for (i=1; i<; i++) for (j=1; j<=-i; j++) if (v[j] > v[j+1]) { x = v[j]; v[j] = v[j+1]; v[j+1] = x; } } É leto, pois só faz comparações etre posições adjacetes. Pode ser melhorado com testes itermediários para verificar se o vetor já está ordeado. Mesmo assim, o tempo de pior caso é Θ( 2 ).
Ordeação por seleção (SelectioSort) Procedimeto: Selecioe o meor elemeto do vetor e troque-o com o que está a posição 1. Descosiderado a primeira posição do vetor, repita essa operação com as restates.
Exemplo com =6 Vetor iicial: 1 2 3 4 5 6 5 6 2 3 4 1 1 6 2 3 4 5 1 2 6 3 4 5 1 2 3 6 4 5 1 2 3 4 6 5 1 2 3 4 5 6
Algoritmo SelectioSort() { for (i=1; i<; i++) { mi = i; for (j=i+1; j<=; j++) if (v[j] < v[mi]) mi = j; x = v[mi]; v[mi] = v[i]; v[i] = x; } Sempre gasta tempo Θ( 2 )
Ordeação por iserção (IsertioSort) Semelhate ao método de ordeação das cartas de um baralho. Procedimeto: Verifica-se se o valor da posição 2 do vetor poderia ser colocado a posição 1. Repete-se este processo para as posições subsequetes, verificado-se o local adequado da iserção. A iserção de um elemeto a sua ova posição exige a movimetação de vários outros.
Exemplo com =6 34 8 64 51 32 21 Passo 1 34 8 64 51 32 21 8 34 64 51 32 21 elemeto que pode ser iserido mover 8 34 64 51 32 21 iserção
Exemplo com =6 Passo 2 8 34 64 51 32 21 64 Item que pode ser iserido Não ocorre iserção, pois esse elemeto já está o seu lugar
Exemplo com =6 Passo 3 8 34 64 51 32 21 51 8 34 64 32 21 Item que pode ser iserido mover 8 34 51 64 32 21 iserção
Exemplo com =6 Passo 4 8 34 51 64 32 21 32 8 34 51 64 21 Item que pode ser iserido mover 8 34 51 64 21 mover 8 34 51 64 21 mover 8 32 34 51 64 21 iserção
Exemplo com =6 Passo 5 8 32 34 51 64 21 8 32 34 51 64 21 Item que pode ser iserido mover 8 32 34 51 64 mover 8 32 34 51 64 mover 8 32 34 51 64 mover 8 21 32 34 51 64 Fial
Algoritmo IsertioSort() { for (i=2; i<=; i++) { x = v[i]; for (j=i; j>1 && x<v[j-1]; j--) v[j] = v[j-1]; v[j] = x; } } Quado o vetor está ordeado, gasta tempo Θ(). No etato, seu tempo de pior caso é Θ( 2 ).
Lower boud para a ordeação Até agora, apresetamos algoritmos que ordeam úmeros em tempo de pior caso Θ( 2 ). Por equato, esse é o osso upper boud para o problema da ordeação baseado em comparações. Seria possível calcular um lower boud para esse problema? Em outras palavras, desejamos ecotrar um limite iferior teórico para esse problema, isto é, a míima complexidade de tempo de quaisquer de suas resoluções algorítmicas.
Árvore de comparações Qualquer algoritmo de ordeação baseado em comparações pode ser represetado em uma árvore biária. Na raiz fica a primeira comparação realizada etre dois elemetos do vetor; os filhos, as comparações subsequetes. Deste modo, as folhas represetam as possíveis soluções do problema. A altura dessa árvore é o úmero máximo de comparações que o algoritmo realiza, ou seja, o seu tempo de pior caso.
Exemplo: com 3 valores distitos v[1]:v[2] < > v[2]:v[3] v[2]:v[3] < > < > v[1] < v[2] < v[3] < v[3] < v[2] < v[1] v[1]:v[3] v[1]:v[3] > < > v[1] < v[3] < v[2] v[3] < v[1] < v[2] v[2] < v[1] < v[3] v[2] < v[3] < v[1] Como estamos ordeado 3 elemetos, há 3! possíveis resultados
Geeralização Na ordeação de elemetos distitos, há! possíveis resultados, que correspodem às permutações desses elemetos. Portato, qualquer árvore biária de comparações terá o míimo! folhas. A árvore míima de comparações tem exatamete! folhas. Supodo que a altura dessa árvore seja h, etão LB() = h, ode LB() é o lower boud de tempo para a ordeação de elemetos. Sabemos que o úmero máximo de folhas de uma árvore biária de altura h é 2 h. Portato,! 2 h, ou seja, h lg! Logo, LB() lg!
Cálculo do lower boud Pela aproximação de Stirlig:! (2p) 1/2 e - Portato: lg! lg (2p) 1/2 + lg 1/2 + lg + lg e - lg! Θ(1) + Θ(log ) + Θ(.log ) - Θ() Como LB() lg!, etão LB() = Ω(.log ) Se ecotrarmos um algoritmo baseado em comparações que resolva a ordeação em tempo de pior caso Θ(.log ), ele será ótimo em termos de complexidade de tempo, e este problema estará computacioalmete resolvido.
Outra maeira de calcular lg! = lg (.(-1).(-2)..2.1) lg! = lg + lg (-1) + lg (-2) + + lg 2 + lg 1 lg! lg + lg (-1) +... + lg (/2) Todas essas /2 parcelas são maiores que lg (/2) Portato: lg! (/2).lg (/2) lg! = Ω(.log ) LB() = Ω(.log ) Qual o problema desta demostração?
MergeSort (Vo Neuma, 1945) Este algoritmo é um exemplo do paradigma Divisão-e-Coquista, e por isso tem 3 fases: Divisão: o vetor é dividido em duas metades Coquista: cada metade é ordeada recursivamete, dado origem a duas subsoluções Combiação: essas subsoluções são combiadas, formado a solução fial Codição de parada da recursão: quado for ordear apeas um elemeto. Este caso será a subsolução elemetar.
Exemplo para =8 Vetor origial 18 26 32 6 43 15 9 1 Vetor ordeado 1 6 9 15 18 26 32 43 18 26 32 6 43 15 9 1 6 18 26 32 1 9 15 43 18 26 32 6 43 15 9 1 18 26 6 32 15 43 1 9 18 26 32 6 43 15 9 1 18 26 32 6 43 15 9 1 18 26 32 6 43 15 9 1
Algoritmo MergeSort(i, f) { if (i < f) { m = ë(i+f)/2û; MergeSort(i, m); MergeSort(m+1, f); merge(i, m, f); } } Covém que o vetor aux teha mesmo tamaho de v e seja alocado como global Chamada iicial: MergeSort(1, ) merge(i, m, f) { i1 = i; i2 = i; i3 = m+1; while (i2 <= m && i3 <= f) if (v[i2] < v[i3]) aux[i1++] = v[i2++]; else aux[i1++] = v[i3++]; while (i2 <= m) aux[i1++] = v[i2++]; while (i3 <= f) aux[i1++] = v[i3++]; for (j=i; j<=f; j++) v[j] = aux[j]; } Complexidade de tempo de merge: Θ(f-i)
Complexidade de tempo do MergeSort T(1) = 1 (tempo costate) T() = 2T(/2) + Θ(), >1 Supodo = 2 k : T() = 2(2T(/2 2 ) + Θ(/2)) + Θ() = 2 2 T(/2 2 ) + 2Θ() T() = 2 2 (2T(/2 3 ) + Θ(/2 2 )) + 2Θ() = 2 3 T(/2 3 ) + 3Θ() Geeralizado: T() = 2 k T(/2 k ) + kθ() Substituido = 2 k : T() = + Θ().lg T() = Θ(.log ) A geeralização para qualquer ão muda a ordem de T()
Outro modo de calcular o tempo /2 /2 + 2.(/2) = + /4 /4 /4 /4 4.(/4) = + 1 1 1 1 1 1.(1) = Tempo total: Θ(.log ).(1 + lg )
MergeSort iterativo É possível escrever uma variate do MergeSort ão recursiva e sem pilha, cuja execução é mais rápida. b b i f MergeSort(i, f) { p m r b = 1; // b: tamaho de cada bloco while (b < f) { p = i; // p: posição iicial do 1º bloco while (p+b <= f) { r = mi{f, p-1+2*b}; // r: posição fial do 2º bloco m = p+b-1; // m: posição fial do 1º bloco merge(p, m, r); p += 2*b; } b *= 2; // tamaho dos blocos é duplicado } } É uma simulação da versão recursiva, com meor uso da pilha de execução. No etato, as complexidades de tempo e de espaço ão são alteradas.
Coclusões Qualquer algoritmo que ordear úmeros através de comparações em tempo de pior caso Θ(.log ) será ótimo em termos de complexidade de tempo. MergeSort faz isso: portato, o upper boud de tempo para a ordeação através de comparações é Θ(.log ). Como o upper e o lower bouds de tempo da ordeação são iguais, podemos dizer que a ordeação através de comparações é um problema computacioalmete resolvido. No etato, o MergeSort ecessita de espaço extra Θ() para armazear o vetor temporário (covém que seja alocado uma úica vez pelo programa pricipal). Veremos que ele ão é ótimo em termos de complexidade de espaço extra.
RadixSort Em determiadas codições, é possível ordear em tempo de pior caso Θ(). Por exemplo, isso ocorre quado: 1) os valores têm um comprimeto limitado; 2) a ordeação baseia-se em cálculos com esses valores (e ão em comparações). Como fucioa o RadixSort : Os valores de etrada, escritos em alguma base umérica, têm exatamete d dígitos. A ordeação é realizada em d passos: um dígito por vez, começado a partir dos meos sigificativos.
Exemplo com d=3 Passo 1a: Separá-los de acordo com o dígito mais à direita a b a b a c c a a a c b b a b c c a a a c b b a b b a 3 filas, pois a base é 3 c c a c a a b a b a a c a b a a c b b a c a b c Passo 1b: Ui-los seguido a ordem das filas a b a c a a c c a b b a a c b b a b b a c a a c
Exemplo com d=3 Passo 2a: Separá-los de acordo com o segudo dígito a b a c a a c c a b b a a c b b a b b a c a a c a a c b a c b a b b b a a c b c a a a b a c c a a b c Passo 2b: Ui-los seguido a ordem das filas c a a b a b b a c a a c a b a b b a c c a a c b
Exemplo com d=3 Passo 3a: Separá-los de acordo com o terceiro dígito c a a b a b b a c a a c a b a b b a c c a a c b a c b a b a b b a b a c c c a a a c b a b c a a a b c Passo 3b: Ui-los seguido a ordem das filas a a c a b a a c b b a b b a c b b a c a a c c a
Algoritmo d iterações RadixSort() { } Queue q[0..base-1]; for (i=0, factor=1; i<d; factor *= base, i++) { } for (j=1; j<=; j++) q[(v[j]/factor)%base].equeue(v[j]); for (j=0, k=1; j<base; j++) while (!q[j].isempty()) v[k++] = q[j].dequeue(); Θ(+base) Θ() Tempo: Θ(d.(+base)) = Θ(), pois d e base são costates. Por que ão é usado a prática? 1) A costate d costuma ser grade. 2) Overhead da maipulação das estruturas de dados.