2. Observação
Quando o aluno acessa a apresentação que está disponível no site
(sites.google.com/view/jonasmendonca/home), o aluno tem acesso a
todos os códigos que são apresentados. Isso evita que o aluno perca
tempo fazendo cópias e temos mais tempo para trabalhar o uso dos
algoritmos e sugestões de otimização que são apresentadas pelos
próprios estudantes em sala.
3. Algoritmos de Ordenação
São algoritmos que colocam os elementos de uma dada
sequência em uma certa ordem
(ascendente/descendente).
As ordens mais usadas são a numérica e a lexicográfica
(quando ordenamos palavras ou textos).
4. Estabilidade
Estabilidade: relativo à manutenção da ordem original de
itens de chaves iguais.
Um método de ordenação é estável se a ordem relativa
dos itens com chaves iguais não se altera durante a
ordenação.
5. Critério de Avaliação
Sendo n o número de registros no arquivo, as medidas de
complexidade relevantes são:
Número de comparações C(n) entre chaves.
Número de movimentações M(n) de itens
6. Outras considerações
O uso econômico da memória disponível é um requisito primordial na
ordenação interna.
Métodos que utilizam listas encadeadas não são muito utilizados.
Métodos que fazem cópias dos itens a serem ordenados possuem menor
importância.
7. Bubble sort
Os elementos vão “borbulhando” a cada iteração do método até a posição
correta para ordenação da lista.
O método poderia parar quando nenhum elemento borbulhace/trocasse de
posição.
Como os elementos são trocados (borbulhados) frequentemente, há um
alto custo de troca de elementos.
16. Bubble sort
Vantagens:
Algoritmo simples
Algoritmo estável
Desvantagens:
O fato de o arquivo já estar ordenado não ajuda em nada, pois o custo continua quadrático.
Adequado apenas se arquivo pequeno
Como melhorar?
18. Selection sort
Seleção do n-ésimo menor (ou maior) elemento da lista
Troca do n-ésimo menor (ou maior) elemento com a n-ésima posição da
lista
Uma única troca por vez é realizada
21. Selection sort
Vantagens:
Custo linear no tamanho da entrada para o número de movimentos de registros.
É o algoritmo a ser utilizado para arquivos com registros muito grandes.
É muito interessante para arquivos pequenos.
Desvantagens:
O fato de o arquivo já estar ordenado não ajuda em nada, pois o custo continua quadrático.
O algoritmo não é estável.
23. Insertion sort
Algoritmo utilizado pelo jogador de cartas
As cartas são ordenadas da esquerda para direita uma por uma.
O jogador escolhe a segunda carta e verifica se ela deve ficar antes ou na posição que está.
Depois a terceira carta é classificada, deslocando-a até sua correta posição.
O jogador realiza esse procedimento até ordenar todas as cartas
24. Insertion sort
O número mínimo de comparações e movimentos ocorre quando os itens
estão originalmente em ordem.
O número máximo ocorre quando os itens estão originalmente na ordem
reversa.
É o método a ser utilizado quando o arquivo está “quase” ordenado.
É um bom método quando se deseja adicionar uns poucos itens a um
arquivo ordenado, pois o custo é linear.
O algoritmo de ordenação por inserção é estável.
29. Dividir para conquistar
•Dividir para Conquistar: tática de guerra aplicada ao
projeto de algoritmos
•Um algoritmo de divisão e conquista é aquele que resolve o
problema desejado combinando as soluções parciais de (um
ou mais) subproblemas, obtidas recursivamente.
30. Projeto de Algoritmos por Divisão e Conquista
•Seja H um problema algorítmico. O método da divisão e
conquista consiste em tentar resolver H, através dos
seguintes passos:
•Decompor H em subproblemas H1, H2, ..., Hk
31. Projeto de Algoritmos por Divisão e Conquista
•Seja H um problema algorítmico. O método da divisão e conquista consiste em
tentar resolver H, através dos seguintes passos:
•Decompor H em subproblemas H1, H2, ..., Hk
•Resolver cada subproblema Hi, através de uma aplicação recursiva desse
método
32. Projeto de Algoritmos por Divisão e Conquista
•Seja H um problema algorítmico. O método da divisão e conquista consiste em
tentar resolver H, através dos seguintes passos:
•Decompor H em subproblemas H1, H2, ..., Hk
•Resolver cada subproblema Hi, através de uma aplicação recursiva desse
método
•Compor a solução de H, a partir das soluções de H1, H2, ..., Hk
33. Algoritmo Genérico - Dividir para Conquistar
DivConq(x) Entrada: A instância x
Saída: Solução y da
instância x
34. Algoritmo Genérico - Dividir para Conquistar
DivConq(x) Entrada: A instância x
Saída: Solução y da
instância x
Se x é suficientemente pequeno então
35. Algoritmo Genérico - Dividir para Conquistar
DivConq(x) Entrada: A instância x
Saída: Solução y da
instância x
Se x é suficientemente pequeno então
Retorne Solução(x) (dada pelo algoritmo para peq. Instâncias)
36. Algoritmo Genérico - Dividir para Conquistar
DivConq(x) Entrada: A instância x
Saída: Solução y da
instância x
Se x é suficientemente pequeno então
Retorne Solução(x) (dada pelo algoritmo para peq. Instâncias)
Senão
37. Algoritmo Genérico - Dividir para Conquistar
DivConq(x) Entrada: A instância x
Saída: Solução y da
instância x
Se x é suficientemente pequeno então
Retorne Solução(x) (dada pelo algoritmo para peq. Instâncias)
Senão
Divisão
38. Algoritmo Genérico - Dividir para Conquistar
DivConq(x) Entrada: A instância x
Saída: Solução y da
instância x
Se x é suficientemente pequeno então
Retorne Solução(x) (dada pelo algoritmo para peq. Instâncias)
Senão
Divisão
Decomponha x em instâncias menores x1, ..., xk
Para i=1 até k faça yi=DivConq(xi)
39. Algoritmo Genérico - Dividir para Conquistar
DivConq(x) Entrada: A instância x
Saída: Solução y da
instância x
Se x é suficientemente pequeno então
Retorne Solução(x) (dada pelo algoritmo para peq. Instâncias)
Senão
Divisão
Decomponha x em instâncias menores x1, ..., xk
Para i=1 até k faça yi=DivConq(xi)
Conquista
40. Algoritmo Genérico - Dividir para Conquistar
DivConq(x) Entrada: A instância x
Saída: Solução y da
instância x
Se x é suficientemente pequeno então
Retorne Solução(x) (dada pelo algoritmo para peq. Instâncias)
Senão
Divisão
Decomponha x em instâncias menores x1, ..., xk
Para i=1 até k faça yi=DivConq(xi)
Conquista
Combine as soluções yi para obter a solução y de x
Retorne(y)
43. Exemplo 1
Exponenciação
Problema: Calcular na, para todo real a e inteiro n0.
1a. Solução: (Indução Fraca):
Caso base: n=0; a0 = 1
Hipótese de Indução: Suponha que, para qualquer inteiro n>0 e real a, sei
calcular an-1
44. Exemplo 1
Exponenciação
Problema: Calcular na, para todo real a e inteiro n0.
1a. Solução: (Indução Fraca):
Caso base: n=0; a0 = 1
Hipótese de Indução: Suponha que, para qualquer inteiro n>0 e real a, sei
calcular an-1
Passo da Indução: Queremos provar que conseguimos calcular an, para n>0.
45. Exemplo 1
Exponenciação
Problema: Calcular na, para todo real a e inteiro n0.
1a. Solução: (Indução Fraca):
Caso base: n=0; a0 = 1
Hipótese de Indução: Suponha que, para qualquer inteiro n>0 e real a, sei
calcular an-1
Passo da Indução: Queremos provar que conseguimos calcular an, para n>0.
Sei calcular an-1. Então, calculo an multiplicando an-1 por a
46. Exemplo 1 - Algoritmo Expon(a, n)
Entrada: A base a e o expoente n
Saída: O valor de an
Se n = 0 então
Retorne(1) {caso base}
Senão
an’ := Expon(a, n-1)
an := an’*a
Retorne(an)
47. Exemplo 1 - Complexidade
T(n): nº de operações realizadas pelo algoritmo para calcular an
1, n=0
T(n) =
T(n-1)+ 1, n>0
48. Exemplo 1 - Complexidade
T(n): nº de operações realizadas pelo algoritmo para calcular an
1, n=0
T(n) =
T(n-1)+ 1, n>0
49. Exemplo 1 - Complexidade
T(n): nº de operações realizadas pelo algoritmo para calcular an
1, n=0
T(n) =
T(n-1)+ 1, n>0 T(n)= O(n)
51. Merge sort
O Merge sort, ou ordenação por intercalação, é um exemplo de algoritmo de
ordenação do tipo dividir-para-conquistar.
Sua idéia básica é criar uma sequência ordenada a partir de duas outras
também ordenadas. Para isso, ele divide a sequência original em pares de
dados, ordena-as; depois as agrupa em sequências de quatro elementos, e
assim por diante, até ter toda a sequência dividida em apenas duas partes.
Estas 2 partes são então combinadas para se chegar ao resultado final.
52. Merge sort
Portanto, os 3 passos seguidos pelo MergeSort são:
1. Dividir: Dividir os dados em subsequências pequenas;
2. Conquistar: Classificar as duas metades recursivamente aplicando o
merge sort;
3. Combinar: Juntar as duas metades em um único conjunto já ordenado.
66. Exercício
Dada a sequência de números: 12 3 4 13 9 2 6 11 5 1 8 7 10. Ordene em
ordem crescente utilizando o algoritmo aprendido em sala (MergeSort),
apresentando a sequência dos números a cada passo (Teste de Mesa).
67. Quick sort
Proposto por Hoare em 1960 e publicado em 1962.
É o algoritmo de ordenação interna mais rápido que se conhece para uma
ampla variedade de situações.
Provavelmente é o mais utilizado.
A idéia básica é dividir o problema de ordenar um conjunto com n itens em
dois problemas menores.
Os problemas menores são ordenados independentemente.
Os resultados são combinados para produzir a solução final.
68. Quick sort
A parte mais delicada do método é o processo de partição.
O vetor A [Esq..Dir] é rearranjado por meio da escolha arbitrária de um pivô
x.
O vetor A é particionado em duas partes:
Parte esquerda: chaves ≤ x.
Parte direita: chaves > x.
69. Partição
1. Escolha arbitrariamente um pivô x.
2. Percorra o vetor a partir da esquerda até que A[i] > x.
3. Percorra o vetor a partir da direita até que A[j] ≤ x.
4. Troque A[i] com A[j].
5. Continue este processo até os apontadores i e j se cruzarem.
70. Partição
1. Escolha arbitrariamente um pivô x.
2. Percorra o vetor a partir da esquerda até que A[i] > x.
3. Percorra o vetor a partir da direita até que A[j] ≤ x.
4. Troque A[i] com A[j].
5. Continue este processo até os apontadores i e j se cruzarem.
71. Após a partição
O vetor A[Esq..Dir] está particionado de tal forma que:
Os itens em A[Esq], A[Esq + 1], ..., A[j] são menores ou iguais a x;
Os itens em A[i], A[i + 1], ..., A[Dir] são maiores ou iguais a x.
72. Exemplo 1
O pivô x é escolhido como sendo:
O elemento central: A[(i + j) / 2].
Exemplo:
3 6 4 5 1 7 2
78. Quick sort
Vantagens:
É extremamente eficiente para ordenar arquivos de dados.
Necessita de apenas uma pequena pilha como memória auxiliar.
Requer cerca de n log n comparações em média para ordenar n itens.
Desvantagens:
Sua implementação é muito delicada e difícil:
Um pequeno engano pode levar a efeitos inesperados para algumas entradas de dados.
O método não é estável.
79. Quicksort
Melhor caso: particionamento produz segmentos com
mesmo tamanho.
Pior caso: Ocorrerá sempre que o vetor já estiver
ordenado ou em ordem inversa e escolhermos a menor
(ou maior) chave como particionadora.
80. Exercício
Dada a sequência de números: 12 3 4 13 9 2 6 11 5 1 8 7 10. Ordene em
ordem crescente utilizando o algoritmo Quick sort.
81. Heap Sort
Definição de heap - árvore binária completa: tem todos os nós possíveis em
cada nível; exemplos:
82. Heap sort
O heapsort utiliza uma estrutura de dados chamada heap para ordenar os elementos a medida
que os insere na estrutura. Assim, ao final das inserções, os elementos podem ser
sucessivamente removidos da raiz da heap, na ordem desejada.
Um heap é uma estrutura de dados baseada em árvore binária que segue um critério (ou
condição) bem-definido(a).
Estruturalmente, deve ser uma árvore quase completa:
o último nível pode não conter os nós mais à direita.
A heap pode ser representada como uma árvore ou como um vetor. Para uma ordenação
crescente, deve ser construído um heap máximo (o maior elemento fica na raiz). Para uma
ordenação decrescente, deve ser construído um heap mínimo (o menor elemento fica na raiz).
83. Condição do heap
Os dados armazenados em um heap devem satisfazer a seguinte condição:
Todo nó deve ter valor maior ou igual com relação aos seus filhos (Heap Máximo).
A condição não determina nenhuma relação entre os filhos de um nó (não confundir com
árvore binária de pesquisa).
As folhas de nível mais alto estão o mais à esquerda possível
84. Como representar heaps?
Podemos representar heaps como árvores binárias ou vetores.
A idéia é linearizar a árvore por níveis.
85. Relacionando os nós do Heap
A representação em vetores permite relacionar os nós do heap da seguinte
forma:
raiz da árvore: primeira posição do vetor
filhos de um nó na posição i: posições 2i e 2i+1
pai de um nó na posição i: posição [i / 2]
86.
87. Fila de Prioridades
É um heap em que o valor de um nó é sempre menor ou igual aos de seus
eventuais filhos.
99. Heap sort
O método consiste em duas partes principais:
1. Inserir elemento por elemento do vetor numa fila de prioridades
2. Retirar pela raiz todos os elementos da fila de prioridades, retornando-os
ao vetor
101. Heap sort
void FormarFilaPrioridades (int n, vetor V) {
int i, j, temp;
for (i = n-2; i >= 0; i--) {
j = i;
while (j < n-1 && V[j] < V[n-(n-j)/2]) {
temp = V[j];
V[j] = V[n-(n-j)/2];
V[n-(n-j)/2] = temp; j = n-(n-j)/2;
}
}
}
102. Heap sort
void OrdenarFilaPrioridades (int n, vetor V) {
int i, j, k, temp, min; bool parou;
for (i = 0; i < n-1; i++) {
min = V[n-1]; V[n-1] = V[i]; V[i] = min;
parou = false; j = n-1;
while (! parou && 2*j-n > i) {
if (2*j-n == i+1 || V[2*j-n] < V[2*j-n-1])
k = 2*j-n;
else k = 2*j-n-1;
if (V[j] > V[k]) {
temp = V[j]; V[j] = V[k];
V[k] = temp; j = k;
}
else parou = true;
}
}
}
103. Exercício
Dada a sequência de números: 12 3 4 13 9 2 6 11 5 1 8 7 10. Ordene em
ordem crescente utilizando o algoritmo Heap sort.
104. Exercício
Alterar os códigos de todos os métodos de ordenação e medir o tempo gasto
para ordenar vetores com tamanhos 10, 100, 1000, 10000 e 100000
elementos aleatoriamente gerados.
Analisar a curva de crescimento do tempo gasto para executar os métodos
implementados.
Fazer a mesma operação com vetores ordenados em ordem crescente e com
vetores ordenados em ordem decrescente.