Você está na página 1de 22

Análise de Algoritmos de Pesquisa e Ordenação Elton Silva

®
2007-2 1

Capítulo 3

Análise de Algoritmos
de Pesquisa e Ordenação

1- Algoritmos de Pesquisa
A tarefa de pesquisar (ou buscar) está entre as tarefas mais freqüentemente
encontradas no dia a dia. Nesta seção analisaremos alguns algoritmos para recuperar
informação a partir de uma massa grande de informação previamente armazenada. A
informação geralmente é dividida em registros, onde cada registro possui uma chave para
ser usada na pesquisa. O objetivo da pesquisa é encontrar uma ou mais ocorrências de
registros com chaves iguais à chave de pesquisa. Neste caso, ocorre uma pesquisa com
sucesso; caso contrário a pesquisa é sem sucesso. Um conjunto de registros é chamado de
tabela ou arquivo. Geralmente, o termo “tabela” é associado a entidades de vida curta,
criadas na memória interna durante a execução de um programa. Já o termo “arquivo” é
geralmente associado a entidades de vida mais longa, armazenadas na memória externa.

Existe uma variedade de métodos de pesquisa. A escolha do método mais adequado


a uma determinada aplicação depende principalmente: da quantidade de dados envolvidos
e do arquivo estar sujeito a inserções e retiradas constantes, ou do conteúdo do arquivo
ser praticamente estável.

O conjunto de elementos onde será efetuada a pesquisa muitas vezes será


representado através de um vetor, por exemplo, A: array [1..N] of item, onde item
apresenta uma estrutura de registro, contendo um campo que atua como chave para a
pesquisa.

1.1- Pesquisa Seqüencial

É o método de pesquisa mais simples que existe. Funciona da seguinte forma: a


partir do primeiro registro, pesquise seqüencialmente até encontrar a chave k procurada;
então pare. Uma busca deste tipo termina quando for satisfeita uma das duas condições:

1. O elemento é encontrado.
2. Todo o conjunto foi analisado, mas o elemento não foi encontrado.

Isto resulta no seguinte algoritmo:

i := 1;
enquanto (i <= N) e (Ai ≠ k) faça
i := i + 1;
se i > N então retorna 0
senão retorna i

Análise

A condição (i <= N) e (Ai ≠ k) implica que, quando o algoritmo indica o encontro do


elemento desejado, foi encontrado aquele de menor índice, isto é, o primeiro deles (caso
Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 2

haja uma ocorrência múltipla do elemento em questão). Quando i for igual a N+1, isto
significa que não foi encontrado o elemento. Evidentemente, o término das repetições é
garantido, porque, em cada passo, i é incrementado (positivamente) e, portanto,
certamente alcançará o limite N após um número finito de passos, caso não exista o
elemento desejado no vetor.

Considerando f(n) associada ao número total de comparações realizadas, temos:

Melhor caso f(n) = 2.1+1=3 O(1) complexidade constante

Pior caso f(n) = 2(n + 1)+1 = 2n + 3 O(n) complexidade linear

1.2- Pesquisa Seqüencial com sentinela

Na pesquisa seqüencial simples cada passo requer o incremento do índice e a


avaliação de uma expressão booleana. O problema seguinte consiste em verificar se esta
tarefa pode ser simplificada, e portanto, a busca ser acelerada. A única possibilidade de se
obter tal efeito consiste em se encontrar uma simplificação da expressão booleana. Isto é
possível desde que se possa garantir que tal elemento será encontrado. Para tanto,
introduz-se um elemento adicional, com o valor k, no final do conjunto. A este elemento
auxiliar dá-se o nome de sentinela, porque ele evita que a busca avance o limite
demarcado pelo índice. O vetor será então declarado da seguinte forma: A: array[1..N+1]
of item. O algoritmo de busca linear com sentinela torna-se:

i := 1;
An+1 := k;
Enquanto Ai ≠ k faça
i := i + 1;
se i > N então retorna 0
senão retorna i

Análise

Considerando f(n) associada ao número total de comparações realizadas, temos:

Melhor caso f(n) = 2 O(1) complexidade constante

Pior caso f(n) = (n + 1) + 1 = n+2 O(n) complexidade linear

1.3- Pesquisa Binária

Uma busca pode ser mais eficiente se os dados estiverem ordenados. Suponha, por
exemplo, uma lista telefônica em que os nomes não estejam listados em ordem alfabética.
Uma coisa dessas não seria muito útil.

A idéia principal da busca binária é de testar um elemento sorteado aleatoriamente,


por exemplo, o do meio, e compará-lo com um argumento de busca (k). Se tal elemento
for igual a k, a busca termina com sucesso; se for menor que k, conclui-se que todos os
elementos com índices maiores ou iguais ao do meio podem ser eliminados dos próximos
testes, e se for maior que k, todos aqueles que possuem índices menores ou iguais ao do
meio podem ser também eliminados.
Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 3

A seguir são apresentadas duas versões de um algoritmo que usa essa idéia, uma
iterativa e a outra recursiva. Eles utilizam duas variáveis-índices e e d e marcam,
respectivamente, os limites esquerdo e direito da região de A em que um elemento ainda
pode ser encontrado.

Procedimento PesquisaBinária(A, e, d, k); // versão iterativa


// Procura por k na lista A[e..d].
Retorna a posição encontrada, senão retorna 0.
A lista está ordenada em ordem crescente d >= e > 0 //
início
enquanto d >= e faça
início
meio = (e + d) div 2;
caso A[meio]
< k: e = meio + 1;
= k: retorna meio;
> k: d = meio - 1
fim-enquanto
retorna 0
fim PesquisaBinária.

Análise
A cada iteração do algoritmo, o tamanho da tabela é dividido ao meio. Logo, o
número de vezes que ela é dividida ao meio é cerca de lgn. Entretanto, o custo para
manter a tabela ordenada é alto: a cada inserção na posição p da tabela implica no
deslocamento dos registros a partir da posição p para as posições seguintes.
Consequentemente, a pesquisa binária não deve ser usada em aplicações muito dinâmicas.

A seguir, uma versão recursiva para a Pesquisa Binária:

int PesqBinaria(A, e, d, k);


// Versão recursiva. Procura por k na lista A[e..d].
Retorna a posição encontrada, senão retorna 0.
A lista está ordenada em ordem crescente d >= e > 0 //

início
se (e == d)
se (k == A[e]) retorna e
senão retorna 0
senão
início
meio = (e + d) div 2;
se (k > A[meio]) retorna PesqBinaria(A, meio+1, d, k)
senão retorna PesqBinaria(A, e, meio, k)
fim-senão
fim PesqBinaria.

