ORDENAÇÃO
A atividade de ordenação é o processo de rearranjo de um certo conjunto de objetos de acordo com um critério (ordem) específico. O objetivo da ordenação é facilitar a localização dos membros de um conjunto de dados.
ORDENAÇÃO DE VETORES
A preocupação mais importante a ser estabelecida em relação aos métodos de ordenação de vetores corresponde ao uso econômico da memória disponível. Isto implica que a permutação de elementos, responsável por levar o elemento à ordem desejada, deve ser efetuada in situ, e que, portanto, são de menor interesse os métodos que efetuam o transporte físico dos elementos de um vetor A para um vetor resultante B.
Restringindo-se a escolha dos métodos, dentre as inúmeras soluções possíveis, de acordo com o critério de economia de memória, pode-se promover uma primeira classificação de acordo com a eficiência do mesmo em relação à economia de tempo.
Uma boa medida de eficiência é obtida contando-se o número C de comparações necessárias e o número M de movimentos (transposições) dos elementos. Estes números são funções do número
n de elementos a serem ordenados.
Os métodos de ordenação que ordenam os elementos in situ podem ser classificados em três principais categorias:
1. Ordenação por inserção 2. Ordenação por seleção 3. Ordenação por troca
Estes três princípios serão examinados e comparados. Os exemplos operam sobre a variável A, cujos componentes serão ordenados e se referem a um vetor de inteiros de tamanho variável (N), definido como se segue:
inteiro: N;
tipo vet = vetor [1:N] inteiro; vet: A;
ORDENAÇÃO POR INSERÇÃO
Em cada passo, iniciando-se com i=2 e incrementando-se i de uma em uma unidade, o i-ésimo elemento da seqüência vai sendo comparado com os elementos anteriores e, se for o caso, retirado e inserido na posição apropriada.
O processo de ordenação por inserção será mostrado em um exemplo, em que são ordenados oito números (N=8) escolhidos aleatoriamente. O algoritmo deve fazer o seguinte:
para I de 2 até N faça X ← A[I];
inserir X no local adequado em A[1]...A[I]
Valores iniciais 44 55 12 42 94 18 06 67 i = 2 44 55 12 42 94 18 06 67 i = 3 12 44 55 42 94 18 06 67 i = 4 12 42 44 55 94 18 06 67 i = 5 12 42 44 55 94 18 06 67 i = 6 12 18 42 44 55 94 06 67 i = 7 06 12 18 42 44 55 94 67 i = 8 06 12 18 42 44 55 67 94
Para encontrar o local apropriado do elemento observado é conveniente utilizar, de modo
alternado, operações de comparação e de movimentação, examinando X, e comparando-o com o elemento A[J], e então efetuando ou a inserção de X ou a movimentação do elemento A[J], e prosseguindo-se para a esquerda no tratamento dos outros elementos.
Para isso será necessário testar duas condições distintas que causam o término deste processo de análise:
Um elemento A[J] é encontrado com um elemento de valor menor do que o seu A extremidade esquerda é atingida
É um caso típico de uma repetição com duas condições de término, que conduz a utilização de um elemento sentinela (para armazenar temporariamente o valor de algum elemento que está sendo analisado). Para isso, será utilizada uma posição do vetor A como sentinela, o A[0] que receberá o valor de X.
Algoritmo Inserção var
inteiro: I, J, N, X;
tipo vet = vetor [0:N] inteiro;
vet: A; {supondo que o vetor A tenha sido preenchido}... início
para I de 2 até N faça X ← A[I]; A[0] ← X; J ← I;
enquanto X < A[J-1] faça A[J] ← A[J-1]; J ← J-1; fim enquanto; A[J] ← X; fim para; fim
ORDENAÇÃO POR SELEÇÃO
Este método é baseado no seguinte princípio:
Selecionar o elemento que apresenta o menor valor Trocá-lo com o primeiro elemento da seqüência A[1]
Repetir estas operações, envolvendo agora os N–1 elementos restantes, depois os N–2 elementos, ..., até restar um só elemento, o maior deles.
06 55 12 42 94 18 44 67 06 12 55 42 94 18 44 67 06 12 18 42 94 55 44 67 06 12 18 42 94 55 44 67 06 12 18 42 44 55 94 67 06 12 18 42 44 55 94 67 06 12 18 42 44 55 67 94 Algoritmo Seleção var inteiro: I, J, N, K;
tipo vet = vetor [1:N] inteiro;
vet: A; {supondo que o vetor A tenha sido preenchido}... início
para I de 1 até N–1 faça K ← I;
X ← A[I];
para J de I+1 até N faça se A[J] < X então K ← J; X ← A[K]; fim se; fim para; A[K] ← A[I]; A[I] ← X; fim para; fim MÉTODO DA BOLHA
Ordenação: algoritmos elementares
Esta página trata do seguinte problema: Permutar (ou seja, rearranjar) os elementos de um
vetor v[0..n-1] de tal modo que eles fiquem em ordem crescente, ou seja, de tal forma
que tenhamos v[0] ≤ v[1] ≤ . . . ≤ v[n-1] .
Veja o verbete Sorting algorithm na Wikipedia.
Exercício
1. Escreva uma função que verifique se um vetor v[0..n-1] está em ordem crescente.
Algoritmo de Inserção
Eis um algoritmo de ordenação muito popular. (Veja o verbete insertion sort na
Wikipedia. Veja também o capítulo 11 do "Programming Pearls".) Ele é usado, por exemplo, para colocar em ordem um baralho de cartas:
// Esta função rearranja o vetor v[0..n-1] em // ordem crescente.
void
insercao (int n, int v[]) {
int i, j, x;
for (j = 1; j < n; j++) { x = v[j];
for (i = j-1; i >= 0 && v[i] > x; --i) v[i+1] = v[i];
v[i+1] = x; }
}
(Compare o loop interno com o algoritmo de inserção discutido em outra página.) Para entender o funcionamento do algoritmo, basta observar que no início de cada repetição do
for externo, imediatamente antes da comparação de j com n,
1. o vetor v[0..n-1] é uma permutação do vetor original e
2. o vetor v[0..j-1] está em ordem crescente.
Estas condições invariantes são trivialmente verdadeiras no início da primeira iteração, quando j vale 1. No início da última iteração, j vale n e portanto o vetor v[0..n-1] está
em ordem, como desejado. (Note que a última iteração é abortada logo no início, pois a condição "j < n" é falsa.)
444 555 555 666 777 222 999 222 999 222 999
Algoritmo de inserção: desempenho
Quanto tempo o algoritmo consome para fazer o serviço? O tempo é proporcional ao número de execuções da comparação "v[i] > x". Calculemos esse número. No pior caso,
para cada valor de j, a variável i assume os valores j-1, . . . , 0.
j i número de valores de i 1 0 1 2 1, 0 2 3 2, 1, 0 3 . . . . . . . . . n-1 n-2, n-3, . . . , 1, 0 n-1
O número de execuções da comparação "v[i] > x" no pior caso é igual à soma da última
coluna da tabela, ou seja,
n(n-1) / 2 .
Esse número cresce como n2. O algoritmo é um tanto lento: se a ordenação de n números
leva t segundos então a ordenação de 2n números levará 4t segundos e a ordenação de 10n
números consumirá 100t segundos!
Algoritmo de inserção: animações
Veja alguns applets de animação do algoritmo de inserção:
• applet de R. Sedgewick na Universidade de Princeton
• Sorting Algorithms na Universidade de British Columbia
• Sorting Algorithms, página de Pat Morin na Universidade de Carlton, Canadá
Exercícios
2. Escreva uma versão recursiva do algoritmo de ordenação por inserção. 3. Na função insercao, troque a comparação "v[i] > x" por "v[i] >= x". A
4. Que acontece se trocarmos "for(j=1" por "for(j=0" no código da função insercao?
5. Que acontece se trocarmos "v[i+1]=x" por "v[i]=x" no código da função insercao?
6. Critique a seguinte implementação do algoritmo de ordenação por inserção:
7. void ins (int n, int v[]) { 8. int i, j, x;
9. for (j = 1; j < n; j++) { 10. x = v[j];
11. for (i = j-1; i >= 0 && v[i] > x; --i) { 12. v[i+1] = v[i];
13. v[i] = x; 14. }
15. } 16. }
17. Critique a seguinte implementação do algoritmo de ordenação por inserção:
18. void ins (int n, int v[]) { 19. int i, j, x;
20. for (j = 1; j < n; j++) {
21. for (i = j-1; i >= 0 && v[i] > v[i+1]; i--) { 22. x = v[i]; v[i] = v[i+1]; v[i+1] = x;
23. } 24. } 25. }
26. Critique a seguinte implementação do algoritmo de ordenação por inserção:
27. void ins (int n, int v[]) { 28. int h, i, j, x; 29. for (j = 1; j < n; j++) { 30. x = v[j]; 31. for (h = 0; h < j && v[h] <= x; ++h) ; 32. for (i = j-1; i >= h; --i) 33. v[i+1] = v[i]; 34. v[h] = x; 35. } 36. }
37. Escreva uma versão do algoritmo de inserção que tenha o seguinte invariante: no início de cada iteração, o vetor v[j+1..n-1] é crescente.
38. O papel do for interno na função insercao é encontrar o ponto onde v[j] deve ser
inserido em v[0..j-1]. Considere fazer isso com uma busca binária. Analise o
resultado.
39. Escreva uma função que rearranje um vetor v[0..n-1] de inteiros modo que ele
fique em ordem estritamente crescente.
40. Escreva uma função que permute os elementos de um vetor inteiro v[0..n-1] de
41. Escreva uma função que coloque em ordem lexicográfica um vetor de strings. Use o algoritmo de inserção.
Algoritmo de Seleção
Eis outro algoritmo de ordenação muito popular. (Veja o verbete selection sort na
Wikipedia.) Ele seleciona o menor elemento do vetor, depois o segundo menor, e assim por diante:
// Esta função rearranja o vetor v[0..n-1] em // ordem crescente.
void
selecao (int n, int v[]) { int i, j, min, x; for (i = 0; i < n-1; ++i) { min = i; for (j = i+1; j < n; ++j) if (v[j] < v[min]) min = j; x = v[i]; v[i] = v[min]; v[min] = x; }
}
Para entender como e por que o algoritmo funciona basta observar que no início de cada repetição do for externo, imediatamente antes da comparação de i com n-1, valem os
seguintes invariantes:
1. o vetor v[0..n-1] é uma permutação do vetor original,
2. v[0..i-1] está em ordem crescente e
3. v[i-1] ≤ v[i..n-1].
A tradução do terceiro invariante para linguagem humana é a seguinte: v[0..i-1] contém
todos os elementos "pequenos" do vetor original e v[i..n-1] contém todos os elementos
"grandes". Os três invariantes garantem que no início de cada iteração v[0], . . , v[i-1] já
estão em suas posições definitivas.
0
i-1 i
n-1
110 120 120 130 140 999 666 999 666 999 666 pequenos, crescente grandes
Algoritmo de seleção: desempenho
Não é difícil verificar que o algoritmo de seleção, tal como o de inserção, faz cerca de n2/2
Algoritmo de seleção: animações
Veja applets de animação do algoritmo de seleção:
• applet de R. Sedgewick
• Sorting Algorithms na Universidade de British Columbia
• Sorting Algorithms, página de Pat Morin na Universidade de Carlton, Canadá
Exercícios
14. Escreva uma versão recursiva do algoritmo de ordenação por seleção.
15. Na função selecao, que acontece se trocarmos "for(i=0" por "for(i=1"?
Que acontece se trocarmos "for(i=0;i<n-1" por "for(i=0;i<n" ?
16. Na função selecao, troque a comparação "v[j]<v[min]" por "v[j]<=
v[min]". A nova função continua produzindo uma ordenando crescente de v[0..n-1]?
17. Escreva uma função que permute os elementos de um vetor inteiro v[0..n-1] de
modo que eles fiquem em ordem decrescente.
18. Escreva uma função que coloque em ordem lexicográfica um vetor de strings. Use o algoritmo de seleção.
Mais exercícios
19. ORDENAÇÃO DE LISTA ENCADEADA. Escreva uma função que ordene uma lista
encadeada. Inspire-se no algoritmo de inserção para vetores. Faça duas versões: uma para lista com cabeça e outra para lista sem cabeça. (Sua função precisa devolver alguma coisa?).
20. ORDENAÇÃO DE LISTA ENCADEADA. Escreva um função para ordenar uma lista
encadeada. Imite o algoritmo de seleção para vetores. Faça duas versões: uma para lista com cabeça e outra para lista sem cabeça. (Sua função precisa devolver alguma coisa?).
21. Escreva uma função que rearranje as linhas de um arquivo em ordem lexicográfica. Compare com o utilitário sort.
22. [Importante] Suponha que cada elemento de um vetor é um registro com dois campos: um é um inteiro e outro uma string:
Escreva uma função que rearranje o vetor de modo que os campos aa fiquem em
ordem crescente. Escreva outra função que rearranje o vetor de modo que os campos bb fiquem em ordem lexicográfica.
24. PROJETO DE PROGRAMAÇÃO. Uma palavra é anagrama de outra se a seqüência de
letras de uma é permutação da seqüência de letras da outra. Por exemplo, "aberto"
é anagrama de "rebato". Digamos que duas palavras são equivalentes se uma é
anagrama da outra. Uma classe de equivalência de palavras é um conjunto de palavras duas a duas equivalentes. Escreva um programa que receba um arquivo texto contendo palavras (uma por linha) e extraia desse arquivo uma classe de equivalência máxima. Aplique o seu programa ao arquivo de palavras
www.ime.usp.br/~pf/algoritmos/dicios/br, que contém todas as palavras do
português falado no Brasil. (O arquivo é grande; portanto, o seu programa deverá ser muito eficiente e usar boas estruturas de dados.)
Estabilidade
Um algoritmo de ordenação é estável (= stable) se não altera a posição relativa de elementos com mesmo valor. Por exemplo, se o vetor v[0..n-1] tiver dois elementos
iguais a 222, primeiro um azul e depois um vermelho, um algoritmo de ordenação estável mantém o 222 azul antes do vermelho.
original: 444 555 555 666 777 333 999 222 111 222 888
ordenado: 111 222 222 333 444 555 555 666 777 888 999
Eis um exemplo melhor. Digamos que cada elemento do vetor é um struct com dois
campos: o primeiro contém o nome de uma pessoa e o segundo contém o ano de
nascimento da pessoa. Suponha que o vetor original tem dois "João da Silva": primeiro o que nasceu em 1950 e depois o que nasceu em 1970. Se o vetor for ordenado por um algoritmo estável com base no primeiro campo, os dois "João da Silva" continuarão na mesma ordem relativa: primeiro o de 1950 e depois o de 1970.
Exercícios
24. O algoritmo de inserção é estável?
25. Na função insercao, troque a comparação "v[i]>x" por "v[i]>=x". A nova
função faz uma ordenação estável de v[0..n-1]?
26. O algoritmo de seleção é estável?