Anda di halaman 1dari 66

ALGORITMOS E ESTRUTURA DE DADOS II

Leandro Rosniak Tibola


E-mail: tibola@fw.uri.br
Pgina Pessoal: www.fw.uri.br/~tibola

Sumrio

1 Listas Lineares 3
1.1 Alocao Esttica versus Dinmica 4
1.2 Alocao Seqencial 4
1.3 Alocao Encadeada 5

2 Pilhas 6
2.1 Declarando e inicializando uma pilha 7
2.2 Verificando limites da pilha 7
2.3 Inserindo e removendo elementos na pilha 7
2.4 Verificando o elemento do topo da pilha 8

3 Filas 9
3.1 Declarando e inicializando uma fila 10
3.2 Verificando limites da fila 10
3.3 Inserindo e removendo elementos da fila 10
3.4 Problemas na implementao seqencial de filas 11
3.5 Implementao circular para filas 11
3.6 Inserindo e removendo elementos da fila circular 12

4 Classificao por Insero 13


4.1 Mtodo da Insero Direta. 13
4.1.1 Exemplo e implementao de Insero Direta 13
4.1.2 Anlise do Desempenho 14
4.2 Insero Direta com Busca Binria 15
4.3 Mtodo dos Incrementos Decrescentes - Shellsort 16
4.3.1 Exemplo Incrementos Decrescentes Shellsort 16
4.3.2 Implementao 17
4.3.3 Anlise do Desempenho 17

5 Classificao por Trocas 19


5.1 Mtodo da Bolha Bubblesort 19
5.1.1 Implementao 20
5.1.2 Anlise do Desempenho 20
2
5.2 Mtodo da Agitao - Shakesort 21
5.3 Mtodo do Pente Combsort 21
5.4 Mtodo de Partio e Troca - Quicksort 22
5.4.1 Descrio do Mtodo 23
5.4.2 Implementao 25
5.4.3 Anlise do Desempenho 26

6 Classificao por Seleo 29


6.1 Mtodo de Seleo Direta 29
6.1.1 Implementao 29
6.1.2 Anlise do Desempenho 30
6.2 Mtodo da Seleo em rvore HeapSort 30
6.2.1 Exemplo 32
6.2.2 Implementao 37
6.2.3 Anlise do Desempenho 38
6.3 Mtodo de Seleo em rvore Amarrada ThreadedHeapSort 38

7 Classificao por Distribuio de Chaves - Radixsort 41


7.1 Implementao 43
7.2 Desempenho 45

8 Classificao por Intercalao 46


8.1 Implementao 46
8.2 Anlise do Desempenho 49

9 Listas Ordenadas 51
9.1 Implementao Encadeada para Listas Ordenadas 51
9.1.1 A Operao de Insero 52
9.1.2 A operao de Remoo 54
9.1.3 A Operao de Pesquisa 55
9.1.4 Imprimindo uma Lista Ordenada 56
9.1.5 Destruindo uma Lista Ordenada 57
9.2 Tcnicas de Encadeamento Nodos cabea e sentinela 57
9.3 Encadeamento circular 59
9.4 Encadeamento Duplo 61

Bibliografia 62

Lista de exerccios 63
3

1 LISTAS LINEARES

Uma das formas mais comumente usadas para se manter dados agrupados a lista. Afinal, quem nunca
organizou uma lista de compras antes de ir ao mercado, ou ento uma lista dos amigos que participaro
da festa? As listas tm-se mostrado um recurso bastante til e eficiente no dia-a-dia das pessoas. Em
computao, no tem sido diferente: a lista uma das estruturas de dados mais empregadas no
desenvolvimento de programas.
Uma lista linear uma coleo L: [a1, a2, ... an ], n .>= 0, cuja propriedade estrutural baseia-se apenas
na posio relativa dos elementos, que so dispostos linearmente. Se n = 0 dizemos que a lista vazia;
caso contrrio, so vlidas as seguintes propriedades:
a1 o primeiro elemento de L;
an o ltimo elemento de L;
ak , 1 < k < n, precedido pelo elemento ak - 1 e seguido pelo elemento ak + 1 em L.
Em outras palavras, a caracterstica fundamental de uma lista linear o sentido de ordem
unidimensional dos elementos que a compem. Uma ordem que nos permite dizer com preciso onde a
coleo inicia-se e onde termina, sem possibilidade de dvida. Entre as diversas operaes que
podemos realizar sobre listas, temos:
acessar um elemento qualquer da lista;
inserir um elemento numa posio especfica da lista;
remover um elemento de uma posio especfica da lista;
combinar duas listas em uma nica;
particionar uma lista em duas;
obter cpias de uma lista;
determinar o total de elementos na lista;
ordenar os elementos na lista
procurar um determinado elemento na lista;
apagar uma lista;
outras ...
Considerando-se somente as operaes de acesso, insero e remoo, restritas aos extremos da lista,
temos casos especiais que aparecem muito freqentemente na modelagem de problemas a serem
resolvidos por computador. Tais casos especiais recebem tambm nomes especiais:
Pilha: lista linear onde todas as inseres, remoes e acessos so realizados em um nico
extremo. Listas com esta caracterstica so tambm denominadas lista LIFO (Last-In/Firs-Out)
ou em portugus: UEPS (ltimo que Entra/Primeiro que Sai).
Fila: lista linear onde todas as inseres so feitas num certo extremo e todas as remoes e
acessos so realizados no outro extremo. Filas so tambm denominadas listas FIFO (First-
In/First-Out) ou em portugus: PEPS (Primeiro que Entra/Primeiro que Sai).
Fila Dupla: lista linear onde as inseres, remoes ou acessos so realizados em qualquer
extremo. Filas Duplas so tambm denominadas DEQUE (Double-Ended QUEue), ou em
portugus: fila de extremidade dupla. Uma Fila Dupla pode ainda gerar dois casos especiais:
- Fila Dupla de Entrada Restrita (se a entrada for restrita a um nico extremo) e
- Fila Dupla de Sada Restrita (se a remoo for restrita a um nico extremo).
Ao desenvolver uma implementao para listas lineares, o primeiro problema que surge : como
podemos armazenar os elementos da lista, dentro do computador? Sabemos que antes de executar um
programa, o computador precisa carregar seu cdigo executvel para a memria. Da rea de memria
que reservada para o programa, uma parte usada para armazenar as instrues a serem executadas e
a outra destinada ao armazenamento dos dados. Quem determina quanto de memria ser usado para
4

as instrues o compilador. Alocar rea para armazenamento de dados entretanto, responsabilidade


do programador. A alocao de memria, do ponto de vista do programador, estar em uma das quatro
categorias apresentadas a seguir:
Seqencial Encadeada
Esttica Esttica Seqencial Esttica Encadeada
Dinmica Dinmica Seqencial Dinmica Encadeada

1.1 Alocao Esttica versus Dinmica


Dizemos que as variveis de um programa tm alocao esttica se a quantidade de memria utilizada
pelos dados previamente conhecida e definida de modo imutvel, no cdigo-fonte do programa;
durante a execuo, a quantidade de memria utilizada no varia. Por outro lado, se o programa
capaz de criar novas variveis enquanto executa, isto , se as reas de memria, que no foram
declaradas no programa, passam a existir durante a sua execuo, ento dizemos que a alocao
dinmica.
Considerando que uma varivel que uma rea de memria, dizemos que sua alocao esttica se
sua existncia j era prevista no cdigo do programa; de outra forma, dizemos que sua alocao
dinmica. A seguir, vemos o uso de varivel esttica em contraste com varivel dinmica:
program alocacao;
type ptr = ^integer;
var P: ptr; {aloca varivel estaticamente}
begin
new(P); {aloca varivel dinamicamente}
P^ := 12345;
writeln(P^);
end.

Observando a figura, com a memria no momento da execuo, verificamos que mais uma varivel foi
criada na posio de memria 1FFA. Esta uma varivel alocada dinamicamente (s vezes
denominada varivel annima, pois no conhecemos seu nome, temos apenas seu endereo!).
Os atributos seqencial e encadeada s tm sentido se formos armazenar uma coleo de objetos,
como o caso da memria necessria para armazenar uma lista linear ou um vetor.

1.2 Alocao Seqencial


A forma mais natural de armazenar uma lista consiste em colocar os seus elementos em clulas de
memria consecutivas. Assim, se cada clula tem um endereo nico E e utiliza k bytes, temos:

Endereo(ai ) = E ;
5

Endereo(ai -1) = E-k ;


Endereo(ai +1) = E+k .
De forma um pouco mais genrica, assumindo que o elemento a1 encontra-se na clula de endereo
B, temos a equao: Endereo(ai ) = B + (i-1)k.

A maior vantagem no uso de uma rea seqencial de memria para armazenar uma lista linear que,
dado o endereo inicial B da rea alocada e o ndice i de um elemento qualquer da lista, podemos
acess-lo imediatamente, com um simples e rpido clculo. O ponto fraco desta forma de
armazenamento aparece quando precisamos inserir ou suprimir elementos do meio da lista, quando
ento um certo esforo ser necessrio para movimentar os elementos, de modo a abrir espao para
insero, ou de modo a ocupar o espao liberado por um elemento que foi removido.

1.3 Alocao Encadeada


Ao invs de manter os elementos agrupados numa rea contnua de memria, isto , ocupando clulas
consecutivas, na alocao encadeada os elementos podem ocupar quaisquer clulas (no
necessariamente consecutivas) e, para manter a relao de ordem linear, juntamente com cada
elemento armazenado o endereo do prximo elemento da lista. Desta forma, na alocao encadeada,
os elementos so armazenados em blocos de memria denominados nodos, sendo que cada nodo
composto por dois campos: um para armazenar dados e outro para armazenar endereo. Dois
endereos especiais devem ser destacados (como visto na figura):
endereo do primeiro elemento da lista (L);
endereo do elemento fictcio que segue o ltimo elemento da lista (nil).
A alocao encadeada apresenta como maior vantagem a facilidade de inserir ou remover elementos
do meio da lista. Como os elementos no precisam estar armazenados em posies consecutivas de
memria, nenhum dado precisa ser movimentado, bastando atualizar o campo de ligao do elemento
que precede aquele inserido ou removido. Por exemplo, para remover o elemento a2 da lista
representada na figura, basta mudar o nodo no endereo 3FFA de (a1,1C34) para (a1,BD2F). Como
apenas o primeiro elemento acessvel diretamente atravs do endereo L, a grande desvantagem da
alocao encadeada surge quando desejamos acessar uma posio especfica dentro da lista. Neste
caso, devemos partir do primeiro elemento e ir seguindo os campos de ligao, um a um, at atingir a
posio desejada. Obviamente, para listas extensas, esta operao pode ter um alto custo em relao a
tempo.
6
7

2 PILHAS

A pilha uma das estrutura de dados mais teis em computao. Uma infinidade de problemas
clssicos da rea podem ser resolvidos com o uso delas. Uma pilha um tipo especial de lista linear
em que todas as operaes de insero e remoo so realizadas numa mesma extremidade,
denominada topo. Cada vez que um novo elemento deve ser inserido na pilha, ele colocado no seu
topo; e em qualquer momento, apenas aquele posicionado no topo da pilha pode ser removido.
Devido a esta disciplina de acesso, os elementos so sempre removidos numa ordem inversa quela
em que foram inseridos, de modo que o ltimo elemento que entra exatamente o primeiro que sai.
Da o fato destas listas serem tambm denominadas listas LIFO (Last-In/First-Out) ou em portugus:
UEPS (ltimo que Entra/Primeiro que Sai).
Uma pilha ou lista LIFO uma coleo que pode aumentar ou diminuir durante a sua existncia. O
nome pilha advm justamente da semelhana entre o modo como as listas LIFO crescem e diminuem e
o modo como as pilhas, no mundo real, funcionam. O exemplo mais comum do quotidiano uma pilha
de pratos, onde o ltimo prato colocado o primeiro a ser usado (removido da pilha).
Uma pilha suporta 3 operaes bsicas, tradicionalmente denominadas como:
Top (Visualizar o Topo): acessa o elemento posicionado no topo da pilha;
Push (Inserir): insere um novo elemento no topo da pilha;
Pop (Retirar): remove um elemento do topo da pilha.
Sendo P uma pilha e x um elemento qualquer, a operao Push ( P, x ) aumenta o tamanho da pilha P,
acrescentando o elemento x no seu topo. A operao Pop ( P ) faz com que a pilha diminua, removendo
e retornando o elemento existente em seu topo. A operao Top no altera o estado da pilha; apenas
retorna uma cpia do elemento existente no topo da pilha sem remov-lo.
Exerccio: Dadas as operaes sobre uma pilha P, preencha o quadro a seguir com o estado da pilha e
o resultado de cada operao:
8

Como os elementos da pilha so armazenados em seqncia, um sobre o outro, e a incluso ou


excluso de elementos no requer movimentao de dados, o esquema de alocao seqencial de
memria mostra-se bastante apropriado para implement-las. Neste esquema, a forma mais simples de
se represntar uma pilha na memria consiste em :
um vetor, que serve para armazenar os elementos contidos na pilha;
um ndice, utilizado para indicar a posio corrente de topo da pilha.
Note-se que vetor o mecanismo oferecido pelas linguagens de programao que nos premite
alocar, de forma esttica, uma rea seqencial de memria; o ndice o recurso necessrio para
disciplinar o acesso aos elementos da pilha.

2.1 Declarando e inicializando uma pilha


Para declarar uma pilha, precisamos ento de duas variveis: um vetor com o tamanho necessrio para
armazenar as informaes e uma varivel que indica o topo. Para inicializar uma pilha, deve-se
inicializar o topo:
Program Pilha;
const MAX = 10;
var
V: array[1..MAX] of integer;
topo: integer;
begin
topo := 0;

2.2 Verificando limites da pilha


Uma insero s pode ser feita na pilha se o topo for menor que o seu tamanho mximo, caso contrrio
ocorre o que se chama de estouro de pilha ou stack overflow. Da mesma forma, uma retirada s pode
ser feita se o topo for maior que zero, caso contrrio ocorre o que se chama de stack underflow.
Para verificar se uma pilha est cheia, basta testar se o topo igual ao seu tamanho mximo. O teste
para verificar se pode ser feita uma insero numa pilha que tem tamanho mximo igual a 10
mostrado no exemplo abaixo. Para verificar se uma pilha est vazia, basta testar se o topo inferior a
zero. O teste para verificar se pode ser feita uma retirada mostrado no exemplo abaixo:
if topo = MAX
then writeln(Pilha cheia!);
if topo = 0
then writeln(Pilha vazia!);

2.3 Inserindo e removendo elementos da pilha


Para inserir um elemento na pilha, precisamos saber qual o elemento a ser inserido e testar se a pilha
est cheia. Se no estiver cheia, incrementamos o topo e, nesta posio, inserimos o novo elemento.
Para remover um elemento na pilha, precisamos saber se a pilha no est vazia. Se a mesma no est
vazia, mostra-se o valor que est no vetor na posio apontada pelo topo e decrementa-se o topo. O
exemplo abaixo ilustra a insero e remoo de um elemento da pilha:
Program Pilha;
Uses Crt;
const MAX = 10;
Var
V: Array[1..MAX] of integer;
topo: integer;
9

x: integer;
Begin
topo := 0; // inicializao da pilha

{ --- procedimento para insero --- }


write(Valor a inserir: );
readln (x);
if topo = MAX
then writeln(Pilha cheia!);
else begin
topo := topo + 1;
V[topo] := x;
end;

{ --- procedimento para retirada --- }


if topo = 0
then writeln(Pilha vazia!);
else begin
writeln(O valor retirado : , V[topo]);
topo := topo - 1;
end;
end.

2.4 Verificando o elemento do topo da pilha


Para verificar o elemento que est no topo da pilha, verificamos se a pilha est vazia. Se no estiver
vazia, mostra-se o valor que est no vetor na posio apontada pelo topo sem decrementar o topo.
if topo = 0
then writeln(Pilha vazia!);
else writeln(O valor que est no topo : , V[topo]);
10

3 FILAS

Uma fila um tipo especial de lista linear em que as inseres so realizadas em um extremo e as
remoes so feitas no outro. Cada vez que uma operao de insero executada, um novo elemento
colocado no final da fila. Na remoo, retornado o elemento que aguarda h mais tempo na fila,
posicionado no comeo. A ordem de sada corresponde ordem de entrada dos elementos na fila, de
modo que os primeiros elementos que entram so sempre os primeiros a sair. Por este motivo, as filas
so denominadas listas FIFO (First-In/First-Out) ou PEPS (Primeiro que Entra/Primeiro que Sai).
Um exemplo bastante comum de filas verifica-se num balco de atendimento, onde pessoas formam
uma fila para aguardar at serem atendidas. Naturalmente, devemos desconsiderar os casos de pessoas
que .furam. a fila ou que desistem de aguardar! Diferentemente das filas no mundo real, o tipo abstrato
de dados no suporta insero nem remoo no meio da lista.

A palavra inglesa queue significa fila. As operaes bsicas que uma fila suporta so:
Enqueue (Inserir): insere um novo elemento no final da fila;
Dequeue (Retirar): remove um elemento do comeo da fila.
Sendo F uma fila e x um elemento qualquer, a operao Enqueue ( F, x ) aumenta o tamanho da fila
F , acrescentando o elemento x no seu final. A operao Dequeue ( F ) faz com que a fila diminua,
removendo e retornando o elemento existente em seu comeo.
Exerccio: Dadas as operaes sobre uma fila F, preencha o quadro a seguir com o estado da fila e o
resultado de cada operao:

