algoritmos e estruturas de...
TRANSCRIPT
![Page 1: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/1.jpg)
Algoritmos
e Estruturas de Dados
Décima sexta aula:
Quicksort
![Page 2: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/2.jpg)
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 2
Nesta aula vamos…
• Estudar o quicksort.
• Considerar algumas variantes:
Quicksort geral, parametrizando a função de comparação.
Quicksort com partição de Hoare.
Quicksort com partição de Lomuto.
Quicksort iterativo, com pilha.
Quicksort com pivô aleatório.
Quicksort com pivô mediana de três.
Quicksort com cutoff.
![Page 3: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/3.jpg)
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 3
Quicksort
clássico
• Função principal:
void qs (int *a, int x, int y) { int i = x; int j = y; int p = a [(i+j)/2]; do { while (a[i] < p) i++; while (p < a[j]) j−−; if (i <= j) { int m = a[i]; a[i] = a[j]; a[j] = m; i++; j−−; } } while (i <= j); if (x < j) qs (a, x, j); if (i < y) qs (a, i, y); }
void quicksort (int *a, int n) { if (n > 0) qs (a, 0, n−1); }
O quicksort é uma das obras-primas
da programação. Esta é a versão
original de Hoare, tal como
publicada por Wirth no livro
Algorithms + Data Structures =
Programs, aqui adaptada para C.
![Page 4: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/4.jpg)
Primeira parte, partição • Reorganizar os elementos de um vector de
maneira a que todos os que são menores ou
iguais ao elemento de índice médio venham
antes de todos os restantes, isto é, de todos
os que são maiores ou iguais a esse elemento
de índice médio.
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 4
[17, 4, 13, 6, 0, 12, 18, 0, 13, 14, 1, 19]
[1, 4, 0, 6, 0, 12, 18, 13, 13, 14, 17, 19]
[14, 13, 9, 1, 0, 3, 6, 13, 19, 10, 11, 7]
[3, 0, 1, 9, 13, 14, 6, 13, 19, 10, 11, 7]
![Page 5: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/5.jpg)
Segunda parte,
continuar recursivamente
• Isto é, aplicar a partição a cada uma das
“metades” do vector:
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 5
[17, 4, 13, 6, 0, 12, 18, 0, 13, 14, 1, 19]
[1, 4, 0, 6, 0, 12, 18, 13, 13, 14, 17, 19]
[1, 4, 0, 6, 0, 12, 18, 13, 13, 14, 17, 19]
[0, 0, 4, 6, 1, 12, 18, 13, 13, 14, 17, 19]
[0, 0, 1, 4, 6, 12, 18, 13, 13, 14, 17, 19]
[0, 0, 1, 4, 6, 12, 13, 13, 18, 14, 17, 19]
Note bem: por via da aplicação recursiva, quando
chegarmos à segunda metade, já a primeira estará
ordenada!
![Page 6: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/6.jpg)
Como fazer a partição
1. Selecionar o elemento de índice médio, chamado
pivô. (Dizemos que é o pivô central.)
2. Percorrer o vector da esquerda para a direita até
encontrar um elemento maior ou igual ao pivô.
3. Percorrer o vector da direita para a esquerda até
encontrar um elemento menor ou igual ao pivô.
4. Trocar os elementos selecionados nos passos 2 e 3,
se o primeiro ainda estiver à esquerda do segundo.
5. Repetir a partir de 2, continuando a partir das
posições seguintes, até os percursos se cruzarem.
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 6
![Page 7: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/7.jpg)
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 7
Outras ordenações
• Tal como está programado na página
anterior, o quicksort ordena um vector de
números por ordem ascendente.
• Se quisermos outras ordenações, temos
de programar uma nova função quicksort
ad-hoc.
• Ou então parametrizar a função de
comparação.
![Page 8: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/8.jpg)
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 8
Quicksort
geral
• Função principal:
void qs_general (int *a, int x, int y, int cmp (int, int)) { int i = x; int j = y; int p = a [(i+j)/2]; do { while (cmp (a[i], p) < 0) i++; while (cmp (p, a[j]) < 0) j−−; if (i <= j) { int m = a[i]; a[i] = a[j]; a[j] = m; i++; j−−; } } while (i <= j); if (x < j) qs_general (a, x, j, cmp); if (i < y) qs_general (a, i, y, cmp); }
void quicksort_general ( int *a, int n, int cmp (int, int)) { if (n > 0) qs_general (a, 0, n−1, cmp); }
É geral, porque permite usar
qualquer função de
comparação, mas não é
genérico. Ser genérico
significa poder ser usado
com vectores com
elementos de qualquer tipo,
o que não é o caso aqui.
![Page 9: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/9.jpg)
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 9
Ordenação por partição • O quicksort representa o método de
ordenação por partição:
• Primeiro “parte-se” o vector em dois subvectores: o dos elementos “pequenos” (isto é, menores ou iguais ao pivô) e o dos elementos grandes (maiores ou iguais ao pivô).
• Depois aplica-se o algoritmo recursivamente a ambas as “partes”.
• O algoritmo de partição é interessante por si só, e merece ser autonomizado.
![Page 10: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/10.jpg)
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 10
Pair hoare (int *a, int x, int y) { Pair result; int i = x; int j = y; int p = a [(i+j)/2]; do { while (a[i] < p) i++; while (p < a[j]) j−−; if (i <= j) { int m = a[i]; a[i] = a[j]; a[j] = m; i++; j−−; } } while (i <= j); result.first = j; result.second = i; return result; }
Partição de Hoare O resultado é um par de índices:
typedef struct { int first; int second; } Pair;
void qs_hoare (int *a, int x, int y) { Pair r = hoare (a, x, y); if (x < r.first) qs_hoare (a, x, r.first); if (r.second < y) qs_hoare (a, r.second, y); }
void quicksort_hoare (int *a, int n) { if (n > 0) qs_hoare (a, 0, n−1); }
Função principal.
Função
recursiva.
![Page 11: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/11.jpg)
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 11
Partição de Lomuto • O livro Introduction to Algorithms usa uma
partição diferente da de Hoare, muito
interessante também, chamada partição de
Lomuto: int lomuto (int *a, int x, int y) { int p = a [y]; int result = x; int i; for (i = x; i < y; i++) if (a[i] < p) numbers_swap (a, result++, i); numbers_swap (a, result, y); return result; }
Veja a explicação na
página seguinte.
![Page 12: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/12.jpg)
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 12
Explicação • O pivô (a azul) é o último
elemento (está na posição y). Em cada momento, os elementos nas posições [x..result[ (a amarelo) são menores ou iguais ao pivô e os nas posições [result..i[ (a verde) são maiores do que o pivô. Os outros (nas posições [i..y[, a cor-de-rosa) não sabemos. No final (depois do ciclo) trocamos o elemento na posição result com o pivô, o que garante que o pivô já encontrou a sua posição definitiva, e que o vector está partido.
3 7 2 9 5 8 1 7 4 5
3 7 2 9 5 8 1 7 4 5
3 7 2 9 5 8 1 7 4 5
3 2 7 9 5 8 1 7 4 5
3 2 7 9 5 8 1 7 4 5
3 2 5 9 7 8 1 7 4 5
3 2 5 9 7 8 1 7 4 5
3 2 5 1 7 8 9 7 4 5
3 2 5 1 7 8 9 7 4 5
3 2 5 1 4 8 9 7 7 5
3 2 5 1 4 5 9 7 7 8
![Page 13: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/13.jpg)
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 13
Quicksort de Lomuto
• Observe:
void qs_lomuto (int *a, int x, int y) { int r = lomuto (a, x, y); if (x < r−1) qs_lomuto (a, x, r−1); if (r+1 < y) qs_lomuto (a, r+1, y); }
Fica um pouco mais simples, porque o
resultado da partição é um número inteiro,
representando a posição onde fica o pivô
depois da troca final, e não um par de
inteiros, como na partição de Hoare.
![Page 14: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/14.jpg)
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 14
Podemos evitar
as chamadas
recursivas,
empilhando “à
mão” os
argumentos
para posterior
processamento:
void quicksort_iterative_lomuto (int *a, int n) { Stack s = stack_init (); stack_push (&s, 0); stack_push (&s, n−1); while (!stack_empty (s)) { int x; int y; int r; y = stack_top (s); stack_pop (&s); x = stack_top (s); stack_pop (&s); r = lomuto (a, x, y); if (r+1 < y) { stack_push (&s, r+1); stack_push (&s, y); } if (x < r−1) { stack_push (&s, x); stack_push (&s, r−1); } } }
Quicksort iterativo
Empilhamos
aos pares, da
direita para
esquerda, para
ao desempilhar,
sair da
esquerda para
a direita.
![Page 15: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/15.jpg)
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 15
Pivô aleatório • Uma má escolha do pivô pode comprometer o
desempenho do quicksort.
• Para evitar casos particulares desagradáveis,
podemos “randomizar” o pivô.
• Por exemplo, no quicksort de Lomuto, basta
substituir a partição por esta:
int lomuto_with_random_pivot (int *a, int x, int y)
{
numbers_swap (a, x + rand_to (y−x), y);
return lomuto (a, x, y);
} Troca-se o último com um
elemento escolhido aleatoria-
mente. Este será o pivô.
![Page 16: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/16.jpg)
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 16
Pivô mediana de 3
• Escolher para pivô o mínimo ou o máximo do
vector é mau, porque todos os elementos vão
para a mesma partição.
• Evita-se isso usando para pivô a mediana do
conjunto formado pelo primeiro elemento, pelo
elemento central e pelo último elemento.
• Aliás, ordena-se estes três elementos, sobre o
vector e depois escolhe-se o elemento central,
que, depois da ordenação, será a mediana
dos 3.
![Page 17: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/17.jpg)
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 17
• Usa-se uma espécie de
bubblesort, à mão.
• Programamos assim, por
extenso, em vez de usar
a função swap (que troca
o valor de duas
variáveis), para evitar
três chamadas de função
suplementares, por cada
chamada recursiva do
quicksort.
void sort_3 (int *x, int *y, int *z)
{
if (*y > *z)
{
int m = *y;
*y = *z;
*z = m;
}
if (*x > *y)
{
int m = *x;
*x = *y;
*y = m;
}
if (*y > *z)
{
int m = *y;
*y = *z;
*z = m;
}
}
Ordenando 3
Comprido
e chato.
![Page 18: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/18.jpg)
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 18
Quicksort mediana de 3 void qs_median_of_3 (int *a, int x, int y)
{
sort_3 (a+x, a+(x+y)/2, a+y);
if (y − x > 2)
{
Pair r = hoare (a, x+1, y−1);
if (x < r.first)
qs_median_of_3 (a, x, r.first);
if (r.second < y)
qs_median_of_3 (a, r.second, y);
}
} void quicksort_median_of_3 (int*a, int n)
{
if (n > 0)
qs_median_of_3 (a, 0, n−1);
}
Se houver três ou menos
elementos, já estão
ordenados.
O primeiro elemento e
último já estão nas suas
partições. Logo,
podemos excluí-los do
processo de partição.
![Page 19: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/19.jpg)
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 19
Cutoff • A ideia é não fazer as chamadas
recursivas quando os subvectores
tiverem menos do que um certo número
de elementos, o cutoff.
• O vector ficará “quase ordenado”, mas
haverá alguns elementos localmente
fora de ordem.
• A seguir entra o insertionsort, pois é
mesmo desse tipo de vectores que ele
gosta mais.
![Page 20: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/20.jpg)
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 20
Quicksort com cutoff • De facto, é com mediana de 3 e cutoff:
void qs_cutoff (int *a, int x, int y)
{
sort_3 (a+x, a+(x+y)/2, a+y);
if (y − x > 2)
{
Pair r = hoare (a, x+1, y−1);
if (r.first − x + 1 >= CUTOFF)
qs_cutoff (a, x, r.first);
if (y − r.second +1 >= CUTOFF)
qs_cutoff (a, r.second, y);
}
} void quicksort_cutoff (int *a, int n)
{
if (n > 0)
qs_cutoff (a, 0, n−1);
insertionsort (a, n);
}
#define CUTOFF 3
![Page 21: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/21.jpg)
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 21
Exercícios
• Programe o quicksort geral para
vectores de cadeias.
• Programa a versão iterativa do quicksort
de Hoare.
![Page 22: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/22.jpg)
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 22
Controlo
• Que variantes do quicksort estudámos
hoje? Qual a melhor?
• Qual o tamanho máximo da pilha no
quicksort iterativo?
![Page 23: Algoritmos e Estruturas de Dadosw3.ualg.pt/~pjguerreiro/sites/15_aed_1112/lessons/aed_1112_16.pdf · Primeira parte, partição • Reorganizar os elementos de um vector de maneira](https://reader031.vdocuments.net/reader031/viewer/2022031323/5c1428fb09d3f207708b5806/html5/thumbnails/23.jpg)
02-07-2012 Algoritmos e Estruturas de Dados I - 16 © Pedro Guerreiro 23
Na próxima aula
• Analisaremos a complexidade do
quicksort.
• Estudaremos os casos em que se dá
mal.
• Trataremos de mais um interessante
algoritmo de ordenação: o mergesort.