UNICAMP – Universidade Estadual de Campinas
IC – Instituto de Computação
Disciplina: MO417 – Complexidade de Algoritmos I
Professor: João Meidanis
Assunto: Ordenação em Tempo Linear
Capítulo
do Livro Texto Adotado (*):
Capítulo 8
Data
da Aula: 19/03/2003
Data
de Entrega da Ata: 24/03/2003
Redator: Marcelo Fantinato
*
Livro Texto adotado: Cormen,
T.H.;Leiserson, C.E.; Rivest, R.L.; Stein, C.; Algoritmos. Tradução da
2ª edição americana Teoria e Prática. 2002.
Tópicos desta Ata:
1)
Introdução
2)
Limites Inferiores
para Ordenação por Comparação
2.1)
O Modelo de Árvore de
Decisão
2.2)
Limite Inferior no Tempo de Execução para o Pior Caso
3)
Algoritmos de
Ordenação em Tempo Linear
3.1)
Ordenação por
Contagem (Counting Sort)
3.2)
Ordenação da Raiz
(Radix Sort)
3.3)
Ordenação por Balde
(Bucket Sort)
4)
Conclusão
1)
Introdução
Este capítulo aborda três algoritmos de ordenação que são executados em tempo linear, ou seja, que ordenam n números no tempo Theta(n).
Primeiramente é apresentado um limite inferior para
os algoritmos de ordenação por comparação que prova que nenhum deles pode ser
executado em tempo linear. Depois são apresentados então algoritmos de
ordenação que não são por comparação que o podem ser.
2)
Limites
Inferiores para Ordenação por Comparação
Todos os algoritmos de ordenação por comparação possuem a seguinte propriedade:
“a seqüência ordenada que eles determinam se baseia
apenas em comparações entre elementos de entrada”.
Por esses algoritmos de ordenação por comparação
possuírem essa propriedade em comum, é possível definir um limite inferior que
seja comum a todos eles. Esse limite é definido com base no conceito de árvore de decisão.
2.1)
Árvores de
Decisão
Uma árvore de decisão representa as comparações
executadas por um algoritmo de ordenação quando ele opera sobre uma entrada de
um tamanho dado. Assim, uma árvore de decisão permite a visualização de
ordenações por comparação de modo abstrato.
Uma árvore de decisão possui as seguintes
propriedades:
·
Árvore Binária;
·
Árvore possui no
mínimo n! folhas (para n igual ao número de elementos do array a
ser ordenado);
·
Cada folha contém uma
permutação dos dados de entrada.
O comprimento do maior caminho da árvore (da
raiz até uma folha) é utilizado para chegar a um limite inferior, conforme
desejado (o caminho mais longo da árvore representa o pior caso do algoritmo).
Dica do
professor: Dado um algoritmo de ordenação por comparação, é
sempre possível construir sua árvore de decisão a partir do seguinte
procedimento: -
Fixar o n; -
Verificar qual é a
primeira comparação do algoritmo, a qual pode resultar em Sim ou Não. Neste
passo, é necessário saber quais são os índices que estão sendo comparados; -
A primeira
comparação é colocada como a raiz da árvore, incluindo os arcos Sim e Não; -
Continuar esse
procedimento para cada nó da árvore. Em cada próxima comparação, é necessário
saber quais são os índices que estão sendo comparados. Deve se analisar sempre o índice original, ou
seja, não devem ser consideradas as trocas de índices ocorridas durante o
processo. Se o algoritmo de ordenação por comparação
estiver correto, para cada entrada chega-se em uma folha (que é uma
permutação da entrada). |
Questionamento
de Aluno: Foi questionado se existe apenas uma árvore de
decisão para todos os algoritmos de ordenação por comparação ou se deve
existir uma árvore de decisão para cada algoritmo. A turma concluiu que existe um sentimento geral
de que a árvore de decisão depende do algoritmo, devendo então existir uma
árvore de decisão para cada algoritmo. Embora a árvore possua sempre as
mesmas propriedades (como ser binária e possuir no mínimo n! folhas), a
árvore para algoritmos diferentes pode ser diferente tendo, por exemplo,
topologias e elementos diferentes. |
2.2) Limite
Inferior no Tempo de Execução para o Pior Caso
É provado que qualquer algoritmo de ordenação por
comparação exige Ômega(n * lg n) comparações no pior caso.
Ou seja, qualquer um desses algoritmos realiza no mínimo n * lg n
comparações para chegar no resultado final. Portanto, nunca um algoritmo de
ordenação por comparação terá um tempo de execução para o pior caso igual a n.
Questionamento
de Aluno: Foi questionado por que foi usado no livro o
Insertion Sort, que possui tempo de execução quadrático no pior caso –
Theta(n2), se o intuito é chegar em um resultado geral para todos
os algoritmos de ordenação por comparação. O professor disse que o Insertion Sort foi usado
apenas como um exemplo de árvore de decisão. Ele foi usado por ser o mais
fácil, visto que ele não faz tantas trocas. Entretanto, a regra é geral e
vale para qualquer algoritmo de ordenação por comparação. |
Questionamento
de Aluno: Foi questionado que parece ser muito difícil
montar a árvore para outros algoritmos além do Insertion Sort, como – por
exemplo – o Quick Sort. O professor disse que é difícil mesmo. Mas as
árvores de decisão para algoritmos de ordenação por comparação são usadas
apenas para provar teoremas na teoria e não são muito usadas na prática. Para
se ter uma idéia, no caso de uma árvore com n elementos, deve existir
no mínimo n! elementos folha, o que é inviável de se construir. |
3) Algoritmos
de Ordenação em Tempo Linear
3.1) Ordenação
por Contagem (Counting Sort)
A ordenação por Contagem pressupõe que cada um dos n
elementos de entrada é um inteiro no intervalo de 1 a k, para algum
inteiro k. Assim, por exemplo, pode-se ordenar 50 números que variam de
1 a 15, com k = 15.
Exemplos de k: -
ao ordenar idades,
k poderia ser igual a 100 ou igual a 200; -
ao ordenar RAs, k
poderia ser igual a 999999; -
ao ordenar RGs, k
poderia ser igual a 99999999. |
Idéia Básica do Algoritmo:
·
Determinar, para cada
elemento de entrada x, o número de elementos menores que x;
·
Usar essa informação
para inserir o elemento x diretamente em sua posição no arranjo de
saída.
Exemplo Simples (sem considerar que pode haver
vários elementos com o mesmo valor):
Se
existem 17 elementos menores que x, então x é colocado na posição
de saída 18.
Características do algoritmo de ordenação por
contagem apresentado no livro:
·
O algoritmo não usa
comparações, mas utiliza dupla indexação;
·
A ordenação não é
feira localmente, ou seja, o algoritmo exige um espaço de armazenamento de
trabalho temporário;
·
Não é um algoritmo
recursivo;
·
As estruturas de
repetição são todos do tipo for (existem quatro for no algoritmo);
·
O algoritmo é
estável;
·
O tempo de execução é
fornecido em função do valor de k.
Tempo de execução do algoritmo ordenação por
contagem: Theta(k + n).
Tempo de execução do algoritmo ordenação por
contagem quando k = O(n):Theta(n).
Importância da propriedade de estabilidade de um
algoritmo: Um algoritmo é estável quando números com o mesmo
valor aparecem no arranjo de saída na mesma ordem em que se encontram no
arranjo de entrada. Essa propriedade é importante quando os dados
satélites que acompanham os elementos sendo ordenado devem ser transportados
juntamente com o elemento. O algoritmo de ordenação por contagem é estável
já que ele faz a leitura no array intermediário de trás para frente na hora
de criar o vetor resultante. Mas é a manutenção desta estabilidade que obriga
que o algoritmo utilize um array auxiliar. Se a propriedade de estabilidade
não precisasse ser mantida, o algoritmo já poderia ir trabalhando no próprio
array inicial, usando menos memória. |
Errata: Algoritmo COUNTING-SORT (página 136) Linha 6 – o correto é: for
i ← 1 to k |
Dúvida Levantada (Está certo ou errado no livro): Parágrafo 4, página 137. A seguinte frase parece
estar sem sentido: “Ou seja, os vínculos entre dois números são
rompidos pela regra de que qualquer número que aparecer primeiro no arranjo
de entrada aparecerá primeiro no arranjo de saída” Esta dúvida não foi muito discutida e assim a
turma não chegou a uma conclusão sobre isso. Nota do instrutor: consultando o
original em inglês verificamos que a frase original é: "That
is, ties between numbers are broken by the rule that whichever
number appears first in the input array appears first in the output
array", cuja tradução seria algo como: "Ou seja, empates entre
números são resolvidos pela regra de que o número que
aparece primeiro no vetor de entrada aparece primeiro no vetor de
saída". Aparentemente o(a) tradutor(a) se confundiu,
interpretando o verbo "to break ties" do inglês como "romper
vínculos" ao invés do correto aqui que seria "resolver empates". |
3.2) Ordenação
da Raiz (Radix Sort)
O algoritmo radix sort ordena os elementos de um
array em função dos valores contidos em cada um de seus dígitos.
Este algoritmo ordena n elementos de um
array A, que possuem d dígitos cada um, onde o dígito 1 é o dígito de
mais baixa ordem e o dígito d é o dígito de mais alta ordem.
Idéia Básica do Algoritmo:
·
Para
i de 1 a d, ordenar os elementos de A sobre
o dígito i, através de um algoritmo de ordenação estável.
Tempo de execução do algoritmo radix sort:
·
Dados n
números de d dígitos em que cada dígito pode assumir até k
valores possíveis, radix sort ordena corretamente esses números no tempo Theta(d(n
+ k));
·
Dados n
números de b bits e qualquer inteiro positivo r <= b, radix sort ordena corretamente esses
números no tempo Theta((b/r)(n + 2r)).
Dependendo da escolha de k ou b e r, o resultado
final para o tempo de execução do radix sort pode ser Theta(n).
Quando é preferível usar o radix sort ou um outro
algoritmo como o quick sort? Determinar o algoritmo de ordenação preferível
depende das características das implementações, da máquina subjacente e dos
dados de entrada e da importância de espaço de armazenamento da memória
primária. |
3.3) Ordenação
por Balde (Bucket Sort)
O algoritmo bucket sort pressupõe que a
entrada possui uma distribuição uniforme dos dados. Além
disso, os dados devem pertencer ao intervalo [0, 1).
Idéia Básica do Algoritmo:
·
Primeiramente,
dividir o intervalo [0, 1) em n subintervalos de igual tamanho (chamados
de baldes);
·
Depois, distribuir os
n números de entrada entre os baldes;
·
Depois, ordenam-se os
números em cada balde – usando algum outro algoritmo de ordenação qualquer;
·
Por último,
percorrem-se os baldes em ordem, listando os elementos contidos em cada um.
Através deste procedimento, espera-se que muitos
números não caiam em cada balde – devido à distribuição realizada de forma
uniforme. Na realidade, espera-se que cada número caia em um balde diferente.
Tempo de execução do algoritmo bucket sort
(encontrada usando linearidade de expectativa):
·
Theta(n) + n*O(2 –
1/n) = Theta(n).
Análise do pior caso:
·
Se todos os números caíssem num balde só: o tempo de execução seria igual
ao do algoritmo usado na ordenação dos números existentes em cada balde.
Questão
Levantada pelo Professor: Só é possível ordenar
números de 0 a 1? E se fossem números de 0 a 10, seria possível? Foi sugerido por um
aluno que no caso de números de 0 a 10, poderia simplesmente dividir todos os
números por 10, ordená-los e depois multiplicá-los por 10 novamente. |
4) Conclusões
Nesta aula foram apresentados três algoritmos de ordenação que são
executados em tempo linear, ou seja,
que ordenam n números no tempo Theta(n).
Portanto, todos esses três algoritmos de ordenação superam o limite inferior encontrado no tempo de
execução para o pior caso dos algoritmos de ordenação por comparação que é de Ômega(n * lg n).
Apesar disso, nem sempre é mais vantajoso ou viável
usar um desses 3 algoritmos por serem mais eficientes em relação ao tempo de
execução. Isso porque cada um desses três algoritmos pressupõe algo sobre os
dados contidos no array de entrada. Assim, eles não podem ser aplicados com
esse tempo de execução linear para qualquer caso de ordenação. Além disso,
deve-se ainda avaliar a necessidade ou não de uma ordenação estável e de uma
ordenação local. Cada um dos algoritmos de ordenação visto até esta aula possui
vantagens e desvantagens dependendo de algumas condições.