Graficamente, representamos uma fila como uma coleo de objetos que cresce da esquerda para a
direita, com dois extremos bem definidos: comeo e final:
11

3.1 Declarando e inicializando uma fila


A partir da representao grfica podemos implementar uma fila tendo trs recursos bsicos:
espao de memria para armazenas os elementos (um vetor V);
uma referncia ao primeiro elemento da coleo (uma varivel comeo);
uma referncia primeira posio livre, aps o ltimo elemento da fila (uma varivel final).
Para inicializar a fila, deve-se atribuir o valor 1 s variveis comeo e final da fila:
Program Fila;
const MAX = 10;
Var
V: Array[1..10] of integer;
comeco, final : integer;
Begin
comeco := 1;
final := 1;

3.2 Verificando limites da fila


Uma insero s pode ser feita se a fila no estiver cheia (final menor que o seu tamanho mximo). Da
mesma forma, uma retirada s pode ser feita se existirem elementos na fila (o comeo for diferente do
final). Para verificar se a fila est cheia, basta testar se o final maior que o seu tamanho mximo; para
verificar se a fila est vazia, basta testar se o comeo igual ao final. Os testes para verificar se pode
ser feita uma insero numa fila que tem tamanho mximo igual a 10, bem como se pode ser feita uma
retirada, so mostrados no exemplo abaixo.
if final > MAX
then writeln(Fila cheia!);
if comeco = final
then writeln(Fila vazia!);

3.3 Inserindo e removendo elementos na fila


Para inserir um elemento na fila, precisamos saber qual o elemento a ser inserido e testar se a fila
est cheia. Se no estiver cheia, o elemento inserido na posio apontada pela varivel final, que
deve ser incrementada depois da insero. Para remover um elemento da fila, precisamos saber se a
fila no est vazia. Se no estiver vazia, mostra-se o valor que est no vetor na posio apontada pelo
comeo e incrementa-se o comeo. O exemplo abaixo ilustra a insero e remoo de um elemento.

{ --- procedimento para insero --- }


write(Valor a inserir: );
readln (x);
if final > MAX
then writeln(Fila cheia!);
else begin
V[final] := x;
final := final + 1
end;

{ --- procedimento para retirada --- }


if comeco = final
then writeln(Fila vazia!);
12

else begin
writeln(O valor retirado : , V[comeco]);
comeco := comeco + 1;
end;

3.4 Problemas na implementao seqencial de filas


Vimos que cada vez que um elemento removido, o ndice que aponta o comeo da fila desloca-se
uma posio direita (incrementado). Supondo que a fila tenha um tamanho mximo de 10, aps 10
inseres o ponteiro que aponta para o final da fila tem o valor 11 e a fila estar cheia, pois o final
maior que o tamanho mximo representa fila cheia, no podendo ser inserido mais nenhum elemento.
Aps 10 retiradas, o ponteiro que aponta para o comeo da fila ter o valor 11 e a fila estar vazia, pois
o comeo igual ao final representa fila vazia. Chegamos a uma situao extremamente indesejvel:
temos uma fila cheia e vazia ao mesmo tempo. Chegamos concluso de que esta nossa soluo no
muito eficiente, apresentando tanto desperdcio de memria quanto problemas de lgica.
Eliminar o erro lgico, que sinaliza fila vazia e cheia ao mesmo tempo, bastante simples. Basta
acrescentar uma varivel para contar quantos elementos esto na fila; quando um elemento for
inserido, ela ser incrementada; quando for removido, ser decrementada. Desta forma, o impasse
pode ser resolvido simplesmente consultando essa varivel.
Para eliminar o desperdcio de memria, o ideal seria que cada posio liberada por um elemento
removido se tornasse disponvel para receber um novo elemento inserido. Para isso teramos de dispor
de uma rea seqencial de memria tal que a posio 1 estivesse imediatamente aps a ltima posio.
Assim, a fila somente estaria cheia, quando realmente tivesse um elemento para cada posio.

3.5 Implementao circular para filas


A partir da representao grfica percebemos que possvel implementar uma fila circular tendo:
espao de memria para armazenas os elementos (um vetor V);
uma referncia ao primeiro elemento da coleo (uma varivel comeo);
uma referncia primeira posio livre, aps o ltimo elemento da fila (uma varivel final);
um indicador da quantidade de elementos da fila (uma varivel qtd).
O exemplo abaixo declara uma fila de tamanho igual a 10 e inicializa seus valores:
Program FilaCircular;
const MAX = 10;
Var
V: Array[1..MAX] of integer;
qtd, comeco, final : integer;
Begin
comeco := 1;
final := 1;
qtd := 0;
13

Para verificar se a fila est cheia, basta testar se a varivel contadora igual ao seu tamanho
mximo. Para verificar se a fila est vazia, basta testar se o comeo igual ao final. Estes testes so
mostrados no exemplo abaixo.
if qtd = MAX
then writeln(Fila cheia!);
if qtd = 0
then writeln(Fila vazia!);

3.6 Inserindo e removendo elementos da fila circular


Para inserir um elemento na fila, precisamos saber qual o elemento a ser inserido e testar se a fila
est cheia; se no estiver cheia, o elemento inserido na posio apontada pela varivel final, que deve
ser incrementada depois da insero. Para remover um elemento da fila, precisamos saber se a fila no
est vazia; se no estiver vazia, mostra-se o valor que est no vetor na posio apontada pelo comeco e
incrementa-se o comeo. O exemplo abaixo ilustra a insero e remoo de um elemento da fila.
Program FilaCircular;
const MAX = 10;
Var
V: Array[1..MAX] of integer;
qtd, comeco, final, x : integer;
Begin
comeco := 1;
final := 1;
qtd := 0; // inicializao da fila

{ --- procedimento para insero --- }


write(?Valor a inserir: ?);
readln (x);
if qtd = MAX
then writeln(Fila cheia!);
else begin
V[final] := x;
qtd := qtd + 1;
if final = MAX
then final := 1;
else final := final + 1;
end;

{ --- procedimento para retirada --- }


if qtd = 0
then writeln(Fila vazia!);
else begin
writeln(O valor retirado : , V[comeco]);
qtd := qtd - 1;
if comeco = MAX
then comeco := 1 ;
else comeco := comeco + 1;
end;
end.
14

4 CLASSIFICAO POR INSERO

Na classificao por insero, os n dados a serem ordenados so divididos em dois segmentos: um j


ordenado e outro a ser ordenado. Num momento inicial, o primeiro segmento formado por apenas um
elemento, portanto, j ordenado; o segundo segmento formado pelos restantes n-1 elementos. A partir
da, o processo se desenvolve em n-1 iteraes: em cada uma delas um elemento do segmento no
ordenado transferido para o primeiro segmento, inserido em sua posio correta em relao queles
que para l j foram transferidos. Os mtodos que pertencem a esta famlia diferem um dos outros
apenas pela forma como localizam a posio em que cada elemento deve ser inserido no segmento j
ordenado.

4.1 Mtodo da Insero Direta


Neste mtodo, considera-se o segmento j ordenado como sendo formado pelo primeiro elemento do
vetor de chaves. Os demais elementos, ou seja do 2 ao ltimo, pertencem ao segmento no ordenado.
A partir desta situao inicial, toma-se um a um dos elementos no ordenados, a partir do primeiro e,
por busca seqencial realizada da direita para a esquerda no segmento j ordenado, localiza-se a sua
posio relativa correta. A cada comparao realizada entre o elemento a ser inserido e os que l j se
encontram, podemos obter um dos seguintes resultados:
O elemento a ser inserido menor do que aquele com que se est comparando. Nesse caso, este
movido uma posio para a direita, deixando, assim, vaga a posio que anteriormente ocupava.
O elemento a ser inserido maior ou igual quele com que se est comparando. Nesse caso,
fazemos a insero do elemento na posio vaga, a qual corresponde sua posio relativa
correta no segmento j ordenado. Caso o elemento a ser inserido seja maior do que todos, a
insero corresponde a deix-lo na posio que j ocupava.
Aps a insero, a fronteira entre os dois segmentos deslocada uma posio para a direita: o
segmento ordenado ganha um elemento e o no ordenado perde um. O processo prossegue at que
todos os elementos do segundo segmento tenham sido inseridos no primeiro.
4.1.1 Exemplo e implementao de Insero Direta
vetor original 18 15 7 9 23 16 14
diviso inicial 18 15 7 9 23 16 14 no ordenado
ordenado
primeira iterao 15 18 7 9 23 16 14
segunda iterao 7 15 18 9 23 16 14
terceira iterao 7 9 15 18 23 16 14
quarta iterao 7 9 15 18 23 16 14
quinta iterao 7 9 15 16 18 23 14
sexta iterao 7 9 14 15 16 18 23 vetor ordenado

{ --- declarao do vetor, anterior --- }


const MAX = 10;
Var V: Array[1..MAX] of integer;

{ --- declarao da procedure de insero --- }


procedure InsercaoDireta;
Var aux, i, j: integer;
Begin
for j := 2 to MAX
do begin
15

aux := V[j];
i := j - 1;
while ((i > 0) and (V[i] > aux))
do begin
V[i+1] := V[i];
i := i 1;
end;
V[i+1] := aux;
end;
end;

A implementao acima demonstra a descrio formulada: o segmento j ordenado percorrido da


direita para a esquerda, at que seja encontrada uma chave menor ou igual quela que est sendo
inserida, ou at que o segmento termine . Enquanto nenhuma dessas condies ocorrer, as chaves
comparadas vo sendo deslocadas uma posio para a direita. Na hiptese da chave a ser inserida ser
maior do que todas as do segmento ordenado, ela permanece no seu local original, caso contrrio,
inserida na posio deixada vaga pelos deslocamentos, avanando-se, a seguir, a fronteira entre os dois
segmentos . O processo todo completado em n-1 iteraes.

4.1.2 Anlise do Desempenho


O desempenho do mtodo da insero direta fortemente influenciado pela ordem inicial das chaves a
serem ordenadas. Isto se deve principalmente ao fato de que as chaves que j esto no vetor ordenado,
e que so maiores do que a que est sendo inserida, devem ser transpostas uma posio para a direita,
para dar lugar quela que vai ser inserida. Observe que, mesmo se usssemos um mtodo de busca
mais eficiente do que a linear, no evitaramos a operao de transposio.
A situao mais desfavorvel para o mtodo aquela em que o vetor a ser ordenado se apresenta na
ordem contrria desejada. Isto significa que cada elemento a ser inserido sempre ser menor do que
todos os que j esto no segmento ordenado. Isto acarreta um deslocamento de todos eles uma posio
para a direita. A tabela a seguir mostra a quantidade de comparaes e transposies necessrias para
inserir cada uma das chaves:
1 chave: 1
2 chave: 2
3 chave: 3
... chave: ...
(n-1)a chave: n-1

O total corresponder soma do nmero de operaes efetuadas em cada iterao: 1+2+3+...+(n.1),


igual soma dos termos de uma progresso aritmtica cujo primeiro termo 1 e o ltimo (n -1):
S = ( 1 + (n+1) ) / 2 * (n - 1) = (n - n) / 2.
Por outro lado, o melhor caso para o mtodo aquele no qual as chaves j se apresentam
previamente ordenadas. Nesta situao, cada chave a ser inserida sempre ser maior do que aquelas
que j o foram. Isto significa que nenhuma transposio necessria. De qualquer maneira,
necessria pelo menos uma comparao para localizar a posio da chave. Logo, nesta situao, o
mtodo efetuar um total de n-1 iteraes para dar o vetor como ordenado.
Podemos supor que em situaes normais o vetor no se apresente j ordenado, nem tampouco com
a ordenao inversa quela desejada. razovel imaginar uma situao intermediria entre os casos
extremos, supondo que esta corresponda mdia aritmtica entre os dois casos. Assim, o desempenho
mdio do mtodo dado por:
( (n - 1) + ( (n - n) / 2 ) ) / 2 = (n + n - 2) / 4
16

Podemos tambm usar uma outra abordagem para estimar a complexidade do mtodo.
Examinaremos o seu desempenho para os casos de haver nenhuma, uma, duas, trs, etc... chaves fora
do seu local definitivo. Depois, generalizaremos para um certo valor mdio de chaves fora do lugar.
Para cada caso, consideramos dois tipos de comparaes: as necessrias para descobrir a posio
correta das chaves que esto fora do seu lugar, e as necessrias para descobrir que as demais chaves j
esto no seu local correto. Iremos supor que as chaves fora do local esto a uma distncia mdia de n/2
posies, j que a distncia mnima 1 e a mxima n-1.
A quantidade de chaves fora do local poder variar de nenhuma at n-1, j que, mesmo que o vetor
se apresente na ordem inversa quela desejada, podemos considerar que pelo menos uma chave se
encontra na sua posio correta, enquanto as n-1 demais se encontram deslocadas.
A tabela a seguir mostra a quantidade de comparaes necessrias em cada caso. A coluna A indica a
quantidade de chaves fora do seu local. A coluna B mostra o nmero de comparaes efetuadas para
descobrir o local correto de cada chave que est fora do seu lugar. J a coluna C exibe o nmero de
comparaes para descobrir que as demais chaves esto no seu local correto.
A B C
0 0 n-1
1 n/2 n-2
2 2 * n/2 n-3
3 3 * n/2 n-4
... ... ...
k=n-1 k * n/2 n (k-1)

O total de comparaes para um certo 0 <= i <= k , pois, a soma das comparaes indicadas nas
colunas B e C, ou seja: (i * n/2) + (n - (i+1)). De fato, para i = 0 e para i = k, temos, respectivamente,
(n 1) e ((n - n) / 2) comparaes, as quais correspondem ao melhor e ao pior caso do mtodo,
tambm respectivamente, na anlise efetuada anteriormente. Considerando, como antes, que o caso
mdio corresponde mdia aritmtica entre o melhor e o pior caso, temos:
nmero mdio de chaves fora do local = ( 0 + (n - 1)) / 2 = (n - 1) / 2
nmero mdio de comparaes = (n-1)/2 * (n/2) + (n- ((n-1)/2-1)) = (n + n-2)/4 o que
corresponde exatamente ao resultado encontrado na anlise anterior.

4.2 Insero Direta com Busca Binria


Como mencionamos no incio desta seo, o uso de um mtodo mais eficiente do que o da busca
seqencial pode ser usado para localizar a posio na qual uma certa chave deve ser inserida no
segmento ordenado. Isto no nos livra, no entanto, da necessidade de efetuar os deslocamentos das
chaves que so maiores do que aquela que est sendo inserida, para lhe dar lugar.
Eventualmente poderamos pensar em organizar o segmento ordenado sob a forma de uma lista
encadeada, de tal forma a evitar a necessidade de deslocamentos. A insero seria feita apenas com
ajustes de ponteiros. Mas, por outro lado isto nos obrigaria a uma pesquisa seqencial na lista para
localizar o ponto de insero. Isto nos deixa num impasse. Assim, mesmo melhorando o tempo de
localizao do ponto de insero, ficamos ainda com o nus dos elevados tempos de deslocamento.
Supondo, no entanto, que mesmo assim optemos por esta soluo, vejamos ento qual o seu
comportamento. Sabemos que o nmero de comparaes necessrias para localizar a posio que um
elemento deve ocupar em uma tabela que contm k smbolos, por pesquisa binria, aproximadamente
17

log2 k. No nosso caso, esta operao ser repetida para valores de k variando de 1 at n-1. Restam as
operaes de deslocamento que, como j sabemos, so realizadas em quantidades proporcionais ao
quadrado do nmero de chaves. Sugerimos, como exerccio, que demonstre que o nmero mdio de
deslocamentos necessrios para inserir as n-1 chaves (n - n)/4.
Vemos, assim, que a alternativa de usar a pesquisa binria para rapidamente encontrar a posio de
insero no produz grandes ganhos no desempenho do mtodo, pois aps a localizao, ainda
necessrio promover os deslocamentos das chaves direita da posio de insero, que uma
operao de desempenho O(n). Caso, no entanto, possamos dispor de uma arquitetura que execute
deslocamentos em blocos, poderemos obter vantagens no tempo despendido com a operao.
Deixamos ainda a sugesto de implementar o mtodo de classificao por insero com busca
binria, comparando-o com o de busca seqencial.

4.3 Mtodo dos Incrementos Decrescentes - Shellsort


