Sunteți pe pagina 1din 5

Quicksort

O problema da ordenao consiste em rearranjar um vetor v[0..n-1] em ordem


crescente, ou seja, permutar os elementos do vetor de modo que tenhamos v[0] v[1]
. . . v[n-1].
O algoritmo Quicksort, inventado por C.A.R. Hoare em 1962, resolve o problema. Em
algumas raras instncias, o Quicksort pode ser to lento quanto os algoritmos
elementares; mas em geral muito rpido. Mais precisamente, o algoritmo consome
tempo proporcional a n log n em mdia e proporcional a n2 no pior caso.
Usaremos duas abreviaturas ao discutir o algoritmo. A expresso v[i..k] x ser
usada como abreviatura de v[j] x para todo j no conjunto de ndices i..k.
Analogamente, a expresso v[i..k] v[p..r] ser interpretada como v[j] v[q]
para todo j no conjunto i..k e todo q no conjunto p..r.

O problema da separao
O ncleo do algoritmo Quicksort o seguinte problema da separao (= partition
subproblem), que formularemos de maneira propositalmente vaga:
rearranjar um vetor v[p..r] de modo que todos os elementos pequenos fiquem na
parte esquerda do vetor e todos os elementos grandes fiquem na parte direita.
O ponto de partida para a soluo deste problema a escolha de um piv, digamos c. Os
elementos do vetor que forem maiores que c sero considerados grandes e os demais
sero considerados pequenos. importante escolher c de tal modo que as duas partes
do vetor rearranjado sejam estritamente menores que o vetor todo. A dificuldade est
em resolver o problema da separao de maneira rpida sem usar muito espao de
trabalho.
O problema da separao admite muitas formulaes concretas. Eis uma primeira:
rearranjar v[p..r] de modo que tenhamos
v[p..j]

v[j+1..r]

para algum j em p..r-1. (Nesta formulao, o piv no explcito.) Eis outra


formulao: rearranjar v[p..r] de modo que
v[p..j-1]

v[j] < v[j+1..r]

para algum j em p..r. Esta pgina usa a segunda formulao; outras formulaes so
mencionadas nos exerccios.

Exerccios 1

1. Positivos e negativos. Escreva uma funo que rearranje um vetor v[p..r] de


inteiros de modo que tenhamos v[p..j-1] 0 e v[j..r] > 0 para algum j em
p..r+1. Faz sentido exigir que j esteja em p..r? Procure fazer uma funo rpida
que no use vetor auxiliar. Repita o exerccio depois de trocar v[j..r] > 0 por
v[j..r] 0. Faz sentido exigir que v[j] seja 0?
2. Um vetor v[p..r] est arrumado se existe j em p..r tal que v[p..j-1] v[j] <
v[j+1..r] . Escreva um algoritmo que decida se v[p..r] est arrumado. Em caso
afirmativo, o seu algoritmo deve devolver o valor de j.
3. Critique a seguinte verso do algoritmo da separao:
4. int sep( int v[], int p, int r) {
5.
int w[1000], i = p, j = r, c = v[p], k;
6.
for (k = p+1; k <= r; ++k)
7.
if (v[k] <= c) w[i++] = v[k];
8.
else w[j--] = v[k];
9.
// agora i == j
10.
w[i] = c;
11.
for (k = p; k <= r; ++k) v[k] = w[k];
12.
return i;
13. }

14. Um programador inexperiente afirma que a seguinte implementao do algoritmo de


separao rearranja o vetor v[p..r], com p < r, e devolve um ndice j em p..r-1
tal que v[p..j] v[j+1..r].
15. int sep( int v[], int p, int r) {
16.
int q, i, j, t;
17.
i = p; q = (p + r) / 2; j = r;
18.
do {
19.
while (v[i] < v[q]) ++i;
20.
while (v[j] > v[q]) --j;
21.
if (i <= j) {
22.
t = v[i], v[i] = v[j], v[j] = t;
23.
++i, --j;
24.
}
25.
} while (i <= j);
26.
return i;
27. }

Mostre um exemplo em que essa funo no d o resultado prometido. E se


trocarmos return i por return i-1? possvel fazer algumas poucas
correes de modo que a funo d o resultado esperado?