O procedimento recursivo é disparado com a chamada PesquisaBinária (A, 1, N, k).

Análise
Encontrando a relação de recorrência associada ao algoritmo PesquisaBinária temos:
T(n) = T(n/2) + 1, T(1) = 1.

Resolvendo esta relação de recorrência, concluímos que o algoritmo é O(lgn),


melhorando o desempenho em relação à busca seqüencial.
Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 4

2 – Algoritmos de Ordenação
Os algoritmos de ordenação (ou classificação) constituem bons exemplos de como
resolver problemas utilizando computadores. As técnicas de ordenação permitem
apresentar um conjunto amplo de algoritmos distintos para resolver a mesma tarefa.
Dependendo da aplicação, cada algoritmo considerado possui uma vantagem particular
sobre os outros. Além disso, os algoritmos ilustram muitas regras básicas para a
manipulação de estrutura de dados.

Ordenar corresponde ao processo de reorganizar um conjunto de objetos em uma


ordem ascendente ou descendente. O principal objetivo da ordenação consiste em facilitar
a recuperação posterior de itens do conjunto ordenado, como por exemplo, a busca de um
nome em um catálogo telefônico, a busca de uma conta de um cliente em uma agência
bancária etc.

Quando falamos em algoritmos de ordenação, podemos estar nos referindo à


ordenação de toda a estrutura ou de parte de uma, tal como um banco de dados. À
ordenação feita numa estrutura como um todo, chamamos de ordenação interna. O outro
tipo, feito sobre parte de uma grande estrutura, chamamos de ordenação externa.

Os algoritmos trabalham sobre registros de um arquivo. Apenas uma parte do


registro, chamada chave, é utilizada para controlar a ordenação. Além da chave podem
existir outros campos em um registro que não têm influência no processo de ordenação. A
escolha da chave é arbitrária. Qualquer tipo sobre o qual exista uma regra de ordenação
bem-definida pode ser utilizada. As ordens numérica e alfabética são as usuais.

Um método de ordenação é dito estável se a ordem relativa dos itens com chaves
iguais mantém-se inalterada pelo processo de ordenação. Por exemplo, se uma lista
alfabética de nomes de funcionários de uma empresa é ordenada pelo campo salário, então
um método estável produz uma lista em que os funcionários com mesmo salário aparecem
em ordem alfabética.

Os métodos de ordenação interna são classificados em métodos simples e


métodos eficientes. Os métodos simples produzem programas pequenos, fáceis de
entender, ilustrando com simplicidade os princípios da ordenação. Apesar dos métodos
mais sofisticados usarem menos comparações, os algoritmos são mais complexos nos
detalhes. Alguns exemplos de métodos simples encontrados na literatura são o BubbleSort
(também chamado de Método da Bolha), SelectionSort (Seleção Direta) e InsertionSort
(Inserção Direta). Exemplos de métodos eficientes são o QuickSort, HeapSort, ShellSort e
MergeSort. Apresentamos aqui a análise de alguns algoritmos de ordenação simples:
BubbleSort, o SelectionSort e o InsertionSort, e um algoritmo eficiente: o QuickSort.

Considerações sobre Análise de Algoritmos


A análise de algoritmos pode ter um enfoque teórico e/ou prático.

A análise teórica dos algoritmos de ordenação é uma análise matemática


geralmente feita em relação às seguintes funções de complexidade C(n), número de
comparações realizadas na estrutura, e M(n), número de movimentações realizadas na
estrutura, considerando o pior caso, melhor caso e caso médio 1 .

1
Para alguns métodos eficientes (HeapSort, ShellSort e QuickSort) esta análise teórica é
incompleta, pois muitas vezes envolve solução de problemas matemáticos que ainda estão
em aberto.
Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 5

A análise empírica (prática), também chamada de análise de desempenho ou


bateria de testes, leva em consideração o tipo de computador onde o programa foi
executado, a linguagem em que foi implementado, tipo da chave (grande ou pequena),
estruturas a serem ordenadas (leves ou pesadas), variação no tamanho das entradas,
variação na forma das entradas (ascendente, descendente e aleatória). Os resultados de
uma avaliação empírica de algoritmos de ordenação consistem nos tempos coletados para
se ordenar um conjunto de elementos, números de comparações realizadas, número de
movimentações e gráficos ilustrativos. O objetivo deste tipo de análise é verificar, na
prática, o comportamento assintótico de um algoritmo. No caso de algoritmos de
ordenação, deve-se coletar dados quantitativos para o tempo em função do número de
elementos a serem ordenados, e em seguida traçar gráficos com os valores obtidos. Os
passos seguidos em uma bateria de testes de algoritmos de ordenação são basicamente os
seguintes:

i) Implementação do algoritmo de ordenação em uma linguagem de programação.

ii) Para a ordenação de registros com chaves grandes, constrói-se um procedimento que
gere valores randômicos para gerar as várias chaves a serem inseridas no vetor. Para isto,
usa-se a função random (ou uma similar na linguagem escolhida) para gerar os números.
Cada chave deve ser formada pela concatenação de n caracteres obtidos da conversão de
cada número gerado. Para que as várias chaves tenham valores distintos usa-se o
procedimento randomize (ou correspondente) antes de iniciar a geração de uma chave.

Tipo Item = estrutura


chave: string 200
outros campos dependentes da aplicação
fim-estrutura

obs.: o tamanho da estrutura dos itens a serem ordenados influencia diretamente no tempo
em relação ao número de movimentações realizadas na estrutura, uma vez que em muitas
linguagens ocorre a chamada deep copy nas atribuições.

iii) Coleta do tempo gasto para ordenar conjuntos com números crescentes de elementos,
por exemplo 100, 500, 1000, 2000, 3000, etc. Para obter o tempo, insere-se no programa
comandos de interface com o sistema operacional e obtém-se, por subtração, o valor
desejado.

iv) Medida do tempo para ordenar estruturas já ordenadas, inversamente ordenadas e


aleatoriamente ordenadas.

v) Para cada caso-teste são traçados gráficos do tipo n x t (onde n é o número de


elementos a ser ordenado e t, o tempo gasto na ordenação). Também podem ser traçados
gráficos do tipo c x t e m x t, relativos ao número de comparações na estrutura e número
de movimentações realizadas, respectivamente.

2.1. BubleSortSort
Idéia básica do método

O Bubblesort é o exemplo mais simples de um algoritmo de ordenação. A idéia do método


consiste em percorrer o vetor seqüencialmente várias vezes. Cada passagem consiste em
comparar dois elementos de posições adjacentes e trocá-los de posição, se eles não
estiverem na ordem correta. Podemos fazer a analogia com o processo de ir avançando
dentro do ônibus, com os passageiros sendo ordenados segundo o número do ponto em
que saltarão.