Este mtodo, proposto em 1959 por Donald L. Shell, explora o fato de que o mtodo da insero direta
apresenta desempenho aceitvel quando o nmero de chaves pequeno ou j existe uma ordenao
parcial. De fato, como o desempenho do mtodo quadrtico, se dividirmos o vetor em h segmentos,
de tal forma que cada um possua aproximadamente n/h chaves e classificarmos cada segmento
separadamente, teremos, para cada um deles, um desempenho em torno de (n/h)2. Para classificar todos
os h segmentos, o desempenho ser proporcional a h * (n/h)2 = n2/h. Alm disso, teremos rearranjado
as chaves no vetor, de modo que elas se aproximem mais do arranjo final, isto , teremos feito uma
ordenao parcial do vetor, o que contribui para a diminuio da complexidade do mtodo.
Para implementar o mtodo, o vetor C[1..n] dividido em segmentos, assim formados:
segmento 1: C[1], C[h+1], C[2h+1], ...
segmento 2: C[2], C[h+2], C[2h+2], ...
segmento 3: C[3], C[h+3], C[2h+3], ...
.....
segmento h: C[h], C[h+h], C[2h+h], ...
onde h um incremento.
No exemplo consideraremos incrementos iguais a potncias inteiras de 2. Num primeiro passo, para
um certo h inicial, os segmentos assim formados so ento classificados por insero direta. Num
segundo passo, o incremento h diminudo (a metade do valor anterior, se este era uma potncia
inteira de 2), dando origem a novos segmentos, os quais tambm sero classificados por insero
direta. O processo se repete at que h=1. Quando for feita uma classificao com h=1, o vetor estar
classificado. Observe que quando h=1, o mtodo se confunde com o da insero direta.
A vantagem reside no fato de que o mtodo shell, em cada passo, faz classificaes parciais do vetor,
o que favorece o desempenho dos passos seguintes, uma vez que a insero direta acelerada quando
o vetor j se encontra parcialmente ordenado (veja mais, em relao a desempenho, em Mtodo de
Insero Direta).
4.3.1 Exemplo Incrementos Decrescentes - Shellsort
Conveno: os nmeros acima de cada clula indicam os ndices das chaves no vetor. Os nmeros
abaixo de cada clula indicam o nmero do segmento ao qual cada clula pertence.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
17 25 49 12 18 23 45 38 53 42 27 13 11 28 10 14
1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4
Primeiro passo: h = 4
Aps classificar cada um dos quatro segmentos, o vetor ter o aspecto a seguir.
18
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16
11 23 10 12 17 25 27 13 18 28 45 14 53 42 49 38
1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2
Segundo passo: h = 2
Aps classificar cada um dos dois segmentos, o vetor ter o aspecto seguinte:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
10 12 11 13 17 14 18 23 27 25 45 28 49 38 53 42
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Terceiro (e ltimo) passo: h = 1

Aps a execuo do ltimo passo, o vetor resultar classificado:


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
10 11 13 13 14 17 18 23 25 27 28 38 42 45 49 53

4.3.2 Implementao
A implementao do mtodo dos incrementos decrescentes uma adaptao da implementao do
mtodo de insero direta. Ao invs de classificar um vetor cujos elementos se encontram contguos
uns aos outros, classifica apenas os elementos do vetor que pertencem a um certo segmento: os
elementos que iniciam numa dada clula f e esto espaados entre si a uma distncia h.
{ --- declarao do vetor, anterior --- }
const MAX = 10;
Var V: Array[1..MAX] of integer;

{ --- declarao da procedure de insero --- }


procedure shellSort;
const arrSaltos: array[1..5] of integer = (9,5,3,2,1);
Var salto, aux, i, j, k: integer;
Begin
for k := 1 to 5
do begin
salto := arrSaltos[k];
j := 1 + salto;
while j <= MAX
do begin
aux := V[j];
i := j salto;
while ((i > 0) and (V[i] > aux))
do begin
V[i+salto] := V[i];
i := i salto;
end;
V[i+salto] := aux;
j := j + salto;
end;
end;
end;

4.3.3 Anlise do Desempenho


Segundo Knuth e Wirth, a anlise do desempenho do mtodo muito complexa, pois enfrenta alguns
problemas matemticos bastante difceis, alguns deles ainda no resolvidos. Um dos problemas
determinar o efeito que a ordenao dos segmentos em um passo produz nos passos subseqentes.
Tambm no se conhece exatamente a seqncia de incrementos que produz o melhor resultado. A este
19

respeito, observa-se que os valores dos incrementos no podem ser mltiplos uns dos outros. Isto evita
que se combine, em uma iterao, dois segmentos entre os quais no havia nenhuma iterao prvia.
Esta iterao desejvel e deve ser a maior possvel, de acordo com o teorema a seguir:
Se a uma seqncia, previamente ordenada, de distncia k, for em seguida aplicada uma ordenao
de distncia i, ento essa seqncia permanece ordenada de distncia k. - D.Knuth
O autor do teorema mostra, por meio de simulaes, que os melhores resultados obtidos foram com
as seguintes seqncias de incrementos:
2k +1,...,9,5,3,1; k = ...3,2,1
2k - l,...,15,7,3,l; k = ...3,2,1
(2k - (-1) k) / 3,...,11,5,3,1; k = ...,3,2
(3k - 1) / 2, ...,40,13,4,1; k = ...,2,1
Observao: na primeira srie o valor 1 foi inserido. Quanto ao valor inicial do incremento,
recomendado que seja aquele valor que divida o vetor de chaves no maior nmero possvel de
segmentos de mais de um elemento.
20

5 CLASSIFICAO POR TROCAS

Os mtodos de classificao por trocas caracterizam-se por efetuarem a classificao por comparao
entre pares de chaves, trocando-as de posio caso estejam fora de ordem no par.

5.1 Mtodo da Bolha - Bubblesort


Neste mtodo, o princpio geral aplicado a todos os pares consecutivos de chaves. Este processo
executado enquanto houver pares consecutivos de chaves no ordenados. Quando no restarem mais
pares no ordenados, o vetor estar classificado.
Suponha que se deseje classificar em ordem crescente o seguinte vetor de chaves: 28 26 30 24 25.
Comparamos todos os pares de chaves consecutivas, a partir do par mais esquerda. Caso as chaves de
um certo par se encontrem fora da ordem desejada, efetuamos a troca das mesmas. O processo de
comparao dos n-1 pares de chaves denominado de varredura. O mtodo efetuar tantas varreduras
no vetor quantas forem necessrias para que todos os pares consecutivos de chaves se apresentem na
ordem desejada.
Primeira varredura:
28 26 30 24 25 compara par (28,26): troca.
26 28 30 24 25 compara par (28,30): no troca.
26 28 30 24 25 compara par (30,24): troca.
26 25 24 30 25 compara par (30,25): troca.
26 28 24 25 30 fim da primeira varredura.
Como ainda existem pares no ordenados, reiniciamos o processo de comparaes de pares de
chaves, executando mais uma varredura. Observe, no entanto, que a primeira varredura sempre ir
posicionar a chave de maior valor na sua posio definitiva correta (no final do vetor). Isto significa
que na segunda varredura podemos desconsiderar a ltima posio do vetor, que portanto fica reduzido
de um elemento. Esta situao se repetir ao final de cada varredura efetuada.
Segunda varredura:
26 28 24 25 30 compara par (26,28): no troca.
26 28 24 25 30 compara par (28,24): troca.
26 24 28 25 30 compara par (28,25): troca.
26 24 25 28 30 fim da segunda varredura.
Terceira varredura:
26 24 25 28 30 compara par (26,24): troca.
24 26 25 28 30 compara par (26,25): troca.
24 25 26 28 30 fim da terceira varredura.
Como no restam mais pares no ordenados, o processo de classificao dado por concludo.
Examinando-se o comportamento do mtodo, vemos que, dependendo da disposio das chaves,
uma certa varredura pode colocar mais do que uma chave na sua posio definitiva. Isto ocorrer
sempre que houver blocos de chaves j previamente ordenadas, como no exemplo abaixo.
Vetor de chaves: 13 11 25 10 18 21 23
A primeira varredura produzir o seguinte resultado: 11 13 10 18 21 23 25
Observe que, neste caso, as quatro ltimas chaves j se encontram na sua posio definitiva. Isto
significa que a prxima varredura no precisa ignorar apenas a ltima chave. As quatro ltimas podem
ser ignoradas. A quantidade de chaves, a partir da ltima, que pode ser ignorada de uma varredura para
outra conhecida pela posio na qual ocorreu a ltima troca. A partir daquele ponto o vetor j se
encontra ordenado.
21

A denominao deste mtodo resulta da associao das chaves com bolhas dentro de um fluido.
Cada bolha teria um dimetro proporcional ao valor de uma chave. Assim, as bolhas maiores subiriam
mai depressa, o que faria com que, aps um certo tempo, elas se arranjassem em ordem de tamanho.
5.1.1 Implementao
A implementao do bubblesort bastante simples. O programa controlado por um comando while,
executado enquanto ocorrerem trocas durante uma varredura, condio esta indicada pela varivel
booleana troca: seu valor ser verdadeiro caso haja trocas durante uma varredura, e falso caso
contrrio. A varivel k indica a posio onde ocorreu a ltima troca. Assim, a varredura seguinte, se
necessria, ser feita somente at k.
A implementao apresentada abaixo para o mesmo vetor V (MAX = 10) no leva em considerao
estes dois pontos citados: (1) parar a iterao se no houve troca e (2) s fazer as comparaes at o
ltimo item trocado. Faa estas alteraes no mtodo como exerccio. Coloque tambm dois
contadores: um para a quantidade de iteraes e outro para a de trocas.
procedure bubbleSort;
Var aux, i, j: integer;
Begin
for i := (MAX 1) downto 1
do begin
for j := 1 to i
do begin
if V[j] > V[j+1]
then begin
aux := V[j];
V[j] := V[j + 1];
V[j + 1] := aux;
end;
end;
end;
end;

5.1.2 Anlise do Desempenho


Consideremos duas situaes extremas para o mtodo: aquela que mais lhe favorece, e a que lhe mais
prejudicial, em termos de quantidade de operaes efetuadas para ordenar um vetor de chaves.
Pelo exame de como o mtodo procede, percebe-se que o caso mais favorvel aquele no qual as
chaves j se encontram na ordem desejada. Nesses casos, ao final da primeira varredura, o mtodo j
ter descoberto que nenhuma troca foi efetuada, e que, portanto, o vetor j est ordenado. Esta primeira
e nica varredura demandar, pois, n- 1 comparaes.
J o caso mais desfavorvel aquele no qual as chaves se encontram na ordem inversa quela
desejada. Nesses casos, a cada varredura, apenas uma chave ser depositada no seu local definitivo,
enquanto as demais apenas se deslocaro uma posio para a esquerda (supondo ordenao crescente).
Desse modo, as quantidades de comparaes que sero efetuadas a cada varredura so as seguintes:
Nmero da Comparaes
1 n -1
2 n -2
3 n -3
... ...
n-1 1

O total de comparaes ser, pois, a soma da progresso aritmtica cujo primeiro termo n - 1 e o
ltimo 1, com n - 1 termos, ou seja, ((n-1+1)/2 ) x (n-1) = (n2 - n)/2.
22

Podemos supor que, na prtica, os casos que ocorram correspondam a situaes intermedirias entre
os extremos acima. Assim, se tomarmos a mdia aritmtica dos valores encontrados, provavelmente
estaremos fazendo uma boa estimativa do nmero mdio de comparaes que sero efetuadas.
Cmdio = (Cpior + Cmelhor) /2 = ((n - n)/2)+ (n-1))/2 = (n + n-2)/4
Na verdade, o nmero mdio de comparaes provavelmente ser pouco menor do que isso, pois no
estamos considerando, nesta anlise, os possveis blocos de chaves j ordenadas. De qualquer maneira,
sendo n2 a parcela dominante, o mtodo de complexidade quadrtica: O(n2).

5.2 Mtodo da Agitao - Shakesort


Ao efetuarmos a anlise do desempenho do mtodo bubblesort, verificamos que a cada varredura
efetuada, a maior das chaves consideradas levada at a sua posio definitiva, enquanto que as
demais se aproximam da sua posio correta de apenas uma casa. Isto sugere um aperfeioamento no
mtodo, no qual, ao final de uma varredura da esquerda para a direita, seja efetuada outra, da direita
para a esquerda, de tal modo que, ao final desta, a menor chave tambm se desloque para a sua posio
definitiva. Estas varreduras em direes opostas se alternariam sucessivamente, at a ordenao
completa do vetor. O mtodo resultante desta alterao do bubblesort recebeu a denominao de
shakesort, j que o seu funcionamento lembra os movimentos de vaivm de uma coqueteleira. A
implementao deste mtodo um bom exerccio.
Quanto ao seu desempenho, a melhoria obtida reside apenas na quantidade de comparaes
efetuadas, uma vez que o mtodo evita comparaes suprfluas que o bubblesort efetua. J o nmero
de trocas necessrias no se altera em relao ao bubblesort. Como esta uma operao mais onerosa
do que aquela, o ganho, em tempo, no ser significativo.

5.3 Mtodo do Pente - Combsort


Um ganho significativo no bubblesort pode ser obtido usando a estratgia de promover as chaves em
direo s suas posies definitivas por saltos maiores do que uma casa de cada vez. Essa alternativa,
proposta por Lacey e Box, consiste em comparar no os pares consecutivos de chaves, mas pares
formados por chaves que distam umas das outras uma distncia h. Na primeira varredura essa distncia
dada pelo valor h = n div 1,3. Nas varreduras subseqentes, esta distncia (ou salto) novamente
dividida por 1,3, at que seja igual a 1. Neste momento, o mtodo se confunde com o bubblesort.
Cada varredura conduz as chaves para locais prximos do definitivo por meio de grandes saltos.
medida que os saltos vo diminuindo de comprimento, aproximando-se da unidade, as chaves estaro
mais prximas de suas posies corretas, quando ento o bubblesort se tornar eficiente.
O fator 1,3 de reduo dos saltos foi obtido pelos autores do algoritmo por simulao. Foram
testados fatores que variavam de 1,1 a 1,45, sendo que o de valor 1,3 foi o que apresentou melhores
resultados, em mdia. O estudo da seqncia de saltos, com fator de reduo 1,3 revela que as
possveis seqncias de valores de h s podem terminar de uma das seguintes formas:
9 6 4 3 2 1
10 7 5 3 2 1
11 8 6 4 3 2 1
O resultado das simulaes revelou, tambm, que o terceiro caso aquele que produz os melhores
resultados. Assim, quando h>11 e o clculo do valor do salto seguinte resultar em 9 ou 10, ele
forado a tomar o valor 11, para que a srie conclua na sua forma mais favorvel ao mtodo. A reduo
do tempo de classificao desse mtodo em relao ao bubblesort tradicional (sem qualquer tipo de
otimizao) foi da ordem de 27 vezes.
23

As sucessivas redues dos saltos so anlogas ao ato de pentear cabelos longos e embaraados,
inicialmente apenas com os dedos e depois usando pentes com espaos entre os dentes cada vez
menores. Da a denominao do mtodo dada pelos autores.
Suponhamos, para exemplo, o mesmo vetor de chaves usado para exemplificar o mtodo bubblesort.
Como o vetor possui 5 chaves, o salto inicial igual a 3 (= 5/1.3). Como vemos, a classificao do
vetor, embora tambm tenha consumido 9 iteraes, demandou apenas trs trocas, enquanto o
bubblesort, para ordenar o mesmo vetor, efetuou 8 trocas. justamente neste ponto que reside a
vantagem do combsort em relao ao bubblesort, uma vez que a operao de troca, por envolver vrios
acessos memria, a que vai determinar a velocidade de classificao.

Observe na implementao que quando h=1 o mtodo se confunde com o bubblesort.

procedure calcSalto(var h: integer);


Begin
h := h / 1.3;
if ((h = 9) or (h = 10) or (h = 11))
then h := 11;
end;

procedure combSort;
Var aux, i, salto: integer;
Begin
salto := MAX;
while (salto >= 1)
do begin
calcSalto(salto);
for i := 1 to (MAX salto)
do begin
if (i + salto) <= MAX)
then begin
if V[i] > V[i + salto]
then begin
aux := V[i];
V[i] := V[i + salto];
V[i + salto] := aux;
end;
end
else break;
end;
end;
end;

5.4 Mtodo de Partio e Troca - Quicksort


24

O mtodo apresentado a seguir foi proposto por Hoare. Seu desempenho to melhor, que seu inventor
denominou-o quicksort (ordenao rpida). De fato o que apresenta, em mdia, o menor tempo de
classificao. Isto porque, embora tenha um desempenho logartmico como muitos outros, o que
apresenta menor nmero de operaes por iterao. Isto significa que, mesmo que tenha que efetuar
uma quantidade de iteraes proporcional a N(log2 N), cada uma delas ser mais rpida.
Seja o vetor de chaves C[1..n] a ser ordenado. Numa etapa inicial, esse vetor particionado em trs
segmentos S1, S2, S3, da seguinte forma:
S2 ter comprimento 1 e conter uma chave denominada particionadora;
S1 ter comprimento >=0 e conter todas as chaves cujos valores forem menores ou iguais ao da
chave particionadora. Esse segmento posicionado esquerda de S2;
S3 tambm ter comprimento >=0 e conter todas as chaves cujos valores forem maiores do que
o da particionadora. Esse segmento posicionado direita de S2.
Esquematicamente esse particionamento mostrado nas figuras a seguir. vetor inicial: 1

Observe que, pelo fato de:


