algoritmos e estruturas de...

23
Algoritmos e Estruturas de Dados Décima sexta aula: Quicksort

Upload: duongnguyet

Post on 15-Dec-2018

214 views

Category:

Documents


0 download

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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.