Ilustração passo a passo:


Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 6

trocas
Seqüência inicial 44 55 12 42 94 18 06 67 06 ↔ 18
44 55 12 42 94 06 18 67 06 ↔ 94
44 55 12 42 06 94 18 67 06 ↔ 42
44 55 12 06 42 94 18 67 06 ↔ 12
44 55 06 12 42 94 18 67 06 ↔ 55
44 06 55 12 42 94 18 67 06 ↔ 44
06 44 55 12 42 94 18 67 18 ↔ 94
06 44 55 12 42 18 94 67 18 ↔ 42
06 44 55 12 18 42 94 67 12 ↔ 55
06 44 12 55 18 42 94 67 12 ↔ 44
06 12 44 55 18 42 94 67 67 ↔ 94
06 12 44 55 18 42 67 94 18 ↔ 55
06 12 44 18 55 42 67 94 18 ↔ 44
06 12 18 44 55 42 67 94 42 ↔ 55
06 12 18 44 42 55 67 94 42 ↔ 44
Seqüência final (ordenada) 06 12 18 42 44 55 67 94

O Algoritmo em alto nível

MÉTODO BubbleSort(A: vetor);


PARA i = 2 até n FAÇA
PARA j = n até i FAÇA
se A[j] < A[j-1]
trocar A[j] com A[j-1]

O pseudocódigo mais detalhado do algoritmo é apresentado a seguir:

(1) MÉTODO BubbleSort (A: vetor);


(2) {
(3) for (i=2; i<=n; i++)
(4) for (j=n; j>=i; j--)
(5) if (A[j].chave < A[j-1].chave)
(6) { aux=A[j-1]; A[j-1]=A[j]; A[j]=aux }
(7) };

Análise do Algoritmo em relação a C(n) e M(n)

C(n): número de comparações nas chaves

As comparações nas chaves são realizadas no if da linha 5. Das linhas 4 a 7 são feitas n-i+1
comparações. No total, o número de comparações realizadas da linha 3 a linha 7 é
n
(n − 1) + 1 n2 − n
∑ (n − i + 1) = (n − 1) + (n − 2) + ... + 1 =
i =2 2
.(n − 1) =
2
= O(n 2 ).

M(n): número de movimentações nas chaves

No melhor caso (quando o vetor já está previamente ordenado), a linha 6 do algoritmo


nunca é executada, e M(n)=0.
Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 7

O pior caso acontece quando o vetor se encontra inversamente ordenado, pois a cada
passo é feito o número máximo de movimentações associadas à linha 6. O número de
n
3n 2 − 3n
movimentações total M(n) é igual a 3.∑ (n − i + 1) = = O(n 2 ).
i=2 2
Estabilidade do método

O algoritmo de ordenação BubbleSort é estável, pois chaves iguais permanecem na mesma


ordem relativa após a ordenação. Por exemplo, observe o que acontece com as chaves 3 e
3* no vetor a seguir:

Seqüência inicial 3 4 2 3* 1 1 ↔ 3*
3 4 2 1 3* 1↔2
3 4 1 2 3* 1↔4
3 1 4 2 3* 1↔3
1 3 4 2 3* 2↔4
1 3 2 4 3* 2↔3
1 2 3 4 3* 3* ↔ 4
Seqüência final (ordenada) 1 2 3 3* 4

É claro que este exemplo sozinho não prova que o algoritmo é estável. A prova dessa
propriedade vem do fato do BubbleSort trocar somente elementos de posições adjacentes
(o que não acontece, por exemplo, no método SelectionSort que veremos na seção a
seguir).

Análise de Desempenho

Para testar o desempenho do BubbleSort, o algoritmo foi implementado na linguagem Java,


JSDK 1.4.1, plataforma JBuilder 9 Personal, e testado em um computador ATLHON Atlhon
XP 2400 com 256MB de memória, rodando na plataforma Windows XP. A chave utilizada
foi uma string de 200 caracteres (opção configurável). Os testes foram feitos com o vetor
ordenado, invertido e de forma aleatória.

Na tabela a seguir são apresentados alguns resultados obtidos:

Tempo
Nº de Chaves Aleatório Ordenado Invertido (ms) C(n) M(n)
100 x 16 9801 2486
100 x 0 9801 0
100 x 16 9801 4950
400 x 31 159201 41701
400 x 31 159201 0
400 x 32 159201 79800
500 x 62 249001 62342
500 x 47 249001 0
500 x 63 249001 124750
600 x 79 358801 90762
600 x 63 358801 0
600 x 94 358801 179709
1000 x 218 998001 247909
1000 x 203 998001 0
1000 x 235 998001 499500
1500 x 547 2247001 562680
1500 x 531 2247001 0
1500 x 562 2247001 1124250
Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 8

3000 x 2375 8994001 2291819


3000 x 2328 8994001 0
3000 x 2437 8994001 4498487
6000 x 17734 35988001 8886106
6000 x 18906 35988001 0
6000 x 17406 35988001 17996998
10000 x 57062 99980001 24883867
10000 x 53438 35988001 0
10000 x 59875 35988001 27182818,7

Tabela 1 – Resultados da análise de desempenho do BubbleSort

Conclusões sobre o BubbleSort

O BubbleSort é um algoritmo de ordenação simples que precisa de O(n2) comparações para


ordenar n itens. Ele é um algoritmo de fácil implementação. Dependendo da linguagem de
programação utilizada, pode ser recomendado para conjuntos que estejam praticamente
ordenados, pois o número de movimentações será baixo ou até mesmo zero (em caso do
conjunto estar ordenado). Isto influencia diretamente no tempo de execução do algoritmo
caso os registros a serem movidos sejam grandes. Já em conjuntos grandes e muito
desordenados este método não é recomendado, pois teríamos um grande número de
movimentações.

2.2. SelectionSort
Idéia básica do método

O método de classificação por seleção caracteriza-se por selecionar, a cada repetição, o


menor (ou maior) elemento do vetor, que é então colocado em sua posição correta. A
seguir, repete-se o processo, desconsiderando-se a parte do vetor que já está ordenada. A
ordenação se encerra quando restar somente um elemento não selecionado, que vai estar,
juntamente com todos os restantes, na sua posição correta no vetor.

Ilustração passo a passo:

Seqüência inicial 44 55 12 42 94 18 06 67 06 ↔ 44
06 55 12 42 94 18 44 67 12 ↔ 55
06 12 55 42 94 18 44 67 18 ↔ 55
06 12 18 42 94 55 44 67 42 ↔ 42
06 12 18 42 94 55 44 67 44 ↔ 94
06 12 18 42 44 55 94 67 55 ↔ 55
06 12 18 42 44 55 67 94 67 ↔ 67
Seqüência final (ordenada) 06 12 18 42 44 55 67 94