C[i] <= C[k] para i=1,...,k-1 e
C[i] > C[k] para i = k+1,...,n,
a chave particionadora C[kl ocupa a sua posio correta na ordenao. O processo de particionamento
reaplicado aos segmentos S1 e S3 e a todos os segmentos correspondentes da resultantes, que
tiverem comprimento >=1. Quando no restarem segmentos a particionar, o vetor estar ordenado.
Como vemos, o mtodo consiste na aplicao sucessiva do particionamento de segmentos, que a
operao bsica do mtodo. Assim sendo, esta operao deve ser implementada de forma simples e
eficiente. Um dos pontos principais da operao o da escolha da chave particionadora, j que o seu
valor determinar o tamanho dos segmentos S1 e S3. A chave particionadora ideal aquela que produz
segmentos S1 e de igual tamanho (ou aproximado). Isto pode ser obtido se o seu valor for igual ao da
chave de valor mediano. Entretanto, para encontrarmos tal chave, seria necessrio percorrer todo o
vetor a ser particionado. Este procedimento tornaria a operao lenta, pois deve ser aplicado a todos os
segmentos de comprimento maior ou igual a um e que se formam aps cada particionamento.
Para evitar isto, devemos usar um critrio de escolha simples e rpido, de preferncia que no
envolva uma pesquisa entre as chaves. Se no tivermos nenhum conhecimento prvio sobre a
distribuio dos valores das chaves, podemos supor que qualquer uma delas pode ser a particionadora;
assim, com a finalidade de simplificar o algoritmo de particionamento, arbitraremos que a chave que
se encontra na posio inicial do vetor a ser particionado ser a particionadora. Caso tenhamos um
conhecimento prvio sobre a distribuio dos valores das chaves, podemos usar um outro critrio de
escolha. Por exemplo, se soubermos que o vetor j se encontra parcialmente ordenado, podemos eleger
como particionadora aquela chave que se encontra na posio central do vetor.

5.4.1 Exemplo do Mtodo


O exemplo a seguir ilustra o processo de particionamento, usando a chave inicial do vetor como
particionadora. O algoritmo executa o particionamento em n passos (n = nmero de chaves). Nos n-1
primeiros, as chaves (excluda a particionadora) so deslocadas para o lado esquerdo do vetor, se
menores ou iguais particionadora, ou para o lado direito, se maiores. No ltimo passo a chave
particionadora inserida na sua posio definitiva correta.
25

Suponhamos o vetor: 9 25 10 18 5 7 15 3
Escolhemos a chave 9 como particionadora e a guardamos em uma varivel temporria cp. A
posio ocupada por ela se torna disponvel para ser ocupada por outra chave, situao indicada por x.
Marcamos tambm o incio e o fim do vetor por dois apontadores: i (de incio) e f (de fim). A
expresso esquerda escrita ao lado do vetor indica que a posio apontada pelo ponteiro i (o da
esquerda) est disponvel e pode ser ocupada por outra chave. Nos passos seguintes, a expresso
direita indica o contrrio.
i f

1. x 25 10 18 5 7 15 3 esquerda cp=9

A seguir, comparamos a chave que est apontada por f com a particionadora. Como aquela menor
do que esta, a deslocamos para o lado esquerdo do vetor (que o lado onde ficam as chaves menores
ou iguais particionadora), ao mesmo tempo em que avanamos o ponteiro i para indicar que a chave
recm-movida j se encontra no segmento correto. A nova posio vaga passa a ser a apontada por f:
i f

2. 3 25 10 18 5 7 15 x direita cp=9

Agora comparamos a chave 25 (pois a nova posio vaga a da direita) com a particionadora. Como
25 > 9, deslocamos 25 para x, e recuamos o ponteiro uma posio para a esquerda, indicando assim
que a chave 25 j se encontra no seu segmento correto. Agora a posio vaga a da esquerda:
i f

3. 3 x 10 18 5 7 15 25 esquerda cp=9

O processo prossegue comparando a chave 15. Neste caso, por ela ser maior do que a particionadora,
no deve ser trocada de posio, pois j se encontra no segmento correto. Apenas o ponteiro f
deslocado para a esquerda. A posio vaga permanece sendo a da esquerda:
i f

4. 3 x 10 18 5 7 15 25 esquerda cp=9

No passo seguinte a chave 7 colocada na posio vaga, e o ponteiro i ajustado:


i f

5. 3 7 10 18 5 x 15 25 direita cp=9

Os passos seguintes so feitos de forma similar e so mostrados a seguir:


i f

6. 3 7 x 18 5 10 15 25 esquerda cp=9

i f
26

7. 3 7 5 18 x 10 15 25 direita cp=9

O resultado do 7 passo (correspondente ao n-1) produz a situao na qual os dois ponteiros i e f se


encontram. Quando isto ocorre, os segmentos S1 e S3 esto formados. A posio vaga entre eles
corresponde ao segmento S2, que contm a chave particionadora cp. Assim, resta copiar cp para a
posio apontada por i e j, que este passo estar completo. Observe que, embora os segmentos S1 e S3
ainda no estejam ordenados, a chave particionadora j se encontra na sua posio correta.
i, f

8. 3 7 5 x=9 18 10 15 25 cp=9

O processo de classificao prossegue com o particionamento dos segmentos S1 e S3 e todos os


demais segmentos correspondentes de tamanho maior ou igual a um que forem se formando. A
seqncia a seguir exibe apenas o estado final de cada processo de particionamento. As chaves
particionadoras so mostradas em tipo negrito, enquanto os segmentos de apenas um elemento que se
formarem so mostrados em tipo itlico.
3 7 5 9 1 1 18 2
5 0 5
3 5 7 9 10 15 18 25
O que encerra a ordenao do vetor.

5.4.2 Implementao
A implementao do mtodo exigir, portanto, a definio de dois procedimentos: um para executar
uma ordenao (a que mostramos no exemplo) e outro para controlar os particionamentos. O primeiro
procedimento, denominado ordenao a seguir apresentado.

procedure Ordenacao(var inicio: inicio; fim: integer);


Var cp: integer;
esquerda: boolean
Begin
esquerda := true;
cp := V[inicio];
while (inicio < fim)
do begin
if (esquerda)
then begin
if (cp >= V[fim]) // troca para S1 (esquerda)
then begin
V[inicio] := V[fim];
V[fim] := 0;
inicio := inicio + 1;
esquerda := false;
end
else fim := fim - 1;
end
else begin
if (cp < V[inicio]) // troca para S3 (direita)
then begin
V[fim] := V[inicio];
V[inicio] := 0;
fim:= fim - 1;
esquerda := true;
27

end
else inicio := inicio + 1;
end;
end;
V[inicio] := cp; // retorna a varivel inicio
end;

Uma alternativa de implementao da operao de particionamento, adequada quando a chave


particionadora escolhida ocupar uma posio intermediria do vetor a ser particionado e possuir um
valor mediano, foi proposta por N. Wirth da seguinte forma: uma vez escolhida a chave particionadora
(denominada cp), percorre-se o vetor a ser particionado da esquerda para a direita, at que seja
encontrada uma chave c[i] > cp. A seguir percorre-se o vetor da direita para a esquerda, at que seja
encontrada uma chave c[j] <= cp. Nesta ocasio as duas chaves c[i] e c[j] so permutadas de posio.
As varreduras prosseguem a partir destes pontos, at que os dois deslocamentos se encontrem em uma
posio intermediria do segmento. esquerda ficaro as chaves menores ou iguais a cp; direita as
maiores. Observe que este processo divide o segmento em dois outros, j que a particionadora usada
apenas como referncia e ir estar sempre contida no segmento da esquerda.
Falta mostrar a implementao do procedimento que ir controlar os particionamentos necessrios
para ordenar o vetor. Essa operao (particionamento sucessivo) recursiva, uma vez que, obtido o
primeiro particionamento, a operao reaplicada sobre os segmentos resultantes, at que todas as
parties fiquem reduzidas a um nico elemento. Assim, o procedimento que implementa a operao
refletir esta natureza recursiva, sendo recursivo tambm:
procedure quickSort(comeco, final: integer);
Var divisor: integer;
Begin
if (final > comeco)
then begin
ordenacao(comeco, final); // devolve comeco
divisor := comeco;
quickSort(comeco, divisor 1); // ordena segmento da direita
quickSort(divisor + 1, final); // ordena segmento da esquerda
end;
end;

Em linguagens de programao que no suportam a recursividade, a implementao do


procedimento quicksort deve manipular explicitamente a pilha de pedidos pendentes de
particionamento. Deixaremos a implementao dessa alternativa como exerccio.

5.4.3 Anlise do Desempenho


A anlise feita a seguir baseada na primeiro implementao da operao de particionamento descrita.
Esta implementao, ao contrrio da segunda alternativa, no efetua trocas (que implicam uma
operao triangular). Cada chave encontrada fora do seu segmento apenas transposta para a posio
vaga existente. Essa operao (transposio de segmento) no ser considerada na anlise. Apenas
consideraremos a quantidade de comparaes efetuadas para decidir em que segmento deve se
localizar cada uma das chaves. Como comparamos a chave particionadora com todas as demais,
obviamente sempre sero requeridas n-1 comparaes para particionar n chaves.
Supondo, agora, a situao ideal em que a chave particionadora seja tal que o particionamento
produza dois segmentos S1 e S3 de igual tamanho, teremos dividido o vetor de n elementos em
segmentos com os seguintes tamanhos:
S1 com (n - 1) /2 chaves;
S2 com 1 chave;
28

S3 com (n - 1)/2 chaves.


Esquematicamente:

Persistindo a situao ideal ao longo dos demais particionamentos, os segmentos que sero
sucessivamente gerados tero os seguintes comprimentos:

E assim por diante, at que (n-k)/(k+1) <=1. Cada um desses segmentos vai requerer, para ser
particionado, tantas comparaes quantos so seus elementos menos uma unidade.
Assim, temos:
1. Para o primeiro segmento: n-1 comparaes;
2. Para os 2 seguintes: (((n-1)/2)-1) x 2 = n-3 comparaes;
3. Para os 4 seguintes: (((n-3)/4)-1) x 4 = n-7 comparaes;
4. Para os 8 seguintes: (((n-7)/8)-1) x 8 = n-15 comparaes;
e assim sucessivamente, enquanto restarem segmentos de mais de um elemento. Esta condio deixar
de ocorrer aps executados log2 n passos de particionamento. Logo, o total das comparaes a serem
efetuadas igual soma do nmero de comparaes efetuadas em cada passo, ou seja:

Este seria, pois, o melhor caso para o mtodo. Naturalmente, no devemos esperar que isto ocorra
em situaes normais. Supondo que as chaves sejam nicas, a probabilidade de escolhermos ao acaso
exatamente a particionadora mediana 1/n. No entanto, segundo Wirth, quando escolhemos a chave
particionadora aleatoriamente, o desempenho mdio do quicksort se torna inferior ao do caso timo de
um fator da ordem de apenas 2 ln 2.
29

H, ainda, a considerar o pior caso: quando a chave particionadora escolhida a menor (ou a maior)
de todas. Nesse caso, e persistindo a situao em todos os demais particionamentos, o mtodo se torna
extremamente lento, com desempenho quadrtico. De fato, se a chave escolhida for sempre a menor de
todas, os particionamentos produziro os segmentos S1 vazios e os S3 com n-1 elementos, conforme o
esquema mostrado adiante. Sendo necessrios, pois, n-1 passos, cada um deles exigindo um nmero de
comparaes igual ao nmero de elementos menos um, ou seja:
(n-1) + (n-2) + (n-3) + ... + 1 = ((n-1) + 1)/2 x (n-1) = (n2 - n)/2
Esse caso ocorrer, por exemplo, se o vetor j estiver ordenado e sempre escolhermos a primeira
chave como particionadora. Assim, quando o vetor estiver parcialmente ordenado, conveniente usar
uma chave que ocupe uma posio mais central do mesmo.
30

6 CLASSIFICAO POR SELEO

Os mtodos que formam a famlia de classificao por seleo caracterizam-se por procurarem, a cada
iterao, a chave de menor (ou maior) valor do vetor e coloc-la na sua posio definitiva correta, qual
seja, no incio (ou no final) do vetor, por permutao com a que ocupa aquela posio. O vetor a ser
classificado fica, desta maneira, reduzido de um elemento. O mesmo processo repetido para a parte
restante do vetor, at que este fique reduzido a um nico elemento, quando ento a classificao estar
concluda. Os mtodos de classificao por seleo diferenciam-se uns dos outros, pelo critrio
utilizado para selecionar, a cada iterao, a chave de menor (ou maior) valor.

6.1 Mtodo de Seleo Direta


Neste mtodo, a seleo da menor chave feita por pesquisa seqencial. Uma vez encontrada a menor
chave, esta permutada com a que ocupa a posio inicial do vetor, que fica, assim, reduzido de um
elemento. O processo de seleo repetido para a parte restante do vetor, sucessivamente, at que
todas as chaves tenham sido selecionadas e colocadas em suas posies definitivas.
Suponha que se deseja ordenar o vetor: 9 25 10 18 5 7 15 3. Segundo o princpio formulado, a
ordenao desse vetor de 8 chaves demandar 7 iteraes, conforme mostrado na tabela a seguir.

Observe que a ltima iterao (7) encerra a classificao, pois se as n-1 menores chaves do vetor
esto em suas posies definitivas corretas, ento a maior (e ltima) automaticamente tambm estar.
6.1.1 Implementao
A implementao do mtodo direta. O procedimento a seguir toma a primeira chave da parte ainda
no ordenada do vetor como sendo, em princpio, a menor de todas, comparando-a seqencialmente
com todas as demais. Sempre que encontrar uma de valor inferior, passa a considerar esta como a
menor. Ao final de cada iterao, a menor chave encontrada inserida na primeira posio da parte no
classificada, que assim fica reduzida de um elemento.
procedure SelecaoDireta;
Var i, j, indX, menor: integer;
Begin
for i := 1 to (MAX -1)
do begin
menor := V[i];
indX := i;
for j := (i + 1) to MAX
do begin
if (V[j]) < menor)
then begin
menor := V[j];
31

indX := j;
end;
end;
V[indX] := V[i];
V[i] := menor;
end;
end;

6.1.2 Anlise do Desempenho


Na anlise do desempenho do mtodo de seleo direta podemos considerar dois aspectos: a
quantidade de comparaes efetuadas e a quantidade de trocas de mnimos efetuadas. Quanto ao
nmero de comparaes efetuadas, temos:
1 iterao: compara o 1 elemento com os n-1 demais: n-1 comparaes.
2 iterao: compara o 2 elemento com os n-2 demais: n-2 comparaes.
3 iterao: compara o 3 elemento com os n-3 demais: n-3 comparaes.
...
(n-1) iterao: compara o (n-1) elemento com o ltimo: 1 comparao.
Total de comparaes: (n-1) + (n-2) + (n-3) + ... + 1 = (n-n)/2 = O(n).
Observe que a quantidade de comparaes efetuadas constante para um dado n, ou seja, no
depende do arranjo prvio das chaves. J o nmero de trocas de mnimos durante a busca da menor
chave em cada iterao no constante, pois depende da ordem em que as chaves se apresentam. Este
fato vai fazer com que os tempos de classificao de dois vetores com as mesmas chaves, porm
arranjadas de formas diversas, possam ser diferentes.
Supondo que a distribuio dos valores das chaves seja uniforme, ento o nmero mdio de trocas de
mnimos efetuadas ao longo de uma iterao pode ser estimado da seguinte forma: a probabilidade de
que a segunda chave seja menor do que a primeira (tomada como mnimo inicial) 1/2. A
probabilidade de que a terceira chave seja menor do que as duas primeiras 1/3. A probabilidade da
quarta ser menor do que as trs primeiras 1/4 e, assim por diante, at a ltima chave, cuja
probabilidade de ser menor do que todas as n-1 que a antecedem 1/n. Estes valores correspondem s
freqncias relativas com que cada troca vai ocorrer. Portanto, a quantidade mdia de trocas de
mnimos durante uma iterao ser: (1/2)+ (1/3) +(1/4)+ ... + (1/n), sendo n o nmero de chaves
envolvidas na iterao.
A srie de valores apresentada, acrescida da unidade, conhecida na literatura como srie
harmnica, e sua soma denominada nmero harmnico, denotado por Hn e cujo valor, para os fins aqui
propostos, pode ser aproximado por:

O smbolo y representa a constante de Euler, cujo valor : 0,5772157...

6.2 Mtodo da Seleo em rvore Heapsort

A concluso a que chegamos sobre o mtodo da seleo direta torna o seu uso muito restrito, pois
infelizmente sempre sero necessrias n-1 comparaes para decidir qual a menor dentre n chaves.
Por outro lado, essa condio s absolutamente necessria na primeira varredura. Ao final desta,
32

podemos reter outras informaes alm da identificao da menor chave, que permitam acelerar a
busca das outras menores que sucedem a primeira.
Por exemplo, se dividirmos o vetor de chaves em dois segmentos de igual tamanho, poderemos, na
primeira varredura, identificar a menor chave de cada segmento. A seguir, selecionaramos a menor
dentre estas duas, colocando-a na sua posio definitiva correta. Na segunda varredura ser necessrio
operar apenas com o segmento que forneceu a menor chave, ou seja, trabalharemos com n/2 chaves ao
invs de n-1, o que j representa um ganho significativo. Uma vez encontrada a segunda menor chave
deste segmento, a comparamos com a do outro para selecionar a menor chave desta segunda iterao.
Este princpio pode ser estendido de modo a subdividir o vetor de chaves em tantos segmentos
quantos forem possveis, de tal forma a minimizar a quantidade de comparaes a ser efetuada em
cada iterao para encontrar a chave de menor valor. O quadro a seguir exemplifica a aplicao deste
princpio, mostrando um vetor de chaves sucessivamente dividido ao meio, sendo a menor chave de
cada segmento identificada em negrito.