O algoritmo da separao
Eis como D. Gries escreve o algoritmo da separao:
int separa( int v[], int p, int r)
{
int c = v[p], i = p+1, j = r, t;
while (1) {
while (i <= r && v[i] <= c) ++i;
while (c < v[j]) --j;
if (i >= j) break;
t = v[i], v[i] = v[j], v[j] = t;
++i; --j;
}
v[p] = v[j], v[j] = c;
return j;

//
//
//
//
//
//
//
//
//
//

1
2
3
4
5
6
7
8
9
10

A funo rearranja o vetor v[p..r] e devolve um elemento j do conjunto p..r tal que
v[p..j-1]

v[j] < v[j+1..r] .

Gostaramos que j ficasse a meio caminho entre p e r, mas devemos estar preparados
para aceitar os casos extremos j == p e j == r .
p

ccccc>c>c>c>c>c

Algumas perguntas que chamam a ateno para detalhes importantes: Que acontece se
executarmos a funo com p igual a r? Que acontece se eliminarmos i <= r na
linha 3? Por que no h um j >= p na linha 4? Que acontece se escrevermos i > j na
linha 5? Que acontece se trocarmos j por i nas linhas 9 e 10?
Anlise do algoritmo da separao

Eu prefiro escrever a funo separa de maneira um pouco diferente para facilitar a


anlise de sua correo:
// Recebe vetor v[p..r] com p < r. Rearranja os
// elementos do vetor e devolve j em p..r tal que
// v[p..j-1] <= v[j] < v[j+1..r].
int
separa( int v[], int p, int r)
{
int c = v[p], i = p+1, j = r, t;
while (/*A*/ i <= j) {
if (v[i] <= c) ++i;
else if (c < v[j]) --j;
else {
t = v[i], v[i] = v[j], v[j] = t;
++i; --j;
}
}
// agora i == j+1
v[p] = v[j], v[j] = c;
return j;
}

No incio de cada iterao, ou seja, a cada passagem pelo ponto A, temos a seguinte
configurao:
p

c c c? ???? >c >c

Na penltima passagem peo ponto A, teremos


p

i=j

ccccc?

>c>c>c>c

Na ltima passagem pelo ponto A valem as seguintes relaes:

i == j+1
p j r
v[p+1..j] c e v[j+1..r] > c.

Portanto, v[p..j-1] v[j] < v[j+1..r] na fim da execuo da funo, como


prometido.
Desempenho do algoritmo da separao

Qual o desempenho da funo separa? Quanto tempo a funo consome? O consumo


de tempo proporcional ao nmero de comparaes entre elementos do vetor. No
difcil perceber que esse nmero proporcional ao nmero r - p + 1 de elementos
do vetor.

Exerccios 2
1. Qual o resultado da funo separa quando os elementos de v[p..r] so todos
iguais? E quando v[p..r] crescente? E quando v[p..r] decrescente?
E quando cada elemento de v[p..r] tem um de dois valores possveis?
2. A funo separa produz um rearranjo estvel do vetor, ou seja, preserva a ordem
relativa de elementos de mesmo valor?
3. Analise e discuta a seguinte verso da funo separa. Ela equivalente do livro de
Cormen, Leiserson, Rivest e Stein.
4. int separa_CLRS( int v[], int p, int r) {
5.
int c = v[r], i = p, j, t;
6.
for (j = p; j < r; ++j) {
7.
if (v[j] <= c) {
8.
t = v[i], v[i] = v[j], v[j] = t;
9.
++i;
10.
}
11.
}
12.
t = v[i], v[i] = v[r], v[r] = t;
13.
return i;
14. }

15. Critique a seguinte verso do algoritmo da separao:


16. int separa( int v[], int p, int r) {
17.
int c = v[p], i = p+1, j = r, t;
18.
while (i <= j) {
19.
if (v[i] <= c) ++i;
20.
else {
21.
t = v[i], v[i] = v[j], v[j] = t;
22.
--j;
23.
}
24.
}

25.
26.
27. }

v[p] = v[j], v[j] = c;


return j;

28. Critique a seguinte verso do algoritmo da separao:


29. int separa( int v[], int p, int r) {
30.
int c = v[p], i = p+1, j = r, t;
31.
while (i <= j) {
32.
if (v[i] <= c) {
33.
v[i-1] = v[i];
34.
++i;
35.
}
36.
else {
37.
t = v[i], v[i] = v[j], v[j] = t;
38.
--j;
39.
}
40.
}
41.
v[j] = c;
42.
return j;
43. }
44. Analise e discuta a seguinte verso da funo separa. Ela foi extrada do livro de

E. Roberts.
45. int separa_R( int v[], int p, int r) {
46.
int c = v[p], i = p+1, j = r, t;
47.
while (1) {
48.
while (i < j && c < v[j]) --j;
49.
while (i < j && v[i] <= c) ++i;
50.
if (i == j) break;
51.
t = v[i], v[i] = v[j], v[j] = t;
52.
}
53.
if (v[i] > c) return p;
54.
v[p] = v[i], v[i] = c;
55.
return i;
56. }

57. Verso Recursiva. Escreva uma verso recursiva da funo separa.


58. Desafio. Escreva uma verso da funo separa tal que (r-p)/4 j-p 3(r-p)/4,
sendo j o ndice produzido pela funo.
59. Lista. Suponha dada uma lista encadeada que armazena nmeros inteiros. Escreva
uma funo que transforme a lista em duas: a primeira contendo os nmeros pares e a
segunda contendo os mpares.
}

S-ar putea să vă placă și