O Algoritmo em alto nível

MÉTODO SelectionSort(A: vetor);


PARA i = 1 até (n – 1) FAÇA
guardar o índice do menor elemento Ai ...... An em k;
trocar Ai com Ak
FIM-PARA

O pseudocódigo mais detalhado do algoritmo é apresentado a seguir:


Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 9

MÉTODO SelectionSort(A: vetor);


(1) PARA i = 1 até (n – 1) FAÇA
(2) {
(3) k = i;
(4) PARA j = (i + 1) até n FAÇA
(5) SE (A[j].CHAVE < A[k].CHAVE) ENTÃO k = j;
(6) aux = A[k];
(7) A[k] = A[i];
(8) A[i] = aux
(9) }

Análise do Algoritmo em relação a C(n) e M(n)

No algoritmo de Seleção Direta, a ordem inicial das chaves não interfere no desempenho do
mesmo, já que é necessário, a cada iteração do método, efetuar a comparação com os n-i
elementos do arranjo, para uma dada iteração i.

Quanto ao número de comparações efetuadas C(n), temos:

1ª iteração: compara o 1º elemento com os n-1 demais: n-1 comparações.


2ª iteração: compara o 2º elemento com os n-2 demais: n-2 comparações.
3ª iteração: compara o 3º elemento com os n-3 demais: n-3 comparações.
...
(n-1)ª iteração: compara o (n-1)º elemento com o último: 1 comparação.
Número total de comparações C(n): (n-1) + (n-2) + ... + 1 = (n2 - n)/2 = O(n2)

Observe que, neste método, a quantidade de comparações efetuadas é constante para um


dado n, ou seja, não depende do arranjo prévio das chaves. Portanto, a complexidade
desse algoritmo no pior caso, no caso médio e no melhor caso é a mesma: O(n²) .

A cada iteração é realizada uma troca (linhas 6, 7, 8), que se constitui de 3 movimentações
no arranjo. Ou seja, a cada iteração são realizadas 3 movimentações. Assim, o número
total de movimentações M(n) = 3n-3. Portanto, a complexidade desse método em relação
ao número de movimentações realizadas é, no pior caso, no melhor caso e no caso
médio, O(n).

Instabilidade do método

No método Seleção Direta há a possibilidade de elementos de chaves iguais mudarem de


posição no processo de ordenação. Isso pode ser mostrado através do exemplo a seguir:

Considere o seguinte arranjo a ser ordenado: [ 4¹ 6 7 2 9 8 4² 1 3 4³ 9 0 ].

Observe que, nesse exemplo, na primeira iteração do método Seleção Direta, haverá a
troca entre o elemento 0 e o elemento 4¹:

1ª iteração: [ 0 | 6 7 2 9 8 4² 1 3 4³ 9 4¹ ]

Nas próximas iterações, a ordenação procederá da seguinte forma:

2ª iteração: [ 0 1 | 7 2 9 8 4² 6 3 4³ 9 4¹ ]
3ª iteração: [ 0 1 2 | 7 9 8 4² 6 3 4³ 9 4¹ ]
4ª iteração: [ 0 1 2 3 | 9 8 4² 6 7 4³ 9 4¹ ]
5ª iteração: [ 0 1 2 3 4² | 8 9 6 7 4³ 9 4¹ ]
Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 10

Observe que, na 5ª iteração, houve a mudança da ordem relativa do número 4, em relação


à ordem em que ele foi inicialmente apresentado.

Na última iteração do método, o conjunto ordenado será:

11ª iteração: [ 0 1 2 3 4² 4³ 4¹ 6 7 8 9 9 ]

Visto que a ordem relativa dos elementos de chaves iguais inicialmente apresentados (4¹,
4², 4³) foi alterada (para 4² 4³ 4¹), pode se concluir que o método é instável.

Análise de Desempenho

Para testar o desempenho do método de Seleção Direta, o algoritmo foi implementado na


linguagem C, utilizando a ferramenta Borland C++ Builder v5.0 e testado em um
computador ATLHON (850 MHz, 256 MB RAM) rodando na plataforma Windows XP. A chave
utilizada é um vetor com 200 caracteres. Foram utilizados dois tipos de estruturas:
estrutura leve e estrutura pesada. A estrutura leve é um registro (do tipo Item) constituído
de uma chave (vetor de 200 caracteres), um inteiro ‘id’ (utilizado para análise da
estabilidade do método). A estrutura pesada é um registro (também do tipo Item)
acrescido de um vetor de 1000 inteiros. O tamanho da entrada utilizada foi 100, 500, 1000,
2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 20000, 30000 e 40000 chaves e
executaram-se os testes com vetores em ordem ascendente, descendente e aleatória.

Através de experimentos realizados com estruturas pesadas, observou-se que não houve
diferença no tempo de ordenação entre as estruturas utilizadas. Isso se justifica pela
linguagem adotada (C++) para a implementação do método, pois a mesma trabalha com
ponteiros e, dessa forma, movimenta-se apenas as referências para os registros.

As tabelas a seguir apresentam exemplos de resultados obtidos com a bateria de testes


realizada:

n C(n) M(n) TEMPO (s) n C(n) M(n) TEMPO(s)


100 4950 297 0,000 100 4950 297 0,000
500 124750 1497 0,010 500 124750 1497 0,010
1000 499500 2997 0,020 1000 499500 2997 0,020
2000 1999000 5997 0,060 2000 1999000 5997 0,060
3000 4498500 8997 0,140 3000 4498500 8997 0,140
4000 7998000 11997 0,310 4000 7998000 11997 0,310
5000 12497500 14997 0,381 5000 12497500 14997 0,381
6000 17997000 17997 0,531 6000 17997000 17997 0,531
7000 24496500 20997 0,731 7000 24496500 20997 0,731
8000 31996000 23997 0,941 8000 31996000 23997 0,941
9000 40495500 26997 1,181 9000 40495500 26997 1,181
10000 49995000 29997 1,452 10000 49995000 29997 1,452
20000 199990000 59997 5,859 20000 199990000 59997 5,859
30000 449985000 89997 13,059 30000 449985000 89997 13,059
40000 799980000 119997 23,244 40000 799980000 119997 23,244
Tabela 2 - Chaves iniciais em ordem aleatória Tabela 3- Chaves iniciais em ordem inversa

Conclusões sobre o SelectionSort

A complexidade do algoritmo em relação ao número de comparações é O(n2) independente