A identificao da menor chave em cada um dos sucessivos segmentos nos quais o vetor foi
subdividido pode ser representada pela seguinte estrutura de rvore:

Agora podemos selecionar a menor de todas as chaves e remov-la do sistema, deixando vagas as
posies que ela ocupava. Essas vagas sero tomadas, por sua vez, pelas chaves sucessoras (na ordem
crescente), de cada subrvore, permanecendo livre apenas a ltima posio anteriormente ocupada pela
chave removida. Na primeira figura vemos a rvore com a chave 09 removida e, na seguinte vemos as
vagas por ela deixadas ocupadas pelas chaves sucessoras.
33

Remoo da menor chave

Promoo da sucessora

Desse modo, a segunda menor chave aparecer na raiz da rvore e poder ser removida, seguindo-se
a promoo da terceira menor chave, mostrada na prxima figura. Esse processo se repete at que
todas as n chaves tenham sido removidas.

Promoo da terceira menor chave

Observe que, a cada nvel da rvore que se desce, o problema fica reduzido pela metade, pois apenas
uma subrvore afetada pela remoo da chave. Com isso, a cada iterao, evitamos trabalhar com a
totalidade das chaves restantes, o que ir acelerar bastante a seleo da menor chave em cada iterao.
Este , pois, o princpio bsico do mtodo de seleo em rvore.
6.2.1 Exemplo
A implementao do mtodo, proposta por J. Williams, permite simplificar ainda mais a estrutura de
rvore que mantm a hierarquia das chaves. A simplificao elimina a redundncia de ocorrncias de
chaves na rvore, fazendo com que ela possua exatamente n nodos, ao invs dos 2n-1 at agora usados,
o que simplifica a manipulao da estrutura. Dado ento um vetor de chaves C1, C2, ... , Cn,
consideramos este vetor como sendo a representao de uma rvore binria, usando a seguinte
interpretao dos ndices das chaves:
C1 raiz da rvore e C2i = subrvore da esquerda de Ci, C2i+1 = subrvore da direita de Ci para
i=1, n div 2.
Exemplificando: dado o vetor C1..7, e utilizando a interpretao anterior, podemos v-lo como
sendo a representao da rvore mostrada na figura:
34

rvore representada pelo vetor C1..7

O passo seguinte consiste em trocar as chaves de posio dentro do vetor, de tal forma que estas
passem a formar uma hierarquia, na qual todas as razes das subrvores sejam maiores ou iguais a
qualquer uma das suas sucessoras, ou seja, cada raiz deve satisfazer as seguintes condies:
Ci >= C2i e Ci >= C2i+1 para i= 1, n div 2
Quando todas as razes das subrvores satisfazem essas condies, dizemos que a rvore forma um
heap. Da a denominao do mtodo.
Essas condies, na verdade, estabelecem uma relao de ordem entre as subrvores, de tal modo
que, para qualquer heap, podemos afirmar que C1 >= Ci, i=2, n, ou seja, podemos afirmar que a maior
chave dentre as n que formam o vetor aquela que est na raiz da rvore, isto , a chave C1.
O problema agora , ento, efetuar as trocas de posies das chaves no vetor, de tal forma que a
rvore representada passe a ser um heap. Esse processo pode ser feito testando cada uma das
subrvores para verificar se elas satisfazem, separadamente, a condio de heap. Apenas as subrvores
que possuem pelo menos um sucessor devem ser testadas. A maneira mais simples de sistematizar esse
teste inici-lo pela ltima subrvore, qual seja, aquela cuja raiz est na posio n div 2 do vetor de
chaves, prosseguindo, a partir da, para as subrvores que antecedem esta, at testar a raiz da rvore.
Desse modo, a primeira subrvore da figura a ser testada aquela cuja raiz C3, seguindo-se o teste
pela subrvore de raiz C2 e finalizando com a de raiz C1. Sempre que for encontrada uma subrvore
que no forme um heap, seus componentes devem ser rearranjados de modo a formar o heap. Por
exemplo, se for encontrada uma subrvore como a da figura (a), seus componentes devem ser
rearranjados na forma da figura (b).

Transformao de uma subrvore em heap

Pode ocorrer tambm que, ao rearranjarmos uma subrvore, isto venha a afetar outra, do nvel
imediatamente inferior, fazendo com que esta deixe de formar um heap. Essa possibilidade obriga a
verificar, sempre que for rearranjada uma subrvore, se a sucessora do nvel abaixo no teve a sua
condio de heap desfeita. Para exemplificar todo o processo, suponhamos o seguinte vetor de chaves:
1 2 3 4 5 6 7
12 09 13 25 18 10 22
cuja interpretao sob a forma de rvore :
35

A transformao dessa rvore em heap inicia pela subrvore cuja raiz 13, j que seu ndice, no
vetor, 7 div 2 = 3. O rearranjo dos componentes desta subrvore produz a configurao da rvore e
do vetor correspondente mostrada na figura:

Transformao da subrvore de raiz 13 em heap

A prxima subrvore a ser rearranjada a de raiz 09, mostrada na figura:

Transformao da subrvore de raiz 09 em heap

E finalmente a de raiz 12, na figura abaixo.

Transformao da subrvore de raiz 12 em heap


36

Nesse caso ocorreu a possibilidade mencionada, na qual a transformao de uma subrvore afeta
outra de um nvel abaixo. A subrvore afetada deve ser rearranjada, conforme mostra a figura abaixo.

Rearranjo de uma subrvore do nvel inferior

Uma vez que todas as subrvores formam heaps, a rvore toda tambm um heap. At aqui se
conseguiu apenas selecionar a maior chave que aparece na raiz da rvore. Entretanto, a partir deste
ponto, em vista da configurao tomada pela rvore, a seleo das chaves seguintes ser simplificada.
Se a chave que est na raiz a maior de todas, ento sua posio definitiva correta na ordem
crescente na ltima posio do vetor, onde ela colocada, por troca com a chave que ocupa aquela
posio. Com a maior chave ocupando a sua posio definitiva podemos, a partir de agora, considerar
o vetor como tendo um elemento a menos, o qual ser mostrado no vetor (rea sombreada), mas no
aparecer na rvore correspondente, conforme ilustrado na figura a seguir.

Colocao da maior chave em sua posio definitiva

Para selecionar a prxima chave, deve-se fazer com que a rvore volte a ser um heap. Como a nica
subrvore que sofreu alteraes foi a da raiz, basta fazer com que esta subrvore volte a formar um
heap, rearranjando-se os seus componentes, e verificando-se, tambm, se este rearranjo no interferiu
em uma das subrvores do nvel imediatamente abaixo.

Seleo da segunda maior chave


37

Novamente a maior chave dentre as restantes aparece na raiz (perceba a pequena quantidade de
comparaes necessrias para efetuar a seleo desta chave). A figura abaixo mostra a segunda maior
chave sendo colocada em sua posio definitiva correta, de forma idntica ao do caso anterior.

Colocao da segunda maior chave em sua posio

Na figura seguir a rvore novamente rearranjada para formar um heap, o que permitir selecionar a
prxima chave.

Seleo da terceira maior chave

Os demais passos seguem o mesmo princpio e so mostrados na figura a seguir. Da mesma forma
que no caso da seleo direta, a classificao estar encerrada aps a iterao n-1, pois teremos
selecionado as n-1 maiores chaves, restando apenas a menor de todas, que estar na posio inicial do
vetor, o que encerra a classificao.
38

Final do processo de classificao

6.2.2 Implementao
O algoritmo que implementa esse mtodo segue exatamente a descrio anterior. Para simplificar a
manipulao do vetor, foi criada uma funo auxiliar keyval, que retorna o valor da chave que est na
posio i do vetor, caso i <= n. Em caso contrrio, o valor da funo ser igual a minkey, que
corresponde ao menor valor de chave possvel de ser representado. Por exemplo, se as chaves forem do
tipo inteiro de 16 bits, ento minkey ser igual a -215 = -32768. O uso dessa funo nos permitir no
fazer distino entre nodos razes e folhas, assegurando o trmino natural do processamento.
/* baseado em algoritmo de [Aze 96], Pg 59 */
function keyval(n, i: integer): integer;
begin
if (i > n)
then return := MINKEY
else return := V[i];
end;

Com auxlio de keyval, escrevemos o procedimento heap, cuja finalidade transformar em heap a
subrvore cuja raiz apontada por r. Para cada subrvore transformada, verificado se a subrvore do
nvel inferior que esteve envolvida na transformao no foi afetada. Em caso afirmativo, a verificao
tambm se estende a ela. O processo se encerra quando no houver mais trocas, ou quando for atingido
o nvel das folhas da rvore.
/* baseado em algoritmo de [Aze 96], Pg 60 */
procedure heap(n, r: integer);
var i, h, r, aux: integer;
troca: boolean;
begin
i := r;
troca := true;
while troca
do begin
// procura maior subrvore
if (keyVal(n, 2*i) > keyVal(n, 2*i+1))
then h := 2 * i // subrvore da esquerda
else h := 2 * i + 1; // subrvore da direita
if (V[i] < keyVal(n, h)) // compara raiz com o maior sucessor
then begin
aux := V[i]; // troca
V[i] := V[h];
V[h] := aux;
i := h; // desce para a raiz da subrvore afetada pela troca
end
else troca := false;
end;
end;

Resta agora o procedimento final, que denominaremos heapsort. Este ser constitudo de duas partes.
A primeira far a formao do heap inicial: preparar a rvore para a seleo da maior chave. A
segunda parte, que denominaremos manuteno do heap, rearranjar a rvore aps cada seleo da
maior chave, para que ela retome a forma de heap, permitindo assim a seleo da prxima chave. Essa
segunda parte demanda n-1 iteraes, e corresponde fase de classificao propriamente dita.

/* baseado em algoritmo de [Aze 96], Pg 61 */


procedure heapSort(n: integer);
var i: integer;
troca: boolean;
begin
39

i := n div 2; // raiz da ltima rvore


for r := i downto 1 // formao do heap inicial
do heap(n, r);
for n1 := (n-1) downto 1 // manuteno do heap
do begin
aux := V[i]; // troca
V[i] := V[n1+1];
V[n1+1] := aux;
heap(n1, 1); // refaz heap a partir da raiz da rvore
end;
end;

6.2.3 Anlise do Desempenho


Na anlise do desempenho do heapsort, devemos considerar separadamente as duas fases do mtodo: a
formao do heap inicial e a sua manuteno. Para a fase de formao, a situao mais favorvel ao
mtodo aquela na qual as chaves j satisfazem as condies Ci >= C2i e Ci >= C2i+1, para i=1, n div
2. Uma permutao que satisfaz essas condies a do vetor na ordem decrescente, ou seja, na ordem
contrria desejada. Nesses casos, a rvore derivada j forma um heap, no sendo necessria nenhuma
troca de posio entre as chaves. Apenas sero efetuadas 2 x (n div 2) comparaes (duas para cada
subrvore testada), para se certificar de que a rvore um heap.
Com vistas simplificao da anlise, consideraremos a diviso exata n/2 ao invs da diviso inteira
n div 2. Isto afeta em meia unidade apenas os casos de n mpar. Assim, o nmero de comparaes no
melhor caso ser considerado como sendo igual a n e no a 2 (n div 2).
Ao contrrio, o pior caso para a formao do heap aquele no qual o vetor j se encontra na ordem
desejada. Nesta situao, alm de haver trocas em cada uma das n/2 subrvores examinadas, as trocas
sempre afetaro as demais subrvores dos nveis inferiores, at o ltimo nvel, pois as chaves
rebaixadas em cada subrvore sero sempre as menores, as quais devero se localizar nas folhas.
Assim, teremos as n comparaes seguidas de n/2 trocas, acrescidas daquelas comparaes e trocas
referentes ao escorregamento das chaves menores at o nvel das folhas.
Para calcular esta quantidade extra de comparaes e trocas, podemos usar a tcnica da induo,
calculando quantas sero efetuadas a partir de cada nvel da rvore, iniciando pela raiz (nvel 1),
obtendo, a seguir, a expresso que generaliza para qualquer nvel. No caso da subrvore da raiz,
teremos uma troca para que ela, isoladamente, forme um heap, seguida de log2 (n)-2 trocas
correspondentes ao escorregamento da chave rebaixada at o nvel das folhas. No nvel 2, alm das
duas trocas correspondentes s duas subrvores deste nvel, teremos 2x(log2(n)-3) trocas devidas ao
escorregamento at o nvel das folhas, e assim sucessivamente at o penltimo nvel, no qual somente
haver trocas referentes transformao das subrvores em heaps, j que, por formar o ltimo nvel de
subrvores, no h propagao.

6.3 Mtodo de Seleo em rvore Amarrada - Threadedheapsort


A fase de manuteno do heap pode ser implementada de forma alternativa. Ao invs de trocar o
elemento da raiz com aquele que ocupa a ltima posio do vetor, provocando a necessidade de
rearranjar o heap, podemos coloc-lo em um vetor auxiliar, em sua posio correta e, depois,
simplesmente promover as chaves sucessoras de cada subrvore a partir da raiz.
Tudo se passa como se as chaves sucessoras de cada raiz de subrvore estivessem amarradas umas s
outras e, ao removermos a raiz da rvore, a sua sucessora seria puxada pela amarra, ocupando assim o
lugar vago da raiz, trazendo, ao mesmo tempo, um nvel acima, as demais sucessoras ligadas pelas
amarras. A ltima chave dessa cadeia deixaria um vazio no seu lugar. Na implementao, esse vazio
pode ser representado por um valor singular de chave, tal como minkey, j utilizado na funo keyval.
40

A seqncia de quadros das figuras ilustram esse processo, partindo do heap j formado. O valor
minkey est representado pelo smbolo (*). Os arcos representam as amarras. A seta apontando para
cima sobre a raiz indica que esta ser removida. Abaixo de cada rvore mostrado o vetor de chaves
(acima) e o vetor auxiliar (abaixo).

Manuteno do heap por arrasto das chaves sucessoras (1 parte)

Manuteno do heap das chaves sucessoras (2 parte)

O algoritmo que implementa esta alternativa semelhante ao j apresentado, e praticamente auto-


explicativo. O procedimento pull up faz o papel das amarras, promovendo a ascenso de cada uma das
chaves sucessoras um nvel acima do qual se encontram. O procedimento threadheapsort constri o
heap inicial (de forma idntica ao heapsort) e, na fase de manuteno, a cada iterao, coloca o
elemento da raiz na sua posio correta no vetor auxiliar V1 e executa a operao pull up.

/* baseado em algoritmo de [Aze 96] Pg 72 */


procedure pullUp(n: integer);
var i, h, chave: integer;
begin
i := 1;
while (chave = MINKEY)
do begin
if (keyVal(n, 2*i) > keyVal(n, 2*i+1))
then h := 2 * i // amarra sucessor da esquerda
else h := 2 * i + 1; // amarra sucessor da direita
41

chave := keyVal(n, h); // obtm valor do sucessor


V[i] := chave; // promove um nvel
i := h; // desce
end;
end;

procedure threadHeapSort(n: integer);


// V, V1: arrays declarados previamente
var i, n, k: integer;
begin
i := n div 2;
for k := i downto 1 // formao do heap
do heap(n, k);
V1[n] := V[1];
for k := n-1 downto 1 // manuteno do heap
do begin
pullUp(n); // promove chaves sucessoras
V1[k] := V[1]; // coloca maior chave em sua posio
end;
end;

A anlise da fase de manuteno neste caso mais simples, uma vez que todas as chaves, com
exceo daquela que j se encontra na raiz, devero subir desde o nvel onde esto at o nvel 1. Dessa
forma, teremos 2 chaves subindo 1 nvel, 4 chaves subindo 2, 8 subindo 3 e assim por diante. Esse
valor corresponde quantidade total de nveis que as chaves subiram.
Obviamente esse total superior s trocas efetuadas pelo mtodo heapsort em sua fase de
manuteno, as quais correspondem a aproximadamente 3/4 do valor que obtivemos. Entretanto, no
caso do mtodo threadedheapsort, no se trata de operaes de trocas, mas simples transposies
(atribuies) de chaves de um vetor para outro. Devemos ento, para comparar as duas alternativas,
computar as atribuies efetuadas em cada caso. Cada troca do heapsort corresponde a trs atribuies,
enquanto cada operao de subida de nvel do threadedheapsort corresponde a somente uma atribuio.
Assim, o total de atribuies do heapsort expresso por (9/4)n * log2(n/4)+3n enquanto as atribuies
do threadedheapsort correspondem a n * log2 (n/4)+n, o que claramente corresponde a uma vantagem
para esta alternativa.
Alm disso, a quantidade de comparaes efetuadas tambm menor, pois enquanto o heapsort
efetua duas comparaes para cada troca, o threadedheapsort efetua apenas uma para cada promoo
de nvel. O quadro a seguir mostra as quantidades de operaes efetuadas em cada um dos dois
mtodos na fase de manuteno do heap. A desvantagem do uso dessa alternativa a necessidade de
uma rea extra para o vetor ordenado.

Trocas para manter o heap

Resumo das comparaes e atribuies efetuadas


42
43

7 CLASSIFICAO POR DISTRIBUIO DE CHAVES - RADIXSORT

Este mtodo antecede aos computadores digitais. uma adaptao do processo de classificao
mecnica de cartes perfurados, usados desde a primeira metade do sculo XX como dispositivo para
armazenamento de dados. O modelo mais comum representava at 80 caracteres, um por coluna,
formando, cada carto, um registro de arquivo. Caracteres contguos podiam representar um dado
(campo). Desta forma, o carto era formado de um ou mais campos de dados, conforme a aplicao.
Antes do advento do computador, o carto perfurado era usado em processos mecanizados, que
envolviam basicamente operaes de tabulao, contagem e totalizao de dados. Com a chegada do
computador digital, o carto passou a ser usado tambm como meio de entrada de dados. Em ambos os
casos, freqentemente surgia a necessidade de ordenar fisicamente os cartes segundo os valores de
um determinado campo de dados, para que pudessem ser processados naquela ordem.
A operao de classificao dos cartes era feita por um equipamento que lia a informao
perfurada, em uma dada coluna de cada vez, e remetia o carto para um escaninho correspondente ao
valor lido. Dessa forma, todos os cartes que possuam o dgito 0 na coluna selecionada eram
empilhados no escaninho 0. Os que possuam o dgito 1, no escaninho 1, e assim por diante. Para obter
os cartes ordenados pelo dgito da coluna selecionada, bastava recolh-los dos escaninhos na ordem
crescente, ou seja, primeiro os do escaninho 0, depois os do escaninho 1 e assim at o 9.
Quando o dado a ser classificado ocupava um campo com mais de uma coluna (possua mais de um
dgito), era necessrio efetuar tantos passos de ordenaes quantos fossem os dgitos, iniciando pelo de
menor ordem (das unidades) e prosseguindo at o de mais alta ordem. Aps classificar cada coluna, as
pilhas formadas em cada escaninho eram colocadas umas sobre as outras, na ordem crescente, sendo
repetido o processo para as colunas seguintes, sucessivamente, at a ltima.
Por exemplo, suponhamos que um arquivo formado por 20 cartes tivesse, em um certo campo, os
seguintes valores perfurados:
523, 018, 125, 937, 628, 431, 243, 705, 891, 362, 429, 005, 540, 353, 115, 427, 910, 580, 174, 456
A classificao pelo dgito da unidade produziria a seguinte distribuio pelos escaninhos:
115
580 353 005
910 891 243 705 427 628
540 431 362 523 174 125 456 937 018 429
0 1 2 3 4 5 6 7 8 9

Recolhendo os cartes na ordem descrita, obteremos:


540, 910, 580, 431, 891, 362, 523, 243, 353, 174, 125, 705, 005, 115, 456, 937, 427, 018, 628, 429
Classificando agora pela coluna das dezenas obtemos :
429
628
018 427
005 115 125 937 243 456
705 910 523 431 540 353 362 174 580 891
0 1 2 3 4 5 6 7 8 9

Recolhendo-os em ordem:
705, 005, 910, 115, 018, 523, 125, 427, 628, 429, 431, 937, 540, 243, 353, 456, 362, 174, 580, 891
44

E finalmente classificando pela coluna da centena e depois retirando-os em ordem, obtemos os


cartes na ordem desejada:
456
174 431 580
018 125 362 429 540 937
005 115 243 352 427 523 628 705 891 910
0 1 2 3 4 5 6 7 8 9

005, 018, 115, 125, 174, 243, 353, 362, 427, 429, 431, 456, 523,540,580,628, 705, 891,910,937.
O mesmo processo podia ser aplicado em campos com dados alfanumricos, exigindo no entanto
que cada passo fosse desdobrado em duas etapas, j que os caracteres alfabticos eram representados
por meio de duplas perfuraes em cada coluna do carto.
A utilizao deste princpio para classificar dados usando um computador parece desvantajosa, pois o
processo requer tantos passos de classificao quantos so os dgitos dos dados. Entretanto, como em
cada passo somente um dgito do dado considerado, podemos utiliz-lo como um ndice que vai
remeter o dado todo para o escaninho correspondente. Ou seja, no ser necessrio efetuar
comparaes entre os dados para estabelecer a sua ordem relativa. Resta-nos, assim, construir um
mecanismo que simule de maneira eficiente os escaninhos da classificadora de cartes, considerando
que no sabemos, a priori, qual a capacidade de empilhamento que deve ter cada um deles.
Para isto, temos duas alternativas. A primeira construir uma lista encadeada onde os dados so
inseridos medida que so distribudos segundo os valores de seus dgitos. Para simplificar a
administrao da lista como um todo, pode-se manter uma lista (pilha) separada para representar cada
escaninho, juntamente com um vetor de cabeas que apontam para o topo de cada uma. O valor inicial
dos ponteiros null, indicando escaninhos vazios. Para que possamos manter a informao que indica
a base de cada pilha, podemos organizar as listas sob forma circular, de modo que o nodo do topo
aponte para o nodo da base. A manuteno dessa informao necessria para que possamos, ao final
de cada passo, unir as listas formadas, e evita, ao mesmo tempo, a necessidade de manter outro vetor
de ponteiros que apontem para as bases das pilhas.
medida que os dados vo sendo distribudos, os ponteiros correspondentes vo sendo atualizados,
indicando o topo de cada pilha. Aps a distribuio do ltimo dado, as pilhas devem ser concatenadas,
formando a lista ordenada pelo dgito em questo. Deve haver um apontador cabea para indicar o
incio da lista. Dessa forma, a distribuio das chaves pelos escaninhos pode ser feita utilizando-se o
dgito selecionado para enderear o vetor de ponteiros, inserindo-se a chave na posio apontada pelo
ponteiro correspondente, o que corresponde a distribuir a chave no topo do seu escaninho.
Visando minimizar as operaes de alocao e desalocao dinmica de nodos da lista, os mesmos
podem ser reutilizados de um passo para outro, bastando efetuar o ajuste adequado dos ponteiros
envolvidos, o que evita o uso de uma rea extra de memria. Para tornar os passos homogneos,
conveniente dispor inicialmente as chaves sob a forma de lista encadeada.
A segunda alternativa para simular os escaninhos da mquina classificadora calcular previamente
a quantidade de dados que conter cada um deles. Com isto, podemos utilizar uma nica estrutura de
vetor para representar os escaninhos, j que a posio inicial de cada um deles pode ser calculada. Os
ndices que apontam estas posies iniciais so obtidos calculando-se as freqncias acumuladas dos
dgitos, e apontam para a primeira posio livre (topo) de cada escaninho. medida que as chaves vo
sendo distribudas pelos escaninhos, os ndices vo sendo incrementados, passando a indicar as
prximas posies livres.
Para executar o primeiro passo do exemplo, obtemos as seguintes freqncias de ocorrncia de cada
dgito, as quais nos permitem calcular as freqncias acumuladas, que por sua vez indicam a posio
inicial de cada escaninho:
45
Dgito 0 1 2 3 4 5 6 7 8 9
Freqncia 3 2 1 3 1 4 1 2 2 1
Freqncia acumulada 0 3 5 6 9 10 14 15 17 19
Freqncias acumuladas por escaninho

Esta segunda alternativa possui a vantagem de dispensar o uso de ponteiros (usa ndices). Por outro
lado, exige uma varredura prvia das chaves para contabilizar as freqncias de ocorrncia dos dgitos.
Qualquer que seja a alternativa adotada, o mtodo tem a vantagem de adaptar a mquina
classificadora para as chaves que vo ser ordenadas, caso estas possuam alguma caracterstica especial
que possa ser explorada para obter alguma vantagem no processo como um todo.
Considere, por exemplo, a classificao de dados que representam os anos de nascimento de um
grupo de pessoas. Se considerarmos as chaves como um nmero comum de 4 dgitos cada, teremos de
fazer a classificao em 4 passos. Entretanto, podemos observar que apenas no 1 e 2 passo podem
ocorrer todos os dgitos de 0 a 9. No 3 passo os dgitos possveis so 8, 9 e 0, enquanto que no 4
apenas o 1 e o 2 so possveis. De posse desta informao, podemos dividir as chaves de forma
diferente. Agrupando os dgitos da centena e do milhar, efetuaremos a classificao em trs passos ao
invs de quatro, sendo o 1 e 2 normalmente processados, enquanto o 3 considerar os agrupamentos
18, 19 e 20. Isto possvel, j que podemos construir a mquina classificadora com a quantidade de
escaninhos que quisermos, cada um deles com o endereo que tambm quisermos. Nesse caso, para o
terceiro passo teramos apenas trs escaninhos para receber as chaves que tiverem os valores 18, 19 e
20 na parte considerada. Certamente isto contribui para melhorar a eficincia do mtodo.
Naturalmente, esse mtodo no recomendado para chaves muito longas, como por exemplo nomes
de pessoas, pois alm de exigir muitos passos para decidir a ordem, cada parte da chave possui muitas
alternativas (as letras do alfabeto, com acentos, maisculas e minsculas). Nesses casos, recomenda-se
o uso de mtodos baseados em comparaes entre as chaves, pois muitas vezes apenas a 1 letra do
prenome suficiente para decidir a ordem relativa de dois nomes.
7.1 Implementao
O procedimento distribuio, apresentado adiante, implementa a alternativa que usa listas
encadeadas para representar os escaninhos, e efetua apenas um dos passos do processo de
classificao, fazendo a distribuio da chaves segundo a isima parte de cada uma delas. Supe-se
que os dados j se encontram sob a forma de uma lista encadeada, cujo primeiro elemento apontado
pela varivel incio. Cada nodo da lista um registro de dois componentes: a chave e o ponteiro para o
nodo sucessor. Esses componentes so acessados pelas funes key (p) e next (p), respectivamente. O
vetor top, de b elementos, indexados de 0 a b-1, contm os ponteiros para os topos das pilhas que
representam os escaninhos. A funo parte (i, k) extrai a parte i da chave k. A operao setnext (p, q)
atribui o valor q ao componente next do nodo apontado por p. A execuo do procedimento redistribui
os nodos da lista de modo que estes se apresentem ordenados segundo a parte i das chaves. A varivel
incio aponta para o incio da nova lista. As figuras seguintes mostram a esquematizao da insero de
um nodo em um escaninho vazio e em um no vazio.

Insero de um nodo em um escaninho vazio


46

Insero de um nodo em um escaninho no vazio

/* pseudo-cdigo baseado em algoritmo de [Aze 96], Pg 82 */


procedure distribuicao(inicio: ponteiro de record; i, b: integer);
var p: ponteiro de record;
j, b, d: integer;
begin
// inicializao
p := inicio;
if (p <> nil)
then begin
for j := 0 to (b-1)
do top[j] := nil; // zera o vetor top, de ponteiros
while (p <> nil) // distribuio das chaves
do begin
d := parte(i, key(p)); // extrai a parte i da chave
inicio := next(p);
if top[d] = nil
then setNext(p, p) // escaninho d vazio
else begin // escaninho d no vazio
setNext(p, next(top[d]));
setNext(top[d], p);
end;
top[d] := p; // topo do escaninho d
end;
end;
// concatena os escaninhos
d := 0;
while (top[d] = nil)
do begin
d := d + 1; // procura primeiro escaninho vazio
inicio := next(top[d]); // incio da nova lista
ant := top[d]; // final provisrio da lista
j := d + 1;
while (j = b)
do begin
if top[j] <> null)
then begin // concatena
setNext(ant, next(top[j]));
ant := top[j];
setNext(ant, nil); // marca fim da lista provisria
end;
j := j + 1; // prximo escaninho
end;
end;
end;

A implementao da alternativa que utiliza um vetor no lugar da lista encadeada mais simples, pois
no lida com ponteiros. Entretanto ser necessrio utilizar um vetor de chaves auxiliar, uma vez que
47

no podemos deslocar as posies dos seus elementos como fizemos no caso da lista encadeada. A
implementao, que deixamos a cargo do aluno, est esquematizada a seguir:
procedure distribuicao;
begin
inicializao;
clculo das freqncias;
clculo das freqncias acumuladas;
distribuio das chaves;
end;

Da mesma forma que na alternativa anterior, esse procedimento dever ser repetido para cada parte
na qual as chaves foram divididas, atentando para o fato de que o resultado de um passo de
classificao sempre aparecer no vetor auxiliar.
7.2 Desempenho
Este mtodo apresenta a caracterstica de possuir um desempenho determinstico, ou seja, sempre ser
executada a mesma quantidade de operaes para uma dada quantidade n de chaves,
independentemente da ordem com que estas se apresentem no incio. Como cada passo demanda
exatamente n operaes de distribuio, ento a quantidade total de operaes ser n * np, sendo np o
nmero de partes pelas quais as chaves esto divididas. Trata-se, pois, de um mtodo de desempenho
linear, ou seja, O (n).
Infelizmente, como j mencionado anteriormente, o mtodo no recomendado para chaves longas
(np grande). Outra deficincia do mtodo que ele no pode ser aplicado, na forma como foi
apresentado, em chaves numricas que possuam valores negativos e positivos misturados. Deixamos
para o leitor a discusso deste caso e a proposta de solues.
48

8 CLASSIFICAO POR INTERCALAO - MERGESORT

A classificao por intercalao se baseia em um princpio de funcionamento bastante simples.


Consiste em dividir o vetor de chaves a ser classificado em dois ou mais, orden-los separadamente e,
depois, intercal-los dois a dois, formando, cada par intercalado, novos segmentos ordenados, os quais
sero intercalados entre si, reiterando-se o processo at que resulte apenas um nico segmento
ordenado. Para obter a ordenao dos segmentos iniciais, pode ser usado qualquer mtodo de
classificao. A figura ilustra esse processo, supondo uma diviso inicial em quatro segmentos.

Uma alternativa iniciar com segmentos de comprimento 1, os quais j esto obviamente ordenados.
Dessa forma, consideramos, ento, o vetor de n elementos como sendo formado de n segmentos de 1
elemento cada, e aplicamos o processo reiterado de intercalao a partir da, sem a necessidade de
outro mtodo de classificao para iniciar o processo. A tabela abaixo mostra um exemplo deste
princpio sendo aplicado a um vetor de 8 chaves. Cada linha, a partir da segunda, corresponde ao
resultado da intercalao de todos os pares de segmentos consecutivos da linha anterior.
23 17 8 15 9 12 19 7
17 23 8 15 9 12 7 19
8 15 17 23 7 9 12 19
7 8 9 12 15 17 19 23

Quando o tamanho do vetor no for uma potncia inteira de 2, sempre ocorrer, em pelo menos uma
iterao, um segmento sem um par para ser mesclado. Neste caso, esse segmento, que sempre ser o
ltimo do vetor, simplesmente transcrito para a iterao subseqente, como no caso abaixo.
23 17 8 15 9 12 19 7 14 10
17 23 8 15 9 12 7 19 10 14
8 15 17 23 7 9 12 19 10 14
7 8 9 12 15 17 19 23 10 14
7 8 9 10 12 14 15 17 19 23

8.1 Implementao
A implementao deste mtodo ser feita em trs nveis. No primeiro, denominado simplemerge,
definida a operao de intercalao de um par de segmentos ordenados, resultando um terceiro
segmento tambm ordenado. O segundo nvel, mergepass, far a intercalao de todos os pares de
49

segmentos ordenados nos quais o vetor est dividido. O terceiro e ltimo nvel, mergesort, efetuar
toda a seqncia de intercalaes necessria para que resulte apenas um segmento intercalado.
Esquematicamente, a implementao da operao simplemerge est representada na figura abaixo.
Os dois segmentos a serem intercalados ocupam posies contguas no vetor C (VA na implementao)
. O primeiro segmento inicia na posio isl e termina na fsl. O segundo ocupa as posies de is2 at
fs2. O resultado da intercalao aparece no vetor C (VB na implementao) a partir da posio r (=
is1).

A operao mergepass, por sua vez, efetuar a intercalao de todos os pares consecutivos de
segmentos de comprimento L do vetor C.

/* pseudo-cdigo baseado em algoritmo de [Aze 96], Pg 88 */