da ordem das chaves que se encontram inicialmente no arquivo. Assim, o método de
classificação por Seleção Direta para instâncias muito grandes não é recomendável. Porém,
por ser um método simples, pode ser utilizado para ordenar um conjunto de pequenas
instâncias com tempo de processamento razoável, já que o método é de fácil entendimento
e implementação.
Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 11

Outra desvantagem se enquadra na situação em que se deseja ordenar um conjunto de


elementos preservando a ordem relativa das chaves inicialmente apresentadas. O método,
portanto, não será recomendado pois é instável.

Em relação ao número de movimentações, o método é O(n) em todos os casos. Deste


modo, o método pode ser interessante para aplicações em que o arranjo é composto de
estruturas pesadas, isto é, que armazenam grande quantidade de informação.

2.3. InsertionSort
Idéia básica do método
A classificação por inserção é parecida com a forma como organizamos as cartas de um
baralho. É caracterizada pelo princípio no qual os n dados a serem ordenados são divididos
em dois segmentos: um já ordenado e outro a ser ordenado. Num momento inicial, o
primeiro segmento é formado por apenas um elemento, que, portanto, pode ser
considerado como já ordenado. O segundo segmento é formado pelos n-1 restantes
elementos. A partir daí o processo se desenvolve em n-1 iterações, sendo que em cada
uma delas um elemento do segmento não ordenado é transferido para o primeiro
segmento, sendo inserido em sua posição correta em relação àqueles que lá já se
encontravam.

Ilustração passo a passo:

Seqüência inicial 44 55 12 42 94 18 06 67
44 55 12 42 94 18 06 67
44 55 12 42 94 18 06 67
12 44 55 42 94 18 06 67
12 42 44 55 94 18 06 67
12 42 44 55 94 18 06 67
12 18 42 44 55 94 06 67
06 12 18 42 44 55 94 67
Seqüência final (ordenada) 06 12 18 42 44 55 67 94

O Algoritmo em alto nível

MÉTODO InsertionSort(A: vetor);


PARA i = 2 até n FAÇA
x = A[i];
inserir x na posição apropriada entre A1 e Ai
FIM-PARA

No processo de se encontrar o local apropriado para o elemento x é conveniente utilizar


operações de comparação, indo da direita para a esquerda (para encontrar a posição onde
x vai ser inserido) e de movimentação (para arredar os elementos à frente de x). Note que
existem duas condições distintas que causam término desse processo de análise: (i) um
elemento é encontrado com uma chave de valor menor do que a chave do elemento x, (ii)
a extremidade esquerda da seqüência destino é atingida (ou seja, não foi encontrado
elemento menor que x, então x deve ser inserido no começo da seqüência). Este caso
típico de uma repetição com duas condições de término conduz ao uso da conhecida técnica
da sentinela 2 . Esta técnica é facilmente aplicada neste caso, colocando-se uma sentinela
A0 com o valor de x. Note que isto requer a extensão do intervalo de definição do índice, na
declaração de A, para 0..n.

2
Utilizamos uma idéia semelhante no algoritmo de pesquisa linear com sentinela, só que a
sentinela ficava na posição n+1 ao invés da posição 0.
Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 12

O pseudocódigo mais detalhado do algoritmo segue abaixo:

MÉTODO InsertionSort(A: vetor);


(1) PARA i = 2 até n FAÇA
(2) {
(3) x = A[i];
(4) A[0] = x; // sentinela
(5) j = i-1;
(6) ENQUANTO (A[j].CHAVE > x.CHAVE)FAÇA
(7) {
(8) A[j+1] = A[j];
(9) j = j-1
(10) }
(11) A[j+1] = x
(12)}

Análise do Algoritmo em relação a C(n) e M(n)

(i) C(n) - Complexidade em função do número de comparações realizadas na estrutura.

Melhor Caso: ocorre quando a seqüência já está ordenada. Neste caso, o algoritmo
executa a comparação da linha (6) apenas uma vez a cada iteração. Logo, C(n) = n-1 para
todo o algoritmo (o PARA da linha 1 é executado n-1 vezes), e a ordem de complexidade
do algoritmo é O(n).

Pior Caso: ocorre quando a seqüência está em ordem decrescente. Neste caso, são feitas i
comparações na linha (6) até ser encontrada a posição correta de inserção do elemento
n
corrente. Logo, C(n) = ∑ i = 2+3+...+n = (n +n-2)/2, e a ordem de complexidade do
i =2
2

algoritmo é O(n2).

(ii) M(n) - Complexidade em função do número de movimentações realizadas na estrutura.

Melhor Caso: como no cálculo de C(n), o melhor caso de M(n) ocorre quando os
elementos do vetor já estão ordenados. Neste caso, as únicas movimentações feitas são as
das linhas (3), (4) e (11). A movimentação da linha (6) não é feita nenhuma vez, pois a
condição do ENQUANTO nunca é satisfeita. Como são feitas n-1 iterações no total (linha 1),
o número total de movimentações M(n) = 3n-3, ou seja, o algoritmo é O(n) em relação a
M(n).

Pior Caso: ocorre quando os elementos estão inversamente ordenados. Neste caso, são
feitas i-1 movimentações referentes à linha (8), acrescidas das 3 movimentações das linhas
(3), (4) e (11), o que totalizam i+2 movimentações das linhas (2) a (12). Logo, M(n) =
n

∑i + 2 =
i =2
4+5+...+(n+2) = (n2+5n-6)/2, e a ordem de complexidade associada do

algoritmo é O(n2).

Estabilidade do método

Ao contrário do método SelectionSort, o InsertionSort é estável, ou seja, a posição relativa


de itens com chaves iguais não é alterada após a ordenação.

Fica como sugestão de exercício, utilizar o InsertionSort para ordenar a seqüência de


números [ 4¹ 6 7 2 9 8 4² 1 3 4³ 9 0 ] e verificar a estabilidade do método.
Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 13

Análise de Desempenho

Na bateria de testes com o InsertionSort foi utilizado um computador AMD Athlon(tm),


Processador de 850 MHz, 256 MB de memória RAM. O algoritmo foi implementado em
linguagem Java. Foram usadas grandes chaves de entrada. As estruturas a serem
ordenadas foram primeiramente inteiros (estrutura leve), e strings de 200 caracteres
(estrutura pesada). Também foram feitas variações na ordem de entrada dos elementos.

Em ordem aleatória, que corresponde ao caso médio, a curva do gráfico n x t cresceu


significativamente em relação ao tipo (inteiros ou strings) e ao número de entradas
(quantidade de elementos). O pior desempenho, portanto, tanto em número de
movimentações como em tempo, foi a entrada em ordem decrescente, cujas curvas dos
gráficos cresceram acentuadamente mostrando na prática que essa forma de entrada dos
dados corresponde ao pior caso.

Conclusões sobre o InsertionSort

O tempo de execução do algoritmo de Inserção Direta depende fundamentalmente da


seqüência dos dados de entrada. Por exemplo, se os dados já estão em ordem, então o
InsertionSort é executado em tempo O(n), se eles estão em ordem decrescente ele é
executado em tempo O(n2 ). Um aspecto importante do algoritmo é que ele é estável.

Existem duas variações deste método muito interessantes. Uma delas utiliza busca binária
na parte ordenada do vetor. Infelizmente este recurso não melhora notavelmente a função
de complexidade do algoritmo original, pois o custo é fortemente influenciado pelo numero
de trocas de posições no vetor, e mesmo com a busca binária, esse numero não é alterado.
A outra variação utiliza a estrutura de lista ligada ao invés de vetor. Isto melhora o
algoritmo pois não é necessário arredar os elementos (basta fazer um ajuste de ponteiros),
fazendo-se muitas trocas de posição. Infelizmente, com este recurso fica impossível utilizar
a pesquisa binária pois perde-se a noção de acesso direto a uma posição.

2.4. QuickSort
Idéia básica do método

QuickSort é o algoritmo de ordenação mais rápido que se conhece para um ampla


variedade de situações, sendo provavelmente mais utilizado do que qualquer outro
algoritmo de ordenação.

O QuickSort foi inventado em 1962 por C. A. R. Hoare, um professor de Programação da


Universidade de Oxford. É uma das obras-primas da Ciência da Computação.

Baseia-se numa idéia muito simples: partir o vetor em dois subvetores, de tal maneira que
todos os elementos do primeiro sejam menores ou iguais a todos os elementos do segundo.
Estabelecida a partição, o problema está resolvido, pois basta aplicar recursivamente a
mesma técnica a cada um dos subvetores, enquanto os subvetores tiverem dois ou mais
elementos.

Para fazer essa partição dos vetores (que é a base do QuickSort), começa-se por escolher
um elemento arbitrário no vetor, por exemplo, o do meio. Este elemento é o pivô. Depois,
percorre-se o vetor da esquerda para a direita, procurando um elemento que não seja
menor do que o pivô (tem de se encontrar, porque o pivô está no vetor e o pivô não é
menor do que ele próprio); e percorre-se também da direita para a esquerda, procurando
um elemento que não seja maior do que o pivô (tem de se encontrar, pela mesma razão).
Nessa altura trocam-se os dois elementos (aquele que não era menor do que o pivô, e que
tinha sido encontrado no percurso ascendente e aquele que não era maior, e tinha sido
Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 14

encontrado no percurso descendente) e continua-se a busca, à procura de mais dois


elementos nas mesmas condições. Mas, é claro, só se continua se os dois percursos ainda
não se tiverem cruzado. Quando os dois percursos se cruzarem, todos os elementos ainda
não visitados pelo percurso descendente, são menores ou iguais a todos os elementos
ainda não visitados pelo percurso ascendente.

Terminada a primeira passagem, o vetor está realmente partido em dois: na parte da


esquerda, todos os elementos são menores ou iguais ao pivô; na da direita, todos são
maiores ou iguais. Assim, para ordenar o vetor inicial, basta aplicar recursivamente o
mesmo algoritmo a cada um dos dois subvetores resultantes da partição. Mas é claro que
só é preciso fazer isso quando os subvetores tiverem mais de um elemento.

Ilustração passo a passo:

Seqüência inicial 44 55 12 42 94 18 06 67
06 55 12 42 94 18 44 67
06 18 12 42 94 55 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
Seqüência final (ordenada) 06 12 18 42 44 55 67 94

O Algoritmo em alto nível

MÉTODO Sort(A: vetor);


{
x = pivô // elemento que está na posição central da partição
s1 = elementos menores que x;
s2 = elementos maiores que x;
retorne Sort(s1)| x | Sort(s2)
}

O pseudocódigo mais detalhado do algoritmo segue abaixo:

MÉTODO QuickSort (A: vetor);

MÉTODO Sort(int Esq, Dir);


(1) {
(2) i = Esq; j = Dir;
(3) x = A[(i+j) DIV 2]; // obtém o pivô x
(4) repetir
(5) enquanto (A[i].CHAVE < x.CHAVE) i++;
(6) enquanto (A[j].CHAVE > x.CHAVE) j--;
(7) se (i <= j)
(8) {
(9) aux = A[i]; A[i] = A[j]; A[j] = aux;
(10) i++; j--;
(11) }
(12) até (i>j);
(13) se (Esq < j) Sort(Esq, j); // ordena partição esquerda
(14) se (i < Dir) Sort(i, Dir) // ordena partição direita
(15) };
Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 15

(1) {
(2) Sort(1,n)
(3) };

Análise do Algoritmo
O custo para percorrer o vetor comparando-se cada elemento com o pivô (linhas 5 e
6), é linear, ou seja, Θ(n).
Pior Caso: ocorre quando o algoritmo de particionamento (linhas 1 a 12) produz a cada
chamada um subproblema de tamanho n-1 e outro de tamanho 1. Daí podemos extrair a
seguinte relação de recorrência:

T(1) = Θ(1)
T(n) = T(n-1) + Θ(n)

Resolvendo esta relação de recorrência, acha-se a complexidade do QuickSort no pior caso,


ou seja, Θ(n2).

Vale observar que o pior caso tem uma possibilidade muito remota de acontecer quando os
elementos forem aleatórios.

Melhor Caso: ocorre quando o particionamento divide o vetor a cada chamada em dois
subproblemas de tamanhos aproximadamente iguais a n/2, que produz a seguinte relação
de recorrência:

T(1) = Θ(1)
T(n) = 2T(n/2) + Θ(n)

Resolvendo esta relação de recorrência, acha-se a complexidade do QuickSort no melhor


caso, ou seja, Θ(n.lgn).

Instabilidade do método

O QuickSort é instável, ou seja, a posição relativa de itens com chaves iguais é alterada
após a ordenação.

Vejamos o seguinte exemplo que ilustra a instabilidade do QuickSort.

Seqüência inicial 44 55 12 42 44* 18 06 67


06 55 12 42 44* 18 44 67
06 18 12 42 44* 55 44 67
06 12 18 42 44* 55 44 67
06 12 18 42 44* 55 44 67
06 12 18 42 44* 44 55 67
Seqüência final (ordenada) 06 12 18 42 44* 44 55 67

Análise de Desempenho

A bateria de testes com o QuickSort foi realizada em um computador Athlon XP2000, 256
Mb de memória RAM. O algoritmo foi implementado em linguagem Java. Foram utilizados
para ordenação vetores de strings[200] geradas com sorteio aleatório de caracteres. A
seguir apresentamos alguns dos resultados obtidos:
Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 16

CRITÉRIO DE ESCOLHA DO PIVÔ: "ELEMENTO CENTRAL"

VETOR ALEATÓRIO

Número de Chaves Tempo (ms)


10 0
50 0
100 0
200 0
500 0
800 0
1000 0
2000 15
3000 16
5000 32
10000 62
20000 141
30000 203
50000 391
100000 891
120000 1110
130000 1296

1400
1200
1000
800
Tempo

600
400
200
0
-200 0 50000 100000 150000
Chaves

CRITÉRIO DE ESCOLHA DO PIVÔ: "ELEMENTO MAIS A DIREITA"

VETOR ORDENADO (ORDEM CRESCENTE)

Número de Chaves Tempo (ms)


10 0
50 0
100 0
200 0
500 0
800 15
1000 31
2000 141
3000 703
5000 overflow
Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 17

10000 overflow
20000 overflow

30000 overflow

50000 overflow

100000 overflow

120000 overflow

130000 overflow

800
700
600
500
Tempo

400
Seqüência1
300
200
100
0
-100 0 1000 2000 3000 4000

Chaves

Observe que o aspecto da curva corresponde ao resultado encontrado na análise de


complexidade do pior caso, Θ(n2).

Conclusões sobre o QuickSort

O QuickSort É um bom exemplo de um método de projeto de algoritmos conhecido como


Dividir-e-Conquistar.

É um método rápido para ordenar grandes entradas. A menor eficiência deste método com
certas distribuições de dados, resulta do QuickSort não considerar a ordem parcial dos
dados a ordenar. De fato, quando aplicado a conjunto de dados quase totalmente
ordenados, comporta-se de forma menos eficaz que a de outros algoritmos de ordenação,
como o método de inserção direta. Uma das formas de evitar o pior caso é escolher três
itens quaisquer do vetor e usar a mediana dos três como o pivô na partição.

Há de se lembrar também que o método QuickSort é instável. Além disso, a implementação


do algoritmo é considerada difícil: um pequeno engano (por exemplo, a troca de um “<”
por um “≤”) pode levar a efeitos inesperados para algumas entradas de dados.

Bibliografia
C.A.R. HOARE: Quicksort. Computer Journal, Vol. 5, 1, 10-15 (1962)

Carlos Augusto Santiago, Elayne Ferreira de Souza, Filipe Nunes Ribeiro, Silas Sallaume,
Inserção Direta, trabalho apresentado na disciplina CIC210, Departamento de Computação,
UFOP, novembro 2004.

Elton José da Silva, notas de aula da disciplina CIC210, Departamento de Computação,


UFOP, julho 2004.
Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 18

Marcio Tadayuki Mine, Matheus de Souza Alves Silva, Valter Cezar Prado Júnior, Rodolfo
Dian de Oliveira Aguiar Soares, Lucas Reis Costa, Estudo sobre o Algoritmo SelectionSort,
trabalho apresentado na disciplina CIC210, Departamento de Computação, UFOP,
novembro 2004.

Nívio Ziviani, Projeto de Algoritmos, com implementação em Pascal e C, Pioneira, 2ª.


Edição, 1993.

Niklaus Wirth, Algoritmos e Estruturas de Dados, Guanabara, 1ª. ed., 1989.

Rodrigo Reis, João Paulo de Melo Elias, QuickSort, trabalho apresentado na disciplina
CIC210, Departamento de Computação, UFOP, novembro 2004.
Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 19

Exercícios de Fixação
1. Desenvolva um algoritmo de busca binária que divide, a cada passo, o conjunto de
elementos em dois conjuntos, um de tamanho aproximadamente duas vezes maior que o
tamanho do outro. Compare a complexidade desse algoritmo com o algoritmo de busca
binária visto em sala.

2. Seja A = (a1, a2, ..., an) um vetor ordenado em ordem crescente, que armazena n
números inteiros. Considere o algoritmo de busca binária que, dado um valor inteiro x,
determina se existe i ∈ {1, 2, ..., n} tal que ai = x. Especifique um algoritmo recursivo de
busca ternária, baseado na partição do intervalo de busca em três partes “iguais”. Faça
uma análise comparativa dos algoritmos de busca binária e de busca ternária em termos de
suas complexidades relacionadas ao número de comparações e chamadas recursivas.

3. É possível desenvolver um algoritmo de busca em um vetor ordenado de n elementos,


baseado em comparação de chaves, que seja mais eficiente que o algoritmo de busca
binária? Justifique.

4. Elabore um algoritmo de complexidade θ(n.lgn) que, dado um conjunto não ordenado


C com n números reais e um real x, determine se existem dois números a e b
pertencentes a C, tais que a + b = x.

5. Dada a seguinte implementação em Pascal de uma busca recursiva em árvore binária de


pesquisa, encontre a ordem de complexidade de melhor caso e pior caso.

Function Busca (x: Tchave; raiz: Tree;):Tree;


begin
if raiz=nil then
Busca = nil
else
if raiz^.chave = x then
busca:=raiz
else
if raiz^.chave < x then
Busca:=Busca(x, raiz^.dir)
else
Busca:=Busca(x,raiz^.esq);
end;

6. Encontre a complexidade do seguinte procedimento para percorrer uma árvore binária


com n nodos, imprimindo os seus elementos em ordem:

(1) Procedure InOrdem (t: Ptr);


(2) begin
(3) if t <> nil then begin
(4) InOrdem(t^.esq);
(5) writeln (t^.elem);
(6) InOrdem(t^.dir)
(7) end
(8) end;

7. Em uma bateria de testes de desempenho realizada com árvores binárias e árvores


AVL foram obtidos os gráficos da figura a seguir. Com base nos gráficos, dê uma
interpretação para o que aconteceu nos testes.
Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 20

t Bin
t
AVL AVL

Bin

Núm. chaves Núm. chaves

(inserção) (deleção)

8. Seja o seguinte algoritmo em alto nível de busca em uma árvore-B:


(1) algoritmo Busca (t, x) //procura por x na árvore t
(2) início
(3) achou := false;
(4) enquanto (t ≠ null) e (not achou) faça
(5) início
(6) lê página do disco //a primeira página lida é a raiz;
(7) busca x na página t;
(8) se encontrar, achou := true, senão segue pelo novo t
(9) fim-enquanto
(10) fim
Encontre a função de complexidade A(n), para o caso médio, em relação ao número
de acessos a disco (obs.: n é a altura da árvore).

9. Escreva um algoritmo para testar se um vetor já está ordenado (ordem crescente). Qual
a ordem de complexidade da sua solução em relação ao número de comparações realizadas
na estrutura?

10. Os algoritmos de ordenação-por-seleção e ordenação-por-inserção rearranjam um


vetor A de modo que ele fique em ordem crescente. Qual dos dois é mais rápido no caso
em que os elementos do vetor são todos iguais?

11. Ordene o vetor de caracteres O R D E N A pelos métodos (a) bolha, (b) seleção e (c)
inserção. Mostre o estado do vetor passo a passo.

12. Faça o diagrama de execução do QuickSort para o seguinte vetor de inteiros: 44, 55,
12, 42, 94, 18, 06, 67.

13. Marque V ou F para as afirmativas a seguir. Justifique.


• M(n) é a função de complexidade relacionada ao número de movimentações realizadas
na estrutura e C(n) ao número de comparações realizadas na estrutura.
• Por convenção, quando dizemos que um vetor está ordenado, significa que o mesmo
está ordenado de forma ascendente, ou seja, Ai <= Aj, para i <j.
• As afirmativas se referem às versões dos algoritmos apresentados neste capítulo.
Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 21

BubbleSort
(1) ( ) A principal desvantagem do BubbleSort é que se o vetor já estiver ordenado, M(n) é
máximo.
(2) ( ) Se alterarmos a condição da linha 4 para "se A[j] <= A[j-1]" o algoritmo deixa de
ser estável.
(3) ( ) Se alterarmos a condição da linha 4 para "se A[j] > A[j-1]" o vetor fica
inversamente ordenado após a ordenação.
(4) ( ) Podemos dizer que o algoritmo BubbleSort funciona como um tipo de seleção
direta, só que dá mais trabalho para colocar cada elemento em sua posição correta em
cada passo da ordenação.
SelectionSort
(1) ( ) Se estiver utilizando uma linguagem que faz deep copy (ex.: Pascal), o método não
é recomendável para ordenar arquivos com registros pesados (registros com grande
quantidade de informação).
(2) ( ) A complexidade do algoritmo em relação a C(n) é quadrática para vetores
previamente ordenados, aleatórios ou em ordem decrescente.
(3) ( ) O algoritmo nunca altera a posição relativa de itens com chaves iguais durante a
ordenação.
(4) ( ) É possível reduzir M(n) quando os itens já estiverem previamente ordenados.
(5) ( ) Se o vetor possui todas as chaves iguais, o SelectionSort possui M(n) igual ao
BubbleSort.
InsertionSort
(1) ( ) O uso de sentinela ajuda a simplificar o teste da condição associada à linha (6) para
inserir A[i] na sequência destino.
(2) ( ) O algoritmo tem complexidade linear para C(n) e M(n) quando os itens já estão
ordenados.
(3) ( ) Se usarmos pesquisa ternária para encontrar o local adequado na parte já ordenada
do vetor consegue-se reduzir C(n).
(4) ( ) Para um vetor inversamente ordenado, a variação do algoritmo Inserção Binária
piora M(n).
(5) ( ) O deslocamento de seqüências de elementos na linha (6) é uma das principais
vantagens do InsertionSort.
QuickSort
(1) ( ) O pior caso do QuickSort acontece quando, a cada passo, o pivô é o menor ou
maior elemento da partição.
(2) ( ) O melhor caso do QuickSort acontece quando a cada passo, o pivô escolhido está
na posição central do vetor.
(3) ( ) Em linguagens mais antigas, onde o emprego de recursividade não é permitido, não
é possível implementar o método QuickSort.
(4) ( ) Uma maneira de evitar o pior caso do algoritmo é escolher ao acaso uma pequena
amostra do arranjo e usar a mediana dessa amostra como pivô na partição sendo
ordenada.
(5) ( ) O QuickSort é O(nlgn) quando todos os elementos do vetor possuem chaves iguais.
Análise de Algoritmos de Pesquisa e Ordenação Elton Silva
®
2007-2 22

14. O algoritmo de ordenação por inserção consta essencialmente de duas etapas


disjuntas: a busca do ponto de inserção de cada novo elemento e a inserção propriamente
dita deste elemento no vetor ordenado até então.

a) Discuta as vantagens e desvantagens de se usar:

i) busca binária, ao invés de busca seqüencial, no vetor ordenado, durante a primeira


etapa.

ii) lista ligada para facilitar a inserção do novo elemento.

b) Usando-se (i) ou (ii) consegue-se melhorar a complexidade no pior caso? Justifique.

15. Sabe-se que a cota inferior para o problema de ordenação é n.lgn. José A. Pressado e
João Sá Bido dizem ter desenvolvido, cada um, um algoritmo que é capaz de ordenar
qualquer conjunto de n elementos com complexidade O(n3/2) e O(n2/3) respectivamente.
Você compraria esses algoritmos? Justifique a sua resposta cuidadosamente.

16. Busque na Internet páginas com exemplos de demonstração do funcionamento de


algoritmos de pesquisa e ordenação vistos em sala. Segue um exemplo:
http://www.cosc.canterbury.ac.nz/people/mukundan/dsal/appldsal.html

17. Visite o site: http://www.geocities.com/SiliconValley/Network/1854/Sort1.html.


Explore o funcionamento dos métodos de ordenação nele apresentados (Bolha, HeapSort,
ShellSort, MergeSort e QuickSort).

18. Faça uma análise de complexidade de melhor caso e pior caso relacionada ao número
de comparações C(n) e trocas T(n) da seguinte implementação do BubbleSort.

// Este programa ordena um vetor de forma ascendente


// Fonte: Deitel, , H. M. C++ Como Programar
#include <iostream>
using std::cout;
using std::endl;
#include <iomanip>
using std::setw;
int main()
{
const int arraySize = 10;
int a[ arraySize ] = { 2, 6, 4, 8, 10, 12, 89, 68, 45, 37 };
int i, hold;
cout << "Data items in original order\n";
for ( i = 0; i < arraySize; i++ )
cout << setw( 4 ) << a[ i ];
for ( int pass = 0; pass < arraySize - 1; pass++ ) // passes
for ( i = 0; i < arraySize - 1; i++ ) // one pass
if ( a[ i ] > a[ i + 1 ] ) { // one comparison
hold = a[ i ]; // one swap
a[ i ] = a[ i + 1 ];
a[ i + 1 ] = hold;
}
cout << "\nData items in ascending order\n";
for ( i = 0; i < arraySize; i++ )
cout << setw( 4 ) << a[ i ];

cout << endl;


return 0;
}

Você também pode gostar