procedure simpleMerge(VA, VB: vetor; ia1, fa1, ia2, fa2, r: integer);
// ia1, ia2, fa1, fa2: incio e fim de segmentos no vetor VA
var ib1, ib2: integer; // ndices de incio dos segmentos no vetor VB
k, i: integer;
begin
ib1 := ia1;
ib2 := ia2;
k := r;
{ enquanto no atingir o final de nenhum dos dois segmentos, compara }
while ((ib1 <= fa1) and (ib2 <= fa2)
do begin
if (VA[ib1] < VA[ib2]) // primeiros elementos de cada segmento
then begin
VB[k] := VA[ib1]; // copia elemento do primeiro segmento
ib1 := ib1 + 1;
end
else begin
VB[k] := VA[ib2]; // copia elemento do segundo segmento
ib2 := ib2 + 1;
end;
k := k + 1;
end;
{ verifica saldo dos segmentos e copia, se houver }
if (ib1 > fa1)
50

then begin
for i = ib2 to fa2
do begin
VB[k] := VA[i];
k := k + 1;
end;
end
else begin
for i = ib1 to fa1
do begin
VB[k] := VA[i];
k := k + 1;
end;
end;
end;

procedure mergePass(VA, VB: vetor; n, L: integer);


// L: comprimento dos segmentos
var ib1, ib2: integer; // ndices de incio dos segmentos no vetor VB
k, i: integer;
begin
p := 1; // p: incio do primeiro segmento
q := p + L; // q: incio do segundo segmento
r := 1; // r: incio do segmento resultante
while (q <= n) // enquanto houver pares de segmentos
do begin
simpleMerge(VA, VB, p, q, q-1, q+L-1, n); // intercala um par de segmentos
r := r + 2 * L; // prximo segmento resultante
p := q + L; // prximo primeiro segmento
q := p + L; // prximo segundo segmento
end;
if (p <= n) // verifica se ltimo segmento possui par
then begin
for i = p to n
do VB[i] := VA[i]
end;
end;

Observe que o parmetro fa2 de chamada da procedure simpleMerge que indica a posio final do 2
segmento prev a possibilidade de que o ltimo deles tenha um comprimento menor do que L.
O nvel final da implementao dever efetuar todas as seqncias de intercalaes, desde as dos
segmentos de comprimento 1, at obter um segmento de comprimento n, dobrando o comprimento dos
segmentos aps cada execuo da operao mergepass. Devido ao fato de a intercalao sempre ser
feita em um vetor auxiliar, a operao mergepass dever alternar sucessivamente os vetores origem e
destino. Assim, a primeira intercalao ser feita do vetor VA para o VB. A segunda de VB para VA e
assim at o final. Ao concluir a ltima seqncia de intercalaes, deve ser verificado em qual dos dois
vetores est o resultado final. Isto pode ser feito examinando-se a quantidade de iteraes executadas.
Se estiver em VB, este copiado para VA, uma vez que VB no visvel o usurio.
/* pseudo-cdigo baseado em algoritmo de [Aze 96], Pg 89 */
procedure mergeSort(VA: vetor; n: integer);
// L: comprimento dos segmentos
var ib1, ib2: integer; // ndices de incio dos segmentos no vetor VB
L, i, n, nInt: integer; // nInt: nmero de intercalaes
begin
L := 1; // comprimento do primeiro segmento
nInt := 1;
while (L < n)
do begin
if odd(nInt) // nInt mpar
then mergePass(VA, VB, n, L); // de VA --> VB
51

else mergePass(VB, VA, n, L); // de VB --> VA


nInt := nInt + 1;
end;
if (not (odd(nInt))) // ltima intercalao foi mpar: VA --> VB
then begin // copia VB --> VA
for (i = 1 to n)
do VA[i] := VB[i];
end;
end;

8.2 Anlise do Desempenho


Para anlisar o desempenho deste mtodo, necessrio inicialmente estimar o nmero mdio de
comparaes efetuadas para intercalar dois segmentos de n chaves cada um. Por exemplo, no caso de
dois segmentos de 4 chaves cada, o nmero mnimo de comparaes ser 4 se todos os elementos de
um segmento forem menores (ou maiores) do que os do outro. Assim, teremos dois casos que exigiro
4 comparaes. Por outro lado, o mximo ser 7 se o processo de intercalao tiver de se estender at o
ltimo elemento de cada segmento. Este exemplo nos permite verificar que a quantidade de
comparaes necessrias para intercalar dois segmentos de comprimento n varia de n at 2n-1.
Ocorre que no podemos simplesmente considerar o nmero mdio de comparaes como sendo a
mdia aritmtica entre estes dois extremos (o que daria 5,5 para n=4), pois as freqncias de cada caso
no so as mesmas. No nosso exemplo, o caso de 4 comparaes ocorre duas vezes, enquanto o de 7
ocorre 40 vezes, conforme a tabela abaixo. Portanto, devemos considerar a mdia das quantidades de
comparaes efetuadas, ponderadas pelas freqncias com que cada uma delas ocorre. Temos, pois,
uma mdia ponderada de comparaes igual a 448 / 70 = 6,4.
Comparaes Freqncia Freqncia ponderada ( F x
(C) 4 (F) 2 C) 8
5 8 40
6 20 120
7 40 280
Total 70 448

Observando as freqncias das quantidades de comparaes efetuadas, para diversos valores de n,


verificamos que estas se distribuem segundo o dobro das combinaes (i-1/i-1), para i = n, 2n-1. As
freqncias ocorrem no dobro deste valor porque, para cada par de segmento que corresponde a uma
permutao possvel de ocorrer, existe o par recproco, que exigir a mesma quantidade de
comparaes para ser intercalado. Dessa forma, para qualquer valor de n, podemos tabular as
freqncias, de acordo com a tabela abaixo.
52

O nmero esperado de comparaes necessrias para intercalar dois segmentos de n elementos cada
dado, ento, pela relao entre os dois totais da tabela anterior. Para expressar essa relao sob a
forma de uma funo recorreremos definio de combinaes de r elementos tomados k de cada
vez, e a trs propriedades das combinaes.
53

9 LISTAS ORDENADAS
Este captulo apresenta uma estrutura de dados auto-ajustvel, no sentido de sempre se manter
ordenada aps cada insero ou remoo. Devido a este comportamento altamente dinmico, esta
estrutura perfeita para mostrar como a alocao dinmica encadeada pode ser utilizada na
implementao de um tipo de dados abstrato. Uma lista ordenada L:[a 1, a 2, ..., a n ] uma lista linear
tal que, sendo n > 1, temos:
a 1 <= a k para qualquer 1 < k <= n;
a k <= a n para qualquer 1 <= k < n;
a k-1 <= a k <= a k+1 , para qualquer 1 < k < n.
Se L uma lista ordenada, podemos garantir que nenhum elemento em L inferior a a1 ou superior
a an. Alm disso, tomando um elemento qualquer no meio da lista, nenhum elemento sua esquerda o
supera e nenhum elemento sua direita inferior a ele.
Entre as diversas operaes que podem ser realizadas com uma lista ordenada, podemos destacar:
Ins (Inserir): insere um novo elemento na lista ordenada;
Rem (Remover): remove um elemento da lista ordenada;
Find (Encontrar): procura um elemento na lista ordenada.
Sendo L uma lista ordenada e x um elemento qualquer, a operao Ins ( L, x ) aumenta o tamanho
da lista L , acrescentando o elemento x na sua respectiva posio. A operao Rem ( L ) faz com que
a lista diminua, se o elemento estiver na mesma, removendo o elemento e retornando verdadeiro. A
operao Find ( L, x ) retornar a posio do elemento x na lista.
Exerccio: Dadas as operaes sobre uma lista ordenada L, preencha o quadro a seguir com estado
da lista e o resultado de cada operao:

9.1 Implementao Encadeada para Listas Ordenadas


E bvia a necessidade de inserir e remover elementos no meio de uma lista ordenada. Para evitar o
custo de movimentao de dados que teramos numa implementao seqencial, utilizaremos
alocao dinmica encadeada ao implementar as listas ordenadas.
Uma lista ordenada L:[a1, a2, a3, ..., an,] pode ser graficamente representada como uma lista
encadeada cujos nodos armazenam os elementos a1, a2, a3,.., an.
L a1 a2 a3 an \
54

Como nenhum espao de memria ser alocado at que um elemento seja inserido na lista ordenada,
para defini-la, precisamos apenas especificar o formato dos nodos e o tipo de ponteiro que ser usado
para criar a varivel L, que aponta o primeiro dos nodos encadeados:
Type
elem: char; // define tipo elem baseado em char
LstOrd = ^nodo; // define tipo LstOrd: ponteiro para record nodo
Nodo = record // define estrutura nodo
Obj: elem;
Prox: LstOrd;
End;

No nosso exemplo:
Type
Pessoa = Record
Nome : String[30];
Sexo : Char;
Idade : Integer;
Altura: Real;
End;
ponteiro = ^Pessoas; // este o ponteiro para o nodo
Pessoas = Record // este o nosso nodo
Valor : Pessoa;
Prox : Ponteiro;
End;
Var p, prim : Ponteiro;

Obviamente, uma lista vazia representada por um ponteiro cujo valor nulo, nil. A inicializao e
o teste de lista vazia so triviais nesta implementao, uma vez definido que um ponteiro com valor
nulo representar uma lista vazia. Para criar uma lista ordenada vazia, vamos usar a procedure Criar e
para verificar se uma lista est vazia, usaremos a function Anular:
procedure Criar;
begin
LstOrd := nil;
end;
function Anular: boolean;
begin
if (LstOrd := nil
then result := true
else result := false;
end;

No nosso exemplo, anulamos o ponteiro no evento OnShow do form:


procedure TformPonteiroA.FormShow(Sender: TObject);
begin
Prim := nil;
memo1.Visible := false;
end;

9.1.1 A Operao de Insero


Ao inserir um elemento X numa lista L:[a1, a2, a3, ..., an], aps ter sido alocado um nodo apontado
por N para armazen-lo, temos quatro casos considerar:
a) A lista ordenada L est vazia.
Neste caso, o nodo apontado por N passa a ser o primeiro da lista L. Para que isto ocorra, devemos
anular o campo de ligao do nodo apontado por N, de modo a indicar que nenhum elemento alm de
55

X existe na lista. Em seguida, o endereo do nodo apontado por N deve ser copiado para a varivel L,
tal que ambos os ponteiros apontem o mesmo nodo.

If (L = nil)
Then begin
New(N);
N^.Obj := X;
N^.Prox := nil;
L := N;
end;

b) O elemento X menor ou igual ao primeiro elemento da lista.


O nodo N passa a ser o primeiro da lista, seguido imediatamente pelos nodos que j estavam
armazenados em L, isto , indicamos que o nodo apontado por L passa a ser o sucessor do nodo
apontado por N e, finalmente, fazemos L apontar para o mesmo nodo apontado por N.

If (X <= L^.Obj)
Then begin
New(N);
N^.Obj := X;
N^.Prox := L;
L := N;
end;

c) O elemento X maior que o primeiro e existe outro que o supera.


Neste caso, temos que encontrar na lista L o local correto para realizar a insero. Como no possvel
o acesso direto aos nodos da lista encadeada, devemos partir do primeiro nodo e ir seguindo os campos
de ligao, um a um, at encontrar o nodo que armazena um elemento ak, que supera X. Claramente, o
nodo N dever ser inserido antes de tal elemento.

Var P: ^nodo; // mesmo tipo de L e N


begin
P = L;
while (X > P^.obj)
do P = P^.prox;
New(N); // cria o novo nodo
N^.Obj := X;
N^.Prox := P^.prox; // troca o valor dos ponteiros
P^.prox := N;
End;

d) O elemento X maior que todos os elementos de L.


O nodo N ser inserido no final da lista ordenada L. Novamente, partimos do primeiro nodo e
seguimos os campos de ligao at encontrar o ltimo, aps o qual o nodo N ser inserido.
56

Var P: ^nodo; // mesmo tipo de L e N


begin
P = L;
while (P^.Prox <> nil)
do P = P^.prox;
New(N); // cria o novo nodo
N^.Obj := X;
N^.Prox := nil;
P^.prox := N;
End;

Analisando os casos descritos acima, observamos uma grande semelhana entre os algoritmos
apresentados para os dois primeiros casos. Tambm os dois ltimos casos podem ser descritos por um
nico algoritmo. Logo, o algoritmo completo para insero pode ser codificado como a seguir:

Procedure Inserir(X: elem); // X: char


Var N, P: ^nodo; // ou : LstOrd = mesmo tipo de L e N
begin
New(N); // cria o novo nodo
N^.Obj := X; // X vai sempre para o nodo criado
if (L = nil) or (X < L^.Obj)
then begin // primeiro e segundo casos: X vai ser o primeiro da lista
N^.Prox := L; // no primeiro caso, L = nil
L := N;
end
else begin // terceiro e quarto casos: insere X no meio ou no fim
P = L;
while ((X > P^.obj) and (P^.prox <> nil)) // ? (X > P^.prox^.obj) ?
do P = P^.prox;
N^.Prox := P^.prox; // no quarto caso, P^.prox = nil
P^.prox := N;
end;
end;

9.1.2 A operao de Remoo


Diferentemente da insero, que teoricamente sempre possvel, a remoo nem sempre pode ser
realizada com sucesso. Por exemplo, qual seria o resultado da operao Remover([a,b,c], d), se o
elemento d no faz parte da lista? Para sinalizar sucesso ou falha, a operao de remoo ser
implementada como uma funo lgica, que retorna verdadeiro ou falso de acordo com o sucesso ou
no da operao. Assim como na insero, ao remover um elemento X de uma lista ordenada L,
devemos considerar alguns casos importantes:
a) A lista ordenada L est vazia.
Como o elemento X no pode estar contido numa lista vazia e, conseqentemente, no pode ser
removido, a operao resulta em um valor lgico falso.
b) O elemento X menor que o primeiro elemento da lista ordenada L.
Como a lista L ordenada, se X menor que o primeiro elemento em L, ento X no est contido na
lista e, portanto, no pode ser removido, e a operao resulta em falso.
57

c) O elemento X igual ao primeiro elemento da lista ordenada L.


Neste caso, o primeiro nodo da cadeia dever ser liberado e o ponteiro L atualizado de modo a apontar
o prximo nodo. Para poder acessar o nodo removido a fim de devolv-lo ao espao de memria livre,
antes de deslig-lo da cadeia, precisamos fazer uma cpia do seu endereo.
d) O elemento X maior que o primeiro elemento de L.
Precisamos encontrar a posio ocupada por X. Partimos do primeiro nodo e, seguindo os campos de
ligao, vamos .caminhando. pela lista at encontrar um elemento ak cujo valor maior ou igual a X.
Se tivermos ak=X, ento podemos remov-lo. Caso contrrio, X no se encontra na lista e a operao
falha. Tambm pode ocorrer de X ser maior que qualquer valor contido na lista ordenada, e neste caso,
constataremos a falha da operao quando atingirmos o ltimo nodo da cadeia e ainda no tivermos
encontrado o elemento X.
Os dois primeiros casos so triviais, nada precisa ser feito alm de retornar falso como resultado da
operao. O terceiro caso ( X=a1) tambm bastante simples de ser resolvido. Na verdade, ns
teremos um pouco mais de dificuldade apenas no ltimo caso; mesmo assim, j vimos que procurar um
certo elemento numa lista ordenada bastantes simples. O algoritmo completo para remoo em lista
ordenada est codificado a seguir. Note que o comando Dispose executado sempre que um elemento
removido. Isto faz com que as reas de memria que no so mais necessrias sejam colocadas
novamente disposio para futura alocao pelo comando New(N). Desta forma, garantimos que
enquanto houver memria no utilizada, novos elementos podero ser inseridos na lista ordenada.

function Remover(X: elem): boolean; // X: char


Var N, P: ^nodo; // ou : LstOrd = mesmo tipo de L e N
begin
result := false;
{ if (L = nil) or (X < L^.Obj) // primeiro e segundo casos
then result := false : j naturalmente false }
if (L <> nil) and (X >= L^.Obj)
then begin
if (X = L^.obj) // terceiro caso
then begin
P = L; // guarda L para o dispose
L := L^.prox;
dispose(P);
result := true;
end
else begin // quarto caso
P = L;
while ((X > P^.obj) and (P^.prox <> nil)) // ? (X > P^.prox^.obj) ?
do P = P^.prox;
if (P <> nil) and (X = P^.obj) // ?(P^.prox <> nil) e (X = P^.prox^.obj)?
then begin
N := P^.prox;
P^.Prox := N^.prox;
Dispose(N);
Result := true;
end;
end;
end;
end;

9.1.3 A Operao de Pesquisa


58

Dada uma lista ordenada L e um elemento X, a operao Procurar(X) verifica em L a existncia de X.


Caso o elemento X no seja encontrado, retornado o fracasso da busca. Assim, quando o elemento
for encontrado, verdadeiro ser o valor de retorno da operao, caso contrrio, a funo retornar falso
para indicar que a pesquisa falhou:

function Procurar(X: elem): boolean; // X: char


var P: ^nodo; // ou : LstOrd = mesmo tipo de L e N
begin
P = L;
while ((X > P^.obj) and (P^ <> nil))
do P = P^.prox;
if (P <> nil) and (X = P^.obj)
then result := true
else result := false;
end;

9.1.4 Imprimindo uma Lista Ordenada


Para observar os elementos contidos numa lista ordenada, seria interessante dispor de uma rotina que,
dada uma lista L, a imprimisse no vdeo do computador. Atravs desta rotina poderamos ento ter
acesso ao conjunto completo de valores armazenados na lista. O formato de impresso a ser escolhido,
naturalmente depende do tipo das informaes manipuladas e da aplicao propriamente dita. Por
exemplo, se a lista ordenada armazena os nomes dos convidados de uma festa, poderamos imprimir
um elemento por linha. Aqui, entretanto, seremos consistentes com a notao j utilizada.
procedure Mostrar;
Var P: ^nodo; // ou : LstOrd = mesmo tipo de L e N
begin
P = L;
while (P <> nil))
do begin
writeln(P^.obj)
P = P^.prox;
end;
end;

No nosso exemplo:
procedure TformPonteiroA.btnMostreClick(Sender: TObject);
begin
memo1.Visible := true;
memo1.Lines.clear;
p := prim;
While p <> nil
do Begin
memo1.Lines.add(p^.valor.Nome + intToStr(p^.valor.Idade) + p^.valor.Sexo
+ floatToStr(p^.valor.Altura));
p := p^.prox;
End;
end;

9.1.5 Destruindo uma Lista Ordenada


Considere uma rotina dentro da qual utilizada uma lista encadeada cujos nodos so alocados
dinamicamente, conforme exemplificado a seguir:
procedure Principal;
Var P: ^nodo; // ou : LstOrd = mesmo tipo de L e N
begin
Criar(P);
Inserir(b);
59

Inserir(d);
Inserir(c);
Inserir(a);
Mostrar;
End;

Durante a execuo da rotina, ser criada urna lista encadeada contendo quatro nodos, sendo o
endereo do nodo inicial aquele armazenado na varivel P. Como P uma varivel local, ela ser
destruda ao trmino da execuo da rotina Principal. Entretanto, os nodos pertencentes cadeia, sendo
variveis dinmicas, somente sero liberados atravs de um comando explcito (Dispose). Desta forma,
aps a execuo da rotina, teremos quatro nodos perdidos na memria. Para resolver o problema,
vamos desenvolver uma rotina que, dada uma lista encadeada L, libera todos os seus nodos.
Devemos lembrar que no possvel liberar diretamente o primeiro nodo, pois perderamos o acesso
ao resto da cadeia. Assim, vamos precisar de uma varivel auxiliar P para guardar o endereo de um
nodo at que ele possa ser liberado.
procedure Destruir;
Var P: ^nodo; // ou : LstOrd = mesmo tipo de L e N
begin
while (L <> nil)
do begin
P := L;
L := L^.prox;
Dispose(P);
end;
end;

Com esta nova operao, podemos agora liberar todos os nodos de uma lista encadeada, assim que
ela no for mais necessria em uma certa aplicao. Vale lembrar que vetores so automaticamente
liberados quando saem do escopo em que foram definidos, isto torna desnecessrio definir operaes
de destruio para estruturas implementadas com alocao esttica. Entretanto, toda implementao
baseada em alocao dinmica deve oferecer uma operao de destruio, que ser chamada
explicitamente toda vez que uma varivel criada no for mais necessria na aplicao.
De qualquer forma, aps a execuo total de um programa, o sistema operacional recupera
automaticamente qualquer espao de memria que tenha sido alocado por ele e que no foi liberado
explicitamente. O problema dos nodos perdidos s ocorre durante a execuo do programa, que pode
ser interrompida devido insuficincia de memria.

9.2 Tcnicas de Encadeamento


Ao utilizar alocao encadeada, precisamos lanar mo de alguns artifcios que facilitam a
manipulao das estruturas desenvolvidas. Dependendo das operaes a serem efetuadas sobre a lista
encadeada, ter simplesmente um ponteiro para o nodo inicial pode no ser o suficiente se esperamos
maior eficincia. Este tpico apresenta os nodos cabea e sentinela, que podero ser muito teis.
Um nodo cabea um nodo extra mantido na primeira posio de uma lista encadeada. Ele no
usado para armazenar um elemento da lista, tendo como nico objetivo simplificar os procedimentos
de insero e remoo. Numa implementao que use nodo cabea, a lista encadeada tem pelo menos
um nodo; desta forma, uma lista L vazia passa a ter outra representao:

Vejamos como esta alterao torna mais simples os procedimentos de insero e remoo.
Comecemos pela inicializao: ela no se resume mais em anular o ponteiro inicial da cadeia:
procedure Criar;
begin
60

new(L);
L^.prox := nil;
end;

Como toda lista ter sempre um nodo cabea, que nunca pode ser removido, todas as operaes de
insero e remoo sero realizadas em nodos a partir da segunda posio.
Procedure Inserir(X: elem); // X: char
Var N: ^nodo; // ou : LstOrd = mesmo tipo de L e N
begin
New(N); // cria o novo nodo
N^.Obj := X; // X vai sempre para o nodo criado
P = L^.prox;
while ((X > P^.obj) and (P^.prox <> nil)) // ? (X > P^.prox^.obj) ?
do P = P^.prox;
N^.Prox := P^.prox;
P^.prox := N;
End;

Compare com a procedure anterior. Observe que a insero fica reduzida a um nico caso: o
elemento ser inserido no meio ou no fim da lista. Assim, basta procurar a posio correta para
insero (com o lao while) e atualizar as ligaes. Tambm a remoo ser reduzida a procurar o
elemento no resto da lista e atualizar as ligaes relevantes.
function Remover(X: elem): boolean; // X: char
var N, P: ^nodo; // ou : LstOrd = mesmo tipo de L e N
begin
result := false;
P = L^.prox;
while ((X > P^.obj) and (P^.prox <> nil)) // ? (X > P^.prox^.obj) ?
do P = P^.prox;
if ((P <> nil) and (X = P^.obj)) // ? (P^.prox <> nil) e (X = P^.prox^.obj) ?
then begin
N := P^.prox;
P^.Prox := N^.prox;
Dispose(N);
Result := true;
End;
End;

O nodo cabea pode ser usado para guardar informaes gerais a respeito da lista encadeada. Um
caso bastante comum armazenar nele o nmero de elementos da lista. Naturalmente, cada vez que
um nodo inserido ou removido, o campo de dados do nodo cabea deve ser atualizado.

Um nodo sentinela tambm um nodo extra na ltima posio da lista e armazena um valor especial
chamado high value (HV): o valor mximo entre todos os que podem fazer parte da lista ordenada. Por
exemplo, em uma lista cujos elementos so caracteres, o valor HV seria o cdigo do ltimo caractere
da tabela ASCII: unsigned char = 255. Nodos cabea e sentinela, em conjunto, tornam os algoritmos
mais simples:

Para inicializar a lista, precisamos alocar e encadear os dois nodos iniciais da estrutura:
procedure Criar;
var X: char;
begin
new(L); // cria primeiro nodo
new(N); // cria ltimo nodo
61

N^.obj := High(X); // inicializa valor do ltimo nodo


N^.prox := nil;
L^.prox := N;
end;

A rotina a seguir implementa a operao de insero em listas ordenadas com nodos cabea e
sentinela. Observe que o nodo sentinela permite eliminar o teste que verifica se o final da lista j foi
atingido. Como HV mximo, certamente X nunca ser inserido no fim da lista encadeada.
Procedure Inserir(X: elem); // X: char
Var N: ^nodo; // ou : LstOrd = mesmo tipo de L e N
begin
New(N); // cria o novo nodo
N^.Obj := X; // X vai sempre para o nodo criado
P = L^.prox;
while ((X > P^.obj) // X nunca vai ser > o ltimo
do P = P^.prox;
N^.Prox := P^.prox;
P^.prox := N;
End;

Nada impede que um elemento inserido na lista tenha o valor HV. Entretanto, ao remover um nodo
da cadeia, devemos nos certificar de que ele no seja o sentinela. Para isto que serve o teste P^.prox
<> nil; qualquer nodo pode armazenar o valor de HV, mas somente o sentinela tem um ponteiro nulo!
function Remover(X: elem): boolean; // X: char
var N, P: ^nodo; // ou : LstOrd = mesmo tipo de L e N
begin
result := false;
P = L^.prox;
while (X > P^.obj) // ? (X > P^.prox^.obj) ?
do P = P^.prox;
if (P^.prox <> nil) and (X = P^.obj)
then begin // ? (P^.prox^.prox <> nil) e (X = P^.prox^.obj) ?
N := P^.prox;
P^.Prox := N^.prox;
Dispose(N);
Result := true;
end;
end;

Durante a operao de pesquisa, precisamos tambm ter certeza de que o nodo encontrado no o
nodo sentinela, j que ele no faz parte dos elementos da lista.

9.3 Encadeamento circular


Numa lista com encadeamento circular, ao invs do campo de ligao do ltimo nodo armazenar um
endereo nulo, ele armazena o endereo do primeiro nodo:

Como o endereo do primeiro nodo pode ser obtido atravs do campo de ligao do ltimo nodo, a
varivel ponteiro para uma lista circularmente encadeada guarda no o endereo do primeiro, mas sim
do ltimo nodo da cadeia. A grande vantagem em se usar encadeamento circular que podemos
acessar rapidamente tanto o primeiro quanto o ltimo nodo da lista. Na implementao no circular,
acessar o ltimo elemento requer a passagem por todos os elementos da cadeia, um a um, at atingir o
ltimo. Por exemplo, se resolvemos implementar filas com listas encadeadas, a operao de remoo
62

seria rpida (primeiro elemento), entretanto a operao de insero seria bastante lenta (ltimo
elemento). Veja a seguir, a implementao de filas usando listas circularmente encadeadas.
Type
elem: char; // define tipo elem baseado em char
Fila = ^nodo; // define um ponteiro para record nodo
Nodo = record // define estrutura nodo
Obj: elem;
Prox: Fila;
End;

A inicializao e teste de fila vazia so triviais na implementao com listas circulares. Note que no
h necessidade de teste de fila cheia, j que estamos usando alocao dinmica de memria:

procedure IniciaFila(var F: Fila);


begin
F := nil;
End;

function FilaVazia(F: Fila): boolean;


begin
if (F = nil)
then Result := true
else Result := false;
End;

Ao ser inserido na fila, um novo nodo dever sempre armazenar o endereo do primeiro elemento da
lista encadeada. Se a fila estava vazia antes da insero, o novo nodo ter que apontar para si mesmo.
procedure Inserir(var F: fila; X: elem); // X: char
var N: fila; // ou ^nodo: ponteiro para nodo
begin
New(N); // cria o novo nodo
N^.Obj := X; // X vai sempre para o nodo criado
if filaVazia(F)
then begin
F = N;
N^.prox := N;
end
else begin
N^.prox := F^.prox;
F^.prox := N;
F = N;
end;
end;

Na remoo, devemos atentar para o caso de a fila ter somente o elemento a ser removido, pois,
neste caso, a fila ficar vazia. Analise o cdigo apresentado e veja como o encadeamento circular torna
a implementao muito mais eficiente do que para uma lista encadeada comum.
function Retirar(F: Fila): boolean;
Var P: fila; // ou ^nodo
begin
if (not FilaVazia(F))
then begin
if (F = F^.prox) // fila s tem um elemento
then begin
retirar(F^.obj);
dispose(F);
F := nil
end
else begin // fila tem mais de um elemento
63

P := F^.prox;
F^.prox := P^.prox;
Retirar(P^.obj);
Dispose(P);
end;
result := true;
end
else result := false; // fila vazia
end;

9.4 Encadeamento Duplo


Numa lista com encadeamento duplo, cada nodo tem dois campos de ligao sendo que um deles
armazena o endereo do nodo predecessor e o outro armazena o endereo do sucessor, como mostrado
na figura a seguir.

Sendo cada nodo da forma (esq, obj, dir), dado um ponteiro P que armazena o endereo de um nodo
qualquer no meio da lista, vale a seguinte igualdade: P = P^.esq^.dir = P^.dir^.esq.
Na figura anterior, por exemplo, se partimos do ponteiro P e vamos para a esquerda e depois para a
direita, retornamos ao mesmo nodo de partida. Assim, esta igualdade caracteriza a principal
propriedade de uma lista duplamente encadeada, que a facilidade de retroceder ou avanar na cadeia,
a partir de qualquer nodo, com a mesma eficincia. Entretanto, a igualdade no se verifica se P estiver
apontando um nodo extremo da lista. Por exemplo, se P aponta o primeiro nodo da lista, ento no
possvel ir para a esquerda e depois voltar direita. Por este motivo, listas duplamente encadeadas
normalmente so implementadas de forma circular; neste caso, an precede a1 e a1 sucede an. A
existncia de um nodo sentinela garante a validade da igualdade mesmo estando a lista vazia e,
portanto, torna a estrutura ainda mais uniforme.

Ento em uma lista ordenada com a estrutura apresentada na figura anterior, podemos acessar os
seus elementos em ordem crescente ou decrescente. Se o encadeamento duplo tem a desvantagem de
gastar mais memria, por outro lado, tem a vantagem de tornar as operaes bsicas mais simples.
Sendo P um ponteiro para um nodo qualquer no meio de uma lista duplamente encadeada, e N um
ponteiro para um novo nodo a ser inserido antes de P, podemos usar as seguintes instrues:
N^.esq = P^.esq;
N^.dir = P;
P^.esq^.dir = N;
P^.esq = N;
Para remover o nodo apontado por P, fazemos:
P^.esq^.dir = P^.dir ;
P^.dir^.esq = P^.esq;
dispose (P);
64

Bibliografia
[Aze 96] Azeredo, Paulo A. . Mtodo de Classificao de dados e anlise de suas complexidades.
Editora Campus. Rio de Janeiro. 1996.
[Per 96] Pereira, Silvio Lago. Estrutura de dados Fundamentais: conceitos e aplicaes. Editora
rica. So Paulo. 1996
[Szw 94] Szwarcfiter, Jaime Luiz, Markenzon, Lilian. Estruturas de dados e seus algoritmos.
Editora Livros Tecnicos e Cientficos. Rio de Janeiro. 1994.
[Ten 95] Tenenbaum, Aaron M.. Estruturas de dados usando C. Editora Makron Books, So
Paulo.1995.
[Vel 83] Veloso, Paulo, Santos, Clesio dos, Azeredo, Paulo, Furtado, Antonio. Estrutura de Dados.
Editora Campus, Rio de Janeiro. 1998.
[Buc 02] Bucknall, Julian. Algoritmos e estruturas de dados com Delphi. Editora Berkeley, So
Paulo. 2002.
65

LISTA DE EXERCCIOS
1. Implementar uma pilha com um vetor de n elementos do tipo record (nome[30], idade, sexo e
altura) e as seguintes operaes:
1.1. inserir um elemento: no topo, no meio e no incio. Nas inseres no meio e no incio, andar
com a pilha para a direita: colocar esta operao em uma funo ou procedure separada.
Chamar uma funo para testar se pilha est cheia.
1.2. retirar um elemento: do topo, do meio, do incio. Nas retiradas do meio e do incio, andar com a
pilha para a esquerda: colocar esta operao em uma funo ou procedure separada. Chamar
uma funo para testar se pilha est vazia. Usar a pesquisa do item 1.9 para informar se
elemento est na pilha.
1.3. informar qual o tamanho da pilha,
1.4. gravar a pilha: do vetor para um arquivo,
1.5. ler a pilha: do arquivo para o vetor,
1.6. ordenar a pilha, usando o mtodo da bolha (bubble sort),
1.7. listar a pilha em um memo,
1.8. esvaziar a pilha,
1.9. com a pilha ordenada, programar uma pesquisa: dado um elemento x, informar a posio em
que se encontra na pilha (ou no est na pilha)
o incrementar uma pesquisa seqencial
o incrementar uma pesquisa binria.

2. Implementar uma pilha com uma lista encadeada com ns do tipo record (nome[30], idade, sexo e
altura) e as seguintes operaes:
2.1. inserir um elemento: no topo, no meio e no incio,
2.2. retirar um elemento: do topo, do meio, do incio,
2.3. informar qual o tamanho da pilha,
2.4. gravar a pilha: do vetor para um arquivo,
2.5. ler a pilha: do arquivo para o vetor,
2.6. ordenar a pilha, usando o mtodo da bolha (bubble sort),
2.7. listar a pilha em um memo,
2.8. esvaziar a pilha,
2.9. pesquisar um elemento x, informar a posio em que est (ou no est) na pilha.

3. Implementar uma fila no circular com um vetor de n elementos do tipo record (nome[30], idade,
sexo e altura) e as seguintes operaes:
3.1. inserir elemento no topo,
3.2. retirar um elemento do incio, sem andar com a fila,
3.3. retirar um elemento do meio ou incio, andando com a fila
3.4. informar qual o tamanho da fila e listar a fila,
3.5. pesquisar um elemento x, informar a posio em que est (ou no est) na fila.

4. Implementar uma fila circular com um vetor de n elementos do tipo record (nome[30], idade, sexo
e altura) e as seguintes operaes:
4.1. inserir elemento no topo,
4.2. retirar um elemento do incio, sem andar com a fila,
4.3. retirar um elemento do meio ou incio, andando com a fila
4.4. informar qual o tamanho da fila e listar a fila,
4.5. pesquisar um elemento x, informar a posio em que est (ou no est) na pilha.

5. Implementar uma fila com uma lista encadeada com ns do tipo record (nome[30], idade, sexo e
altura) e as seguintes operaes:
66

5.1. inserir elemento no topo,


5.2. inserir um elemento no incio,
5.3. retirar um elemento do incio,
5.4. retirar um elemento do topo,
5.5. informar qual o tamanho da fila e listar a fila.

6. Implementar uma lista duplamente encadeada com ns do tipo record (nome[30], idade, sexo e
altura) e as seguintes operaes:
6.1 inserir um elemento: no topo, no meio e no incio,
6.2 retirar um elemento: do topo, do meio, do incio: dar mensagem se no estiver na lista,
6.3 informar qual o tamanho da lista,
6.4 listar a lista num memo: do incio para o fim e do fim para o incio,
6.5 esvaziar a pilha,
6.6 programar uma pesquisa seqencial: dado um elemento x, informar a posio em que se
encontra na pilha (ou no est na pilha)

Anda mungkin juga menyukai