Você está na página 1de 168

Procura em vectores

2007/08
A.E.D.
F.S.B.

 Para procurar um elemento num vector vai-se


usar como exemplo um vector de inteiros
 Mas tudo o que se falará serve, com as devidas
adaptações, a todo o tipo de vectores
 Há duas considerações a fazer quando se
procura um elemento num vector:
 O vector não está ordenado
 O vector está ordenado

Procura em vectores
2007/08
A.E.D.
F.S.B.

 Supondo que o vector não está ordenado existe


apenas um tipo de procura:
 Procura linear
 Nesta procura o vector é percorrido, elemento a elemento,
desde o primeiro elemento até ao último
 Termina-se a procura quando ocorre uma de duas situações:
 o elemento é encontrado
 todo o vector foi pesquisado e não se encontrou o elemento
Procura em vectores : linear
2007/08
A.E.D.
F.S.B.

 O algoritmo de procura linear é, supondo que:


 x é o elemento a procurar
 vect é o vector onde procurar
 dim é a dimensão do vector
 O índice do primeiro elemento é 0 (zero)

i = 0
ENQUANTO i < dim E vect[ i ] != x FAZER
i = i +1
FIM

 Se i >= dim não encontrou


 Se i < dim é porque encontrou o elemento x na
posição i

Procura em vectores : linear


2007/08
A.E.D.
F.S.B.

 O algoritmo de procura linear pode ser


implementado pelo seguinte método que:
 Devolve o índice onde encontrou o elemento
 Devolve –1 (índice inválido para qualquer vector) se
não encontrou

int procuraLinear( int oVector[], int x ) {


int i = 0;
while( i < oVector.lenght && oVector[ i ] != x )
i++;

if( i >= oVector.length )


return –1;
return i;
}
Procura em vectores : linear
2007/08
A.E.D.
F.S.B.

 Exemplo de uso:

public static void main( String args[] ) {

int vect[] = {1, 2 ,3 ,5, 3, 10, 8, 12, 17, 4};


int res, x;
Keyboard keyboard = new Keyboard();

System.out.print(“numero a procurar: “);


keyboard.readInt( x );

res = procuraLinear( vect, x );

if( res == -1 )
System.out.println( x + “ não foi encontrado!” );
else
System.out.println( x + “ foi encontrado na posição ” + res );
}

Procura em vectores : linear


2007/08
A.E.D.
F.S.B.

 Os exemplos e o algoritmo apresentados


referem-se apenas a encontrar a PRIMEIRA
ocorrência de x no vector
 Fica como exercício alterar o algoritmo para
procurar a ÚLTIMA ocorrência de x
 Existe uma outra versão do algoritmo que evita
as duas comparações no ciclo while:
 Mais rápida (pode ser significativo para vectores
grandes)
 Envolve aumentar o vector de uma posição e colocar
o valor x nessa posição - assim a procura é sempre
até encontrar o valor de x
 Também fica como exercício ☺
Procura em vectores
2007/08
A.E.D.
F.S.B.

 Supondo que o vector está ordenado podem-se


aplicar as seguintes procuras:
 Procura linear (já descrita)
 Procura binária:
 Nesta pesquisa o vector é dividido em zonas a procurar
 A divisão pode ser feita de qualquer maneira, mas os
melhores resultados (em média) obtêm-se quando se divide o
vector (e as zonas seguintes) em duas partes
 Comparando o elemento a procurar (x) com o elemento no
intervalo escolhe-se a próxima zona a dividir:
 Se x for menor procurar na zona da esquerda
 Se x for maior procurar na zona da direita
 E assim sucessivamente até se encontrar o valor ou a zona
ser de apenas um elemento

Procura em vectores : binária


2007/08
A.E.D.
F.S.B.

 O algoritmo de procura linear é, supondo que:


 x é o elemento a procurar
 vect é o vector onde procurar
 dim é a Dimensão do vector
 O índice do primeiro elemento é 0 (zero)
encontrado = FALSO
esq = 0
dir = dim-1
ENQUANTO esq <= dir E NÃO encontrado FAZER
meio = (esq + dir) / 2
SE vect[meio] = x ENTAO encontrado = VERDADE
SENAO SE vect[meio] < x ENTAO esq = meio+1
SENAO dir = meio-1
FIM

 Se encontrado = FALSO não encontrou


 Se encontrado = VERDADE é porque encontrou o
elemento x na posição Meio
Procura em vectores : binária
2007/08
A.E.D.
F.S.B.

 O algoritmo de procura binária pode ser


implementado pelo seguinte método que:
 Devolve o índice onde encontrou o elemento
 Devolve –1 se não encontrou
int procuraBinaria( int oVector[], int x ) {
boolean encontrou = false;
int esq = 0;
int dir = oVector.length-1;
int meio;
while( esq <= dir && !encontrou ) {
meio = (esq + dir) / 2;
if( oVector[ meio ] == x ) encontrou = true;
else if( oVector[ meio ] < x ) esq = meio + 1;
else dir = meio - 1;
}

if( !encontrou )
return –1;
return meio;
}

Procura em vectores : binária


2007/08
A.E.D.
F.S.B.

 Exemplo de procura do nº 10

2 3 5 7 9 10 11 12 17 19 21 28 30 33 38 42 45 55
esq dir
1ª iteração meio
2 3 5 7 9 10 11 12 17 19 21 28 30 33 38 42 45 55
esq dir
2ª iteração meio
2 3 5 7 9 10 11 12
esq dir
3ª iteração meio
9 10 11 12

Encontrou
Procura em vectores : binária
2007/08
A.E.D.
F.S.B.

 Exemplo de procura do nº 4

2 3 5 7 9 10 11 12 17 19 21 28 30 33 38 42 45 55
esq dir
1ª iteração meio
2 3 5 7 9 10 11 12 17 19 21 28 30 33 38 42 45 55
esq dir
2ª iteração meio
2 3 5 7 9 10 11 12
esq dir
3ª iteração meio
2 3 5
esq
dir
4ª iteração meio
5
dir esq
5ª iteração Não encontrou

Procura em vectores : Binária


2007/08
A.E.D.
F.S.B.

 Os exemplos e o algoritmo apresentados


referem-se apenas a encontrar UMA ocorrência
de x no vector, não sendo a primeira nem a
última
 Fica como exercício alterar o algoritmo para
procurar a primeira ocorrência de x
 E também como exercício procurar a última
Procura em vectores
2007/08
A.E.D.
F.S.B.

 Comparando os dois algoritmos de procura:


 A binária é mais rápida (em média)
 A binária só pode ser utilizada em vectores ordenados
 A linear pode ser usada em qualquer vector
 A linear encontra a primeira ocorrência de x enquanto
a binária apenas encontra uma ocorrência de x

Ordenação de vectores
2007/08
A.E.D.
F.S.B.

 Ter a informação ordenada é muito mais


vantajoso:
 Imagine-se a procurar um nome numa lista telefónica
se esta não estivesse ordenada alfabeticamente
2007/08
A.E.D.
F.S.B. Ordenação de vectores : inserção
 Ordenação por inserção
 Neste caso percorre-se o vector e vai-se colocando
cada elemento na sua posição correcta
 considerando-se apenas os que estão à sua esquerda
 Ao inserir um elemento os maiores são deslocados
para a direita

2007/08
A.E.D.
F.S.B. Ordenação de vectores : inserção
 Algoritmo de ordenação por inserção directa,
assumindo:
 vect é o vector a ordenar
 dim é a dimensão do vector
 O índice do primeiro elemento é 0 (zero)

PARA i = 1 ATÉ dim –1 FAZER


x = vect[i]
inserir x na posição correcta (k) entre vect[0] e vect[i]
FIM

 A posição correcta k é tal que:


 x >= v[ k-1 ]
 x <= v[ k+1 ]
2007/08
A.E.D.
F.S.B. Ordenação de vectores : inserção
 Exemplificação do algoritmo

1ª iteração 2ª iteração 3ª iteração 4ª iteração 5ª iteração

9 9 3 3 3 3 3 3 2 2 2
3 3 9 9 5 5 5 5 3 3 3
5 5 5 5 9 9 9 9 5 5 5
11 11 11 11 11 11 11 11 9 9 7
2 2 2 2 2 2 2 2 11 11 9
7 7 7 7 7 7 7 7 7 7 11

2007/08
A.E.D.
F.S.B. Ordenação de vectores : inserção
 O algoritmo de ordenação por inserção pode ser
implementado pelo seguinte método:

void ordenaInsercaoDirecta( int oVector[] ) {

for( int i = 1; i < oVector.lenght; i++ ) {


int x = oVector[ i ];
int j = i;
while( j > 0 && oVector[ j-1 ] > x )
{
oVector[ j ] = oVector[ j-1 ];
j--;
}
oVector[ j ] = x;
}
}

Porque não começa o for em i = 0?


2007/08
A.E.D.
F.S.B. Ordenação de vectores : inserção
 Exemplo de uso

public static void main( String args[] ) {

int v[13] = { 2, 4, 65, 54, 34, 23, 90, 12, 6, 78, 87, 26, 42 };

ordenaInsercaoDirecta( v );

for( int i=0; i < v.length; i++)


System.out.println("v[" + i + "] = " + v[i] );

2007/08
A.E.D.
F.S.B. Ordenação de vectores : selecção
 Ordenação por selecção
 Neste caso percorre-se o vector, procurando o menor
elemento e coloca-se este na primeira posição
 O elemento na primeira posição é colocado na
posição ocupada anteriormente pelo menor
 Repete-se para os restantes elementos
2007/08
A.E.D.
F.S.B. Ordenação de vectores : selecção
 Algoritmo de ordenação por selecção
assumindo:
 vect é o vector a ordenar
 dim é a dimensão do vector
 O índice do primeiro elemento é 0 (zero)

PARA i = 0 ATÉ dim - 1 FAZER


k = índice do menor elemento de vect[i] a vect[ dim - 1 ]
TROCAR vect[ i ] com vect[ k ]
FIM

2007/08
A.E.D.
F.S.B. Ordenação de vectores : selecção
 Exemplificação do algoritmo

1ª iteração 2ª iteração 3ª iteração 4ª iteração 5ª iteração

9 9 2 2 2 2 2 2 2 2 2
menor

3 3 3 3 3 3 3 3 3 3 3
menor

5 5 5 5 5 5 5 5 5 5 5
11 11 11 11 11 11 11 11 7 7 7
menor

menor

2 2 9 9 9 9 9 9 9 9 9
menor

7 7 7 7 7 7 7 7 11 11 11
2007/08
A.E.D.
F.S.B. Ordenação de vectores : selecção
 O algoritmo de ordenação por selecção pode ser
implementado pela seguinte função:

void ordenaSeleccao( int oVector[] ) {

for( int i = 0; i < oVector.length - 1; i++ ) {


int min = i;
for( int j = i + 1; j < oVector.length; j++ )
if( oVector[ min ] > oVector[ j ] )
min = j;
int aux = oVector[ min ];
oVector[ min ] = oVector[ i ];
oVector[ i ] = aux;
}
}

2007/08
A.E.D.
F.S.B. Ordenação de vectores : permutação
 Ordenação por permutação
 Neste caso percorre-se o vector, do último para o
primeiro, comparando elementos consecutivos dois a
dois e trocando-os se estiverem na ordem inversa
 Após a primeira iteração o menor valor estará na
primeira posição, pelo que as repetições seguintes,
deverão ter em conta apenas os restantes elementos
e assim sucessivamente
 Chama-se a este algoritmo Bubblesort
2007/08
A.E.D.
F.S.B. Ordenação de vectores : permutação
 Algoritmo da ordenação por permutação
(bubblesort) assumindo:
 vect é o vector a ordenar
 dim é a dimensão do vector
 O índice do primeiro elemento é 0 (zero)

PARA i = 1 ATÉ dim - 1 FAZER


PARA j = dim - 1 ATÉ i FAZER
SE vect[ j – 1 ] > vect[ j ] FAZER
TROCAR vect[ i ] COM vect[ j ]
FIM

2007/08
A.E.D.
F.S.B. Ordenação de vectores : permutação
 Pode-se fazer uma melhoria óbvia ao algoritmo:
 Se não houve permutações é porque o vector já está
ordenado

ordenado = FALSO
i = 0
ENQUANTO i < dim-1 E NÃO ordenado FAZER
ordenado = VERDADE
PARA j = dim - 1 ATÉ i + 1 FAZER
SE vect[ j - 1 ] > vect[ j ] FAZER
TROCAR vect[ i ] COM vect[ j ]
ordenado = FALSO
FIM
i = i +1
FIM
2007/08
A.E.D.
F.S.B. Ordenação de vectores : permutação
 Exemplificação do algoritmo
1ª iteração 2ª iteração

- não há mais comparações a fazer


9 9 9 9 9 9 2 2 2 2 2 2

Terminava pelos dois motivos:


3 3 3 3 3 2 9 9 9 9 9 3
5 5 5 5 2 3 3 3 3 3 3 9
11 11 11 2 5 5 5 5 5 5 5 5

- não ouve permutações


2 2 2 11 11 11 7 7 7 7 7 7
7 7 7 7 7 7 11 11 11 11 11 11

3ª iteração 4ª iteração 5ª iteração

2 2 2 2 2 2 2 2 2
3 3 3 3 3 3 3 3 3
9 9 9 5 5 5 5 5 5
5 5 5 9 9 9 7 7 7
7 7 7 7 7 7 9 9 9
11 11 11 11 11 11 11 11 11

2007/08
A.E.D.
F.S.B. Ordenação de vectores : permutação
 O algoritmo de ordenação Bubblesort pode ser
implementado pelo seguinte método:
void bubbleSort( int oVector[] ) {
boolean ordenado = false;
int i = 0;

while( i < oVector.length - 1 && !ordenado ) {


ordenado = true;
for( int j = oVector.lenght - 1; j > i; j-- ) {
if( oVector[ j – 1 ] > oVector[ j ] ) {
int aux = oVector[ j – 1 ];
oVector[ j – 1 ] = oVector[ j ];
oVector[ j ] = aux;
ordenado = false;
}
}
i++;
}
}
Ordenação de vectores
2007/08
A.E.D.
F.S.B.

 As técnicas usadas anteriormente usam todo o


vector e vão percorrendo todo o vector fazendo
comparações com elementos próximos
 Os algoritmos seguintes tomam apenas parte do
vector e fazem comparações entre elementos
distantes
 Foi provado que comparar elementos mais afastados
leva a melhores resultados
 Alguns, os mais rápidos, usam recursividade

Ordenação de vectores : ShellSort


2007/08
A.E.D.
F.S.B.

 Algoritmo de ShellSort
 Inventado por Donald Shell
 Neste caso percorre-se o vector ordenando apenas
elementos que se encontram afastados
 Para ordenar estes usa-se o algoritmo de inserção
 À distância entre elementos chama-se incremento
 Depois destes estarem ordenados ordenam-se os
mais próximos até se ordenarem os elementos
adjacentes
 Ou seja, vai-se diminuindo o incremento até este ser 1
 Como a distância entre elementos se aproxima de 1
este algoritmo também se costuma chamar
 Ordenação por incrementos decrescentes
Ordenação de vectores : ShellSort
2007/08
A.E.D.
F.S.B.

 Algoritmo de ordenação ShellSort, assumindo:


 vect é o vector a ordenar
 dim é a dimensão do vector
 O índice do primeiro elemento é 0 (zero)

PARA h = IncrementoInicial ATÉ 1 FAZER


PARA i = h ATÉ dim -1 FAZER
inserir x na posição correcta (k) entre
vect[0] e vect[i] com intervalos de h
FIM

 Só fica a faltar definir como se calcula o 1º incremento

Ordenação de vectores : ShellSort


2007/08
A.E.D.
F.S.B.

 Quais são os incrementos e como se calculam?


 A escolha dos incrementos deve ser feita com
cuidado pois incrementos diferentes podem levar
a soluções ineficientes
 Na solução original o incremento inicial era metade do
tamanho do vector
 Os seguintes seriam metade do anterior
 Foram depois sugeridas várias sequências de
incrementos que melhoravam substancialmente os
resultados obtidos com a original
Ordenação de vectores : ShellSort
2007/08
A.E.D.
F.S.B.

 Exemplificação do algoritmo com incrementos


originais
1ª iteração 2ª iteração 3ª iteração
h=4 h=2 h=1
9 9 2 2 2 2 2
3 3 3 3 3 3 3
5 5 5 5 5 5 5
11 11 11 11 11 11 6
2 2 8 8 8 8 7
7 7 7 7 7 7 8
12 12 12 12 9 12 9
6 6 6 6 6 6 11
8 8 9 9 12 9 12

Ordenação de vectores : ShellSort


2007/08
A.E.D.
F.S.B.

 Em que é que este algoritmo é melhor que o de


inserção directa?
 Afinal o algoritmo de inserção directa não é usado
para ordenar as subsquências?
 Com uma correcta escolha de índices este
algoritmo faz muito menos trocas
 E as trocas são o que mais influência o desempenho
 Quando se chega ao incremento 1 quase todos os
elementos estão no lugar
Ordenação de vectores : ShellSort
2007/08
A.E.D.
F.S.B.

 O algoritmo ShellSort pode ser implementado


pelo seguinte método, usando os índices
originais:

void ordenaShellOriginal( int v[] ) {

for( int h = v.length / 2; h >= 1; h = h/2 ) {


for( int i = h; i < v.length; i+= h ) {
int x = v[ i ];
int j = i;
while( j > 0 && v[ j-h ] > x ){
v[ j ] = v[ j-h ];
j -= h;
}
v[ j ] = x;
}
}

Ordenação de vectores : ShellSort


2007/08
A.E.D.
F.S.B.

 Outra sequência que se pode usar é uma em


que:
 hi = 3 * hi-1 + 1
 (1, 4, 13, 40, …)
 Para esta sequência tem-se de calcular o primeiro
incremento
 A melhor forma é calcular o incremento i com base no
anterior
 Começa-se em 1 e vai-se aplicando a fórmula até se

atingir o tamanho do vector


 Outra hipótese era aplicar logaritmos para determinar logo o
incremento inicial
 Não o vamos fazer ☺

 Esta é, até agora, a sequência de incrementos que dá


melhores resultados
Ordenação de vectores : ShellSort
2007/08
A.E.D.
F.S.B.

 O algoritmo ShellSort pode ser implementado


pelo seguinte método, usando a sequência
anterior:
void ordenaShellMelhor( int v[] ) {
int h;

// primeiro calcular o maior incremento (h) possível


for( h = 1; h < v.length; h = 3*h + 1);

do{
h /= 3; // actualizar o h para esta iteração
for( int i = h; i < v.length; i+= h ) {
int x = v[ i ];
int j = i;
while( j > 0 && v[ j-h ] > x ){
v[ j ] = v[ j-h ];
j -= h;
}
v[ j ] = x;
}
} while( h > 1 );
}

2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 Algoritmo de MergeSort
 Algoritmo recursivo
 Divide o vector a meio, formando dois vectores
 ordena-os separadamente
 reagrupa-os num novo vector
 Para ordenar os vectores resultantes aplica o mesmo
raciocínio
 Obriga a criar um novo vector por causa do
reagrupamento
2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 Exemplificação do algoritmo
 Divisão em sub-vectores

1ª chamada 2ª chamada 3ª chamada 4ª chamada


esq = 0 9
9 9 esq = 0 9 9 esq = 0 9 9 centro = 0
9 9 esq = 0 3 3 3 3 centro = 1 3 3 dir = 1
3 3 3
5 5 centro = 2 5 5 dir = 2
5 5 11 11 5
11 11 2 2 dir = 4 11
2 2 centro = 4 5ª chamada
2
7 7 esq = 0
9 dir = 0
12 12 7
6 6 12
8 8 dir = 8
Quando esq = dir,
6
8
termina a parte recursiva,
falta recuar nas chamadas

2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 Neste ponto coloca-se outro problema:
 Como se juntam dois vectores, já ordenados de modo
a ficarem um só vector?
 O algoritmo é simples:
 Verifica-se qual dos dois vectores tem o menor
número na posição inicial
 Coloca-se esse elemento na primeira posição de um
novo vector
 Passa-se ao elemento seguinte no vector de onde se
retirou o elemento
 No vector cujo elemento não foi retirado mantém-se na
mesma posição
 Repete-se o algoritmo para os restantes elementos
dos vectores, actualizando sempre o vector que foi
alterado
2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 Exemplificação do algoritmo
 Caso de junção de vectores

1ª iteração 2ª iteração 3ª iteração 4ª iteração 5ª iteração


1 i1 = 0 1 1 i=0 1 1 1 1 1 1 1 1
3 3 i1 = 1 3 2 i = 1 i1 = 1 3 2 3 2 3 2
4 4 4 4 3 i = 2 i1 = 2 4 3 4 3
6 6 6 6 6 4 i = 3 i1 = 3 6 4
9 9 9 9 9 9 5 i=4
10 10 10 10 10 10

2 i2 = 0 2 i2 = 0 2 2 2 2
5 5 5 i2 = 1 5 i2 = 1 5 i2 = 1 5
7 7 7 7 7 7
8 8 8 8 8 8

2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 Exemplificação do algoritmo
 Caso de junção de vectores

5ª iteração 6ª iteração 7ª iteração 8ª iteração 9ª e 10ª iterações


1 1 1 1 1 1 1 1 1 1 1
3 3 2 3 2 3 2 3 2 3 2
4 4 3 4 3 4 3 4 3 4 3
6 i1 = 3 6 4 i1 = 3 6 4 6 4 6 4 6 4
9 9 5 i=4 9 5 i1 = 4 9 5 i1 = 4 9 5 i1 = 4 9 5
10 10 10 6 i=5 10 6 10 6 10 6
7 i=6 7 7
2 2 2 2 2 8 i=7 2 8
5 i2 = 1 5 5 5 5 5 9 i=8
7 7 i2 = 2 7 i2 = 2 7 7 7 10 i = 9
8 8 8 8 i2 = 3 8 8
i2 = 4
Como o 2º vector já acabou não se compara mais,
é só copiar os elementos do outro vector
2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 O algoritmo para fundir os dois vectores vai ser o
apresentado
 Vai ser alterado porque os dois vectores
originais são o mesmo vector, mas partido em
diferentes partes
 O que vai variar são os índices onde se começa a
copiar
 No exemplo anterior basta por i2 a começar em 6
para que os dois vectores sejam afinal o mesmo

2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 Exemplificação do algoritmo
 Reagrupar os subvectores

1ª chamada 2ª chamada 3ª chamada 4ª chamada


esq = 0 9
9 9 esq = 0 9 9 esq = 0 9 9 centro = 0
9 9 esq = 0 3 3 3 3 centro = 1 3 3 dir = 1
3 3 3
5 5 centro = 2 5 5 dir = 2
5 5 11 11 5
11 11 2 2 dir = 4 11
2 2 centro = 4 5ª chamada
2
7 7 esq = 0
9 dir = 0
12 12 7
6 6 12
8 8 dir = 8
Quando esq = dir,
6
8
termina a parte recursiva,
falta recuar nas chamadas
2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 Exemplificação do algoritmo
 Reagrupar os subvectores

1ª chamada 2ª chamada 3ª chamada 4ª chamada


esq = 0 9 3
9 9 esq = 0 9 9 esq = 0 9 9 centro = 0
9 9 esq = 0 3 3 3 3 centro = 1 3 3 dir = 1 9
3 3 3
5 5 centro = 2 5 5 dir = 2
5 5 11 11 5
11 11 2 2 dir = 4 novo vector
11
2 2 centro = 4
7 7
2 Para a recursividade funcionar deve-se
12 12 7 copiar o novo vector para o antigo
6 6 12
8 8 dir = 8 6
8

2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 Exemplificação do algoritmo
 Reagrupar os subvectores

1ª chamada 2ª chamada 3ª chamada 4ª chamada


esq = 0 9 3
9 9 esq = 0 9 9 esq = 0 3 3 centro = 0
9 9 esq = 0 3 3 3 3 centro = 1 9 9 dir = 1 9
3 3 3
5 5 centro = 2 5 5 dir = 2
5 5 11 11 5
11 11 2 2 dir = 4 novo vector
11
2 2 centro = 4 é copiado
2
7 7
12 12 7
6 6 12
8 8 dir = 8 6
8
2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 Exemplificação do algoritmo
 Reagrupar os subvectores

1ª chamada 2ª chamada 3ª chamada


9 9 esq = 0 9 9 esq = 0 3 3
9 9 esq = 0 3 3 3 3 centro = 1 9 5
3 3 5 5 centro = 2 5 5 dir = 2 9
5 5 11 11 5
11 11 2 2 dir = 4 11
2 2 centro = 4
2
7 7
12 12 7
6 6 12
8 8 dir = 8 6
8

2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 Exemplificação do algoritmo
 Reagrupar os subvectores

1ª chamada 2ª chamada 3ª chamada


9 9 esq = 0 3 3 esq = 0 3 3
9 9 esq = 0 3 3 5 5 centro = 1 9 5
3 3 5 5 centro = 2 9 9 dir = 2 9
5 5 11 11 5
11 11 2 2 dir = 4 2
2 2 centro = 4
11
7 7
12 12 7
6 6 12 Este vector também foi alterado
8 8 dir = 8 6
8
2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 Exemplificação do algoritmo
 Reagrupar os subvectores

1ª chamada 2ª chamada
9 9 esq = 0 3 2
9 9 esq = 0 3 3 5 3
3 3 5 5 centro = 2 9 5
5 5 11 11 9
11 11 2 2 dir = 4 11
2
2 2 centro = 4
11
7 7
12 12 7
6 6 12
8 8 dir = 8 6
8

2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 Exemplificação do algoritmo
 Reagrupar os subvectores

1ª chamada 2ª chamada
2 2 esq = 0 3 2
9 9 esq = 0 3 3 5 3
3 3 5 5 centro = 2 9 5
5 5 9 9 9
11 11 11 11 dir = 4 11
2
2 2 centro = 4
11
7 7
12 12 6
6 6 7
8 8 dir = 8 8
12
Este vector também foi alterado
2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 Exemplificação do algoritmo
 Reagrupar os subvectores

1ª chamada
2
9 9 esq = 0 3 2
3 3 5 3
5 5 9 5
11 11 11 6
2 2 centro = 4 7
7 7 8
12 12 6 9
6 6 7 11
8 8 dir = 8 8 12
12

2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 Exemplificação do algoritmo
 Reagrupar os subvectores

1ª chamada
2
2 2 esq = 0 3 2
3 3 5 3
5 5 9 5
6 6 11 6
7 7 centro = 4 7
8 8 8
9 9 6 9
11 11 7 11
12 12 dir = 8 8 12
12
2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 O algoritmo MergeSort para ser implementado
exigue vários métodos:
 O método inicial cria o vector novo e começa a
chamada recursiva
 O método que é recursivo e vai partindo os vectores a
meio
 O método que faz o reagrupamento

2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 O método inicial cria o vector novo e começa a
chamada recursiva, pode ser assim
implementado

void ordenaMergeSort( int v[] ) {

// criar o vector auxiliar para o reagrupamento


int vAux[] = new int[ v.length ];

// dividir em dois e começar a recursão


// indicando onde começa e acaba o vector a ser ordenado
mergeSortRec( v, vAux, 0, v.length-1);
}
2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 O método que é recursivo e vai partindo os
vectores em 2, pode ser assim implementado

void mergeSortRec( int v[], int vAux[], int esq, int dir ) {

if( esq >= dir ) // se só tem um elemento não faz nada


return;

// senão divide em duas partes e ordena essas partes


int centro = (esq + dir) / 2;
mergeSortRec( v, vAux, esq, centro ); // 1ª metade
mergeSortRec( v, vAux, centro+1, dir ); // 2ª metade

// as partes já estão ordenadas, reagrupá-las


reagrupar( v, vAux, esq, centro+1, dir);
}

2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 O método que faz o reagrupamento, pode ser
assim implementado
void reagrupar( int v[], int vAux[], int i1, int i2, int fim2 ) {
// a primeira parte do vector vai de i1 a i2-1
int fim1 = i2-1;
// e a segunda de i2 a fim

// guardar o início pois vai ser preciso para a cópia final


int inicio = i1;

// o índice do vector auxiliar vai começar em i1


int iAux = i1;

// enquanto houver elementos nos dois vectores


// copiar o menor para vAux e incrementar o i respectivo
while( i1 <= fim1 && i2 <= fim2 ){
if( v[ i1 ] < v[ i2 ] ) {
vAux[iAux] = v[ i1 ];
i1++;
}
else {
vAux[iAux] = v[ i2 ];
i2++;
}
iAux++;
}

// … continua no seguinte
2007/08
A.E.D.
F.S.B. Ordenação de vectores : MergeSort
 O método que faz o reagrupamento, pode ser
assim implementado

// … continuação do anterior

for( ; i1 <= fim1; i1++, iAux++ ) // o 2º vector acabou


vAux[ iAux ] = v[ i1 ];
for( ; i2 <= fim2; i2++, iAux++ ) // o 1º vector acabou
vAux[ iAux ] = v[ i2 ];

// copiar novamente para origem


for( int i = inicio; i <= fim2; i++ )
v[i] = vAux[i];
}

2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 Algoritmo de QuickSort
 Algoritmo recursivo
 Mais rápido algoritmo de ordenação existente
 Primeiro é escolhido um valor (pivot)
 Divide o vector em duas partes
 A primeira parte com elementos menores que o pivot
 A segunda parte com elementos maiores que o pivot
 O pivot é colocado no meio das duas partes
 Para ordenar as partes dai resultantes aplica-se o
mesmo raciocínio
2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 Exemplificação do algoritmo
 Usando um pivot completamente arbitrário!

1ª chamada 2ª chamada 3ª chamada


<2
9 9 6 6 2 2 2
pivot = 2
3 3 3 <7 3 3 <5 3 3 >2
pivot = 5
5 5 5 5 5
11 11 2 2 6 >5
2 2 7
7 7 pivot = 7 12
12 12 9 >7
Falta regredir nas chamadas
6 6 8
8 8 11

Reparar que pivot já está ordenado

2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 Exemplificação do algoritmo
 Usando um pivot completamente arbitrário!

1ª chamada 2ª chamada 3ª chamada

9 9 6 6 2 2 2
pivot = 2
3 3 3 <7 3 3 <5 3 3
pivot = 5
5 5 5 5 5
11 11 2 2 6 >5
2 2 7
7 7 pivot = 7 12
12 12 9 >7
6 6 8
8 8 11
2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 Exemplificação do algoritmo
 Usando um pivot completamente arbitrário!

1ª chamada 2ª chamada 3ª chamada

9 9 6 6 2 2
3 3 3 <7 3 3 3
pivot = 5
5 5 5 5 5
11 11 2 2 6
2 2 7
7 7 pivot = 7 12 Vindo de outra chamada
12 12 9 >7
6 6 8
8 8 11

2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 Exemplificação do algoritmo
 Usando um pivot completamente arbitrário!

1ª chamada 2ª chamada

9 9 6 2 2
3 3 3 <7 3 3
pivot = 5
5 5 5 5 5
11 11 2 6 6
2 2 7
7 7 pivot = 7 12
12 12 9 >7
6 6 8
8 8 11
2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 Exemplificação do algoritmo
 Usando um pivot completamente arbitrário!

1ª chamada 2ª chamada

9 9 2 2
3 3 3 3
5 5 5 5
11 11 6 6
2 2 7
7 7 pivot = 7 8
12 12 9
6 6 11
8 8 12
Vindo de outra chamada

2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 Exemplificação do algoritmo
 Usando um pivot completamente arbitrário!

1ª chamada

2 2 2
3 3 3
5 5 5
6 6 6
7 7 7
8 8 pivot = 7 8
9 9 9
11 11 11
12 12 12
2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 Visto assim o QuickSort até é fácil de
compreender, mas há pontos ainda em aberto:
 Como se escolhe o pivot?
 Como se coloca o pivot no centro do vector?
 Como se divide o vector em maiores e menores?

2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 Escolha do pivot:
 Não se deve escolher o primeiro elemento
 Num vector já ordenado isso iria dividir o vector em dois
pedaços: um com um elemento (o pivot) e outro com o resto
dos elementos todos…
 Não se deve escolher o último
 Pelas mesmas razões
 Um muito utilizado é usar a mediana de três valores
 Os valores a utilizar são o primeiro, o último e o central
 Escolhe-se aquele que for o valor do meio
 Outra técnica é escolher um aleatoriamente
 Requer um gerador de números aleatórios
2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 Colocação do pivot no centro do vector:
 Normalmente coloca-se no final do vector
 Só depois da divisão em maiores e menores é que se
recoloca no meio
 Para isso troca-se o pivot com o penúltimo elemento do
vector

2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 Escolha do pivot:
 Pode ser feita com este método

int pivotMediana( int v[], int ini, int fim ) {

int centro = (ini + fim ) /2;

if( v[ini] > v[centro] ) // já se ordenam os 3 elementos


troca( v, ini, centro );
if( v[ini] > v[fim] )
troca( v, ini, fim );
if( v[centro] > v[fim] )
troca( v, centro, fim );

// valor mediano fica na posição central


// para preparar o vector deve-se trocar com penúltimo
// (lembrar que o último é maior que o do centro)
troca( v, centro, fim-1);

return v[ fim - 1 ]; // retorna o pivot


}
2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 O método anterior usou o método troca que
pode ser definido assim

void troca( int v[], int i, int j ) {


int aux = v[ i ];
v[ i ] = v[ j ];
v[ j ] = aux;
}

2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 Como se divide o vector em maiores e menores?
 Existem várias técnicas
 Uma muito usada é ter dois índices a percorrer o
vector
 Um da esquerda para a direita
 Outro da direita para a esquerda
 Quando o da esquerda detecta um maior que o pivot
pára
 Quando o da direita detecta um menor que o pivot
pára
 Quando ambos param trocam os seus valores
 Quando ambos se encontram é porque o vector já
está dividido
2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 Exemplificação do algoritmo de divisão em maiores e menores
 Usando um pivot mediana de 3

v[ini] > v[centro] trocam pivot está no centro Vector pronto para dividir
9 ini = 0 9 2 2 2 2
3 3 3 3 3 3
5 5 5 5 5 5
11 11 11 11 11 11
2 centro = 4 2 9 8 8 6
7 7 7 7 7 7
12 12 12 12 12 12
6 6 6 6 6 8
8 fim = 8 8 8 9 9 9

v[centro] > v[fim] trocam Trocar pivot com penúltimo


Reparar que o 9 já está
no lado dos maiores

2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 Exemplificação do algoritmo de divisão em maiores e menores
 Usando um pivot mediana de 3

Vector pronto deslocar primeiro i


para dividir depois deslocar j
2 i = ini = 0 2 i = 0 já se sabe que tem um valor menor, avança logo
3 3 i = 1 v[ i ] < pivot i segue
5 5 i = 2 v[ i ] < pivot i segue
11 11 i = 3 v[ i ] >= pivot i pára
6 6 Como i < j trocam-se os valores
7 7 j = 5 v[ j ] <= pivot j pára
12 12 j = 6 v[ j ] > pivot j avança
8 j = fim – 1 = 7 8 j = 7 já se sabe que é o pivot avança logo
9 9
2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 Exemplificação do algoritmo de divisão em maiores e menores
 Usando um pivot mediana de 3

Vector pronto recomeça i Trocar pivot com


para dividir posição em i Vector dividido
pára a divisão
2 i = ini = 0 2 2 2
3 3 3 3
5 5 5 5
11 7 7 7
6 6 i = 4 v[ i ] < pivot i segue 6 6
7 11 i = j = 5 a divisão está feita 11 8
12 12 12 12
8 j = fim – 1 = 7 8 8 11
9 9 9 9

2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 A divisão em maiores e menores pode ser feita
com este método
void divideMaioresMenores( int v[], int ini, int fim, int pivot ) {

int i = ini;
int j = fim -1;
while( i < j ) {
while( v[ ++i ] < pivot );
while( v[ --j ] > pivot );
if( i < j )
troca( v, i, j );
}

// recolocar o pivot no sitio


troca( v, i, fim - 1 );
}
2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 Últimas considerações
 Este algoritmo é muito sensível às várias escolhas
que se tem de fazer
 Pivot
 Método de divisão
 As escolhas erradas podem ditar uma grande
penalização na eficiência
 Como se viu no caso da escolha do pivot como sendo o
primeiro elemento do vector
 Para vectores pequenos é mais lento que o de
inserção
 Para valores de 5 a 20 elementos é melhor o de inserção
directa
 Muitas implementações do QuickSort colocam um limite

abaixo do qual aplicam o de inserção

2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 Tal como no MergeSort o algoritmo exige, pelo
menos, dois métodos:
 O que prepara a chamada recursiva
 O que faz as chamadas recursivas

void ordenaQuickSort( int v[] ) {

quickSortRec( v, 0, v.length -1 );

}
2007/08
A.E.D.
F.S.B. Ordenação de vectores : QuickSort
 O que faz as chamadas recursivas

void quickSortRec( int v[], int ini, int fim ) {


// Se tamanho do vector < limite de eficiência
// faz a ordenação por inserção
if( fim - ini < TAMANHO_EFICIENCIA ) {
ordenaInsercao( v, ini, fim );
return;
}

// determinar o pivot
int pivot = pivotMediana( v, ini, fim );

// dividir em menores e maiores


divideMaioresMenores( v, ini, fim, pivot );

quickSortRec( v, ini, i-1 ); // ordenar os menores


quickSortRec( v, i+1, fim ); // ordenar os maiores
}

2007/08
A.E.D.
F.S.B. Ordenação de vectores : BucketSort
 Mais rápido ainda que o QuickSort!
 Mas o QuickSort não era o mais rápido?
 Não é genérico!
 Só serve para valores numéricos inteiros positivos
 Embora com adaptações sirva para negativos
 O QuickSort serve para tudo, logo é o mais rápido dos
genéricos
 Temos de ter a certeza que os números nunca
são maiores que um dado valor
2007/08
A.E.D.
F.S.B. Ordenação de vectores : BucketSort
 Cria-se um vector com o tamanho do maior
número presente no vector a ordenar, chamado
vector de contagem
 Inicializa-se cada posição do vector de
contagem a zero
 Percorre-se o vector a ordenar
 Por cada elemento do vector incrementa-se no vector
de contagem a posição correspondente ao seu valor
 Exemplo: o número 5 iria fazer incrementar de 1 a posição 5
do vector de contagem
 No final é só alterar o vector original de acordo
com a contagem feita no vector de contagem

2007/08
A.E.D.
F.S.B. Ordenação de vectores : BuckerSort
 Exemplificação do algoritmo
 Assume-se que o valor mais alto que se pode ordenar é o 14
Vector de contagem,
com 15 posições
0
0
2 1 Ocorrência de 2 incrementa posição 2 do vector
3 1 Ocorrência de 3 incrementa posição 3 do vector
5 0
5 2 2 ocorrências de 5 incrementa duas vezes a posição 5 do vector
12 0
7 1 Ocorrência de 7 incrementa posição 7 do vector
12 1 Ocorrência de 8 incrementa posição 8 do vector
8 1 Ocorrência de 9 incrementa posição 8 do vector
9 0
2 2 ocorrências de 12 incrementa duas vezes a posição 12 do vector
0 Vector de contagem completo
0 Falta repor o vector de origem
0
2007/08
A.E.D.
F.S.B. Ordenação de vectores : BuckerSort
 Exemplificação do algoritmo
 Assume-se que o valor mais alto que se pode ordenar é o 14
Vector de contagem,
com 15 posições
0 Não há zeros nem uns no vector original
0
2 1 Copia 1 vez o 2 para o vector original
3 1 Copia 1 vez o 3 para o vector original
5 0
5 2 Copia 2 vezes o 5 para o vector original
7 0
8 1 Copia 1 vez o 7 para o vector original
9 1 Copia 1 vez o 8 para o vector original
12 1 Copia 1 vez o 9 para o vector original
12 0
2 Copia 2 vezes o 12 para o vector original
0
0
0

2007/08
A.E.D.
F.S.B. Ordenação de vectores : BucketSort
 Este algoritmo pode ser assim implementado

void ordenaBucketSort( int v[], int nMax ) {


// criação do vector de contagem, com nMax a ser parametrizado
int conta[] = new int[ nMax ];

// contar os valores dos vectores


for( int i = 0; i < v.length; i++ )
conta[ v[i] ]++;

// refazer para o vector original


int iOriginal = 0;
for( int i = 0; i < conta.length; i++ ) {
for( int k = 0; k < conta[ i ]; k++ ){
v[ iOriginal ] = i;
iOriginal++;
}
}
}
Estruturas dinâmicas
2007/08
A.E.D.
F.S.B.

 Uma vantagem de se definirem estruturas de


dados é a possibilidade de criar estruturas de
dados dinâmicas
 Estruturas que crescem e encolhem
 Estruturas que vão mudando à medida que se
inserem ou retiram elementos
 No resto do semestre vão-se discutir alguns tipos
comuns de estruturas dinâmicas

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Um conceito que já se domina são os vectores


 Mas um vector (array) tem um tamanho fixo
 Armazena n elementos e não mais que esses
 Será que se pode criar um vector dinâmico?
 Que cresça consoante as necessidades?
 Que diminua quando tem muitos elementos de sobra?
 Claro que é!
 Embora muitas vezes não se considere um vector
uma estrutura de dados dinâmica, uma vez que o que
varia é o tamanho e não a própria estrutura, como
iremos ver em outras estruturas
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Um conceito associado a um vector é o seu


tamanho
 Se o queremos mudar devemos ter uma variável que
o indique
 no caso do Java esta é automaticamente associada com o
array
 A capacidade de um vector pode não estar
totalmente usada
 Deveremos ter uma variável que indique quantos
elementos úteis estão presentes no vector
 O próprio buffer onde se armazena a informação
propriamente dita é outra variável
 Por isso o melhor caminho é declarar uma
classe que represente um vector dinâmico

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Essa classe poderá ser a seguinte:


 Assumindo que se vai armazenar elementos do tipo
Object

class Vector {
private Object buffer[]; // onde se vai armazenar na realidade os elementos
private int nElems; // nº de elementos úteis no vector
private int capMaxima; // nº de elementos máximo no vector
}
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 O que se faz quando o vector está cheio e se


pretende adicionar um novo elemento?
 Aumenta-se a capacidade do vector
 De quanto se aumenta a memória?
 Para o dobro?
 Mais 50%?
 Muitos dizem ser metade da capacidade inicial do
vector
 Isso implica uma nova variável para a classe

class Vector {
private Object buffer[]; // onde se vai armazenar na realidade os elementos
private int nElems; // nº de elementos úteis no vector
private int capMaxima; // nº de elementos máximo no vector
private int incremento; // nº de elementos a adicionar quando estiver cheio
}

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Após a definição da classe iremos abordar as


seguintes situações:
 Inicialização do vector
 Adição de um elemento ao vector
 Remoção de um elemento ao vector
 Inserção de um elemento ao vector
 E nos casos em que se pretende manter um vector
ordenado?
 Destruição de um vector
 A procura de um elemento num vector já foi
abordada
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Inicialização de um vector
 É necessário indicar a capacidade inicial do vector
 Pode ainda ser indicado o incremento a praticar
 O nº de elementos úteis é zero (ainda está vazio)
 Para isso a classe deve ter três construtores
 Um por defeito, Um que inicialize com uma dada capacidade
inicial e outro com capacidade inicial e incremento

class Vector {
// construtor para inicializar o vector a uma dada capacidade inicial
// e um incremento com valores por defeito
public Vector();

// construtor para inicializar com uma dada capacidade inicial


// incremento é metade deste valor
public Vector( int capInicial ) { ... }

// construtor que permite definir uma capacidade inicial e um incremento


public Vector( int capInicial, int incre ) { ... }
}

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Os construtores poderiam ser assim


implementados
class Vector {
public Vector( ){
this( 1, 1 ); // capacidade inicial 1 e incremento 1
}

public Vector( int capInicial ) {


this( capInicial, capInicial/2 + 1);
// capacidade inicial e incremento metade da capacidade inicial
}

public Vector( int capInicial, int incre ) {


if( capInicial <= 0 )
capInicial = 1;
if( incre <= 0 )
incre = capInicial / 2 + 1;

nElems = 0;
buffer = new Object[ capInicial ];
capMaxima = capInicial;
incremento = incre;
}
}
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Exemplo de inicialização de um vector

Vector meuVector = new Vector( 5 );

meuVector
buffer:
nElems: 0
capMaxima: 5
incremento: 3

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Adicionar um elemento ao vector


 Pensar em três situações:
 Vector vazio (situação limite)
 Vector com espaço livre (situação normal)
 Vector cheio (situação limite)
 As soluções seguintes assumem que o vector é
preenchido sequencialmente
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Adicionar um elemento ( ) ao vector


 Vector vazio (situação limite)

meuVector
meuVector
buffer:
buffer:
nElems: 1
nElems: 0
capMaxima: 5
capMaxima: 5
incremento: 3
incremento: 3

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Adicionar um elemento ( ) ao vector


 Vector com elementos (situação normal)
 É igual a adicionar um elemento num vector vazio
 De referir que a posição onde se vai colocar o novo
elemento é dada pelo nº de elementos existentes!

meuVector meuVector
buffer: buffer:
nElems: 2 nElems: 3
capMaxima: 5 capMaxima: 5
incremento: 3 incremento: 3
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Adicionar um elemento ( ) ao vector


 Vector cheio (situação limite)
 É necessário alocar nova memória
 Copiar os elementos do antigo para o novo
 Passar a usar o novo
 Depois a colocação é feita normalmente
meuVector meuVector
buffer: buffer:
nElems: 5 nElems: 5
capMaxima: 5 capMaxima: 8
incremento: 3 incremento: 3

meuVector
buffer:
nElems: 6
capMaxima: 8
incremento: 3

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Adicionar um elemento ao vector


 É necessário ver se ele está cheio
 Se sim é necessário fazê-lo crescer
 Só depois se adiciona o elemento
 Podem-se (devem-se) adicionar os seguintes
métodos à classe
class Vector {

public boolean estaCheio( ) { // indica se vector está cheio


return nElems == capMaxima;
}

// método para adicionar um elemento


public void adicionar( Object x ) { ... }

// método para fazer o vector crescer


private void aumentar( ) { ... }
}
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Adicionar um elemento ao vector


 É necessário ver se ele está cheio
 Se sim é necessário fazê-lo crescer
 Só depois se adiciona o elemento
class Vector {
public void adicionar( Object x ) {
if( estaCheio() )
aumentar( );

buffer[ nElems ] = x;
nElems++;
}

private void aumentar( ) {


Object novoBuffer[] = new Object[ capMaxima + incremento ];
for( int i = 0; i < nElems; i++ )
novoBuffer[i] = buffer[i];

buffer = novoBuffer;
capMaxima = nElems + incremento;
}

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Remover um elemento do vector


 Pensar em três situações:
 Retirar um elemento do meio
 Retirar um elemento de uma das pontas
 Quando se deve diminuir a capacidade do vector?
 Sempre que se retira um elemento?
 Quando estiver menos de metade cheio?
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Remover um elemento ( ) do meio do vector


 Assim cria-se uma inconsistência
 Como fazer para a evitar?
 Passa-se o último para a posição do retirado
 Deslocam-se todos os elementos, a partir do retirado, uma
posição para a esquerda

meuVector meuVector
buffer: buffer:
nElems: 4 nElems: 3
capMaxima: 5 capMaxima: 5
incremento: 3 incremento: 3

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Remover um elemento ( ) do meio do vector


 Assim cria-se uma inconsistência
 Como fazer para a evitar?
 Passa-se o último para a posição do retirado

 Deslocam-se todos os elementos uma posição para a


esquerda

meuVector meuVector
buffer: buffer:
nElems: 3 nElems: 3
capMaxima: 5 capMaxima: 5
incremento: 3 incremento: 3
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Remover um elemento ( ) do meio do vector


 Assim cria-se uma inconsistência
 Como fazer para a evitar?
 Passa-se o último para a posição do retirado
 Deslocam-se todos os elementos, a partir do
retirado, uma posição para a esquerda

meuVector meuVector
buffer: buffer:
nElems: 3 nElems: 3
capMaxima: 5 capMaxima: 5
incremento: 3 incremento: 3

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Remover um elemento do meio do vector


 Passa-se o último para a posição do retirado
 É mais rápido
 Altera a sequência dos elementos
 Deslocam-se todos os elementos, a partir do retirado,
uma posição para a esquerda
 É muito lento
 Não altera a sequência dos elementos
 Muitas vezes o facto de não alterar a sequência
impõe-se ao facto de ser mais lento e a solução
adoptada é essa
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Remover um elemento do meio do vector


 A classe precisa de um novo método
 O método pode devolver void
 Ou pode retornar o elemento retirado
 esta é a solução mais vezes usada

class Vector {
// implementar UMA das duas
// idx é o índice do elemento a remover

public void retirar ( int idx );


// retira e não devolve nada

public Object retirar ( int idx );


// retira e devolve o elemento retirado

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Remover um elemento do meio do vector


 Passando o último para a posição do retirado

void retirar ( int idx ) {

buffer[ idx ] = buffer[ nElems-1 ];


nElems--;

Object retirar( int idx ) {

Object antigo = buffer[ idx ];


buffer[ idx ] = buffer[ nElems-1 ];
nElems--;
return antigo;
}
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Remover um elemento do meio do vector


 Deslocando todos os elementos, a partir do retirado,
uma posição para a esquerda

void retirar( int idx ) {

for( int i = idx + 1; i < nElems; i++)


buffer[ i – 1 ] = buffer[ i ];
nElems--;
}

Object retirar( int idx ) {

Object antigo = buffer[ idx ];


for( int i = idx + 1; i < nElems; i++)
buffer[ i – 1 ] = buffer[ i ];
nElems--;
return antigo;
}

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Retirar um elemento ( ) - do fim do vector


 Basta decrementar o número de elementos

meuVector meuVector
buffer: buffer:
nElems: 4 nElems: 3
capMaxima: 5 capMaxima: 5
incremento: 3 incremento: 3
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Quando se deve diminuir o vector?


 Sempre que se retira um elemento?
 Não. Os custos associados a isso seriam grandes (libertar memória
implica mover o vector todo)
 Quando houver espaço livre igual ao incremento?
 Não. Isso pode implicar em algumas situações o estar a libertar para
logo a seguir realocar, com os custos envolvidos
 Quando houver espaço livre suficiente superior ao incremento? E
de quanto?
 Porque não? Pode ser, por exemplo, quando houver disponível
incremento e meio
 Decrementa-se do tamanho do incremento
 Nunca?
 Existem implementações que seguem esta regra.
 Será quem usa o vector a definir quando o vector é diminuído

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Quando se deve diminuir o vector?


 Quando houver espaço livre suficiente superior ao
incremento? E de quanto?
 Porque não? Pode ser, por exemplo, quando houver
disponível incremento e meio
 Decrementa-se do tamanho do incremento
 Para usar esta estratégia o método retirar terá de
chamar o método diminuir
void diminuir( ) {
if( nElems < capMaxima – increm * 1.5 ) {
Object novoBuffer = new Object[ capMaxima – incremento ];
for( int i=0; i < nElems; i++ )
novoBuffer[i] = buffer[ i ];
buffer = novoBuffer;
capMaxima -= incremento;
}
}
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Inserir um elemento no vector


 Pensar em três situações:
 Inserir no fim
 Igual ao adicionar

 Inserir no meio
 Inserir no início
 Igual a inserir no meio

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Inserir um elemento ( ) ao vector


 No meio
 É preciso deslocar todos os elementos a partir do seguinte
para a direita
 Muito custoso em termos de eficiência

meuVector meuVector
buffer: buffer:
nElems: 4 nElems: 5
capMaxima: 5 capMaxima: 5
incremento: 3 incremento: 3
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Inserir um elemento ( ) ao vector


 No meio
 É preciso deslocar todos os elementos a partir do seguinte
para a direita
 Muito custoso em termos de eficiência

void inserir(Object x, int idx ) {

if( idx > nElems )


return;

if( estaCheio() )
aumentar( );

for( int i = nElems; i > idx; i-- )


buffer[ i ] = buffer[ i – 1 ];

buffer[ idx ] = x;
nElems++;
}

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Como manter vectores ordenados


 Adicionar elementos
 Seria necessário primeiro uma pesquisa até encontrar a
posição do elemento
 Depois seria necessária uma operação de inserção
 Pouco eficiente
 Retirar elementos
 Igual ao retirar mantendo a ordem dos mesmos
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Vantagens dos vectores


 Acesso rápido aos elementos
 Procuras rápidas (especialmente se ordenados)
 Adição de elementos rápida

 Desvantagens dos vectores


 Manter um vector ordenado é lento
 A inserção de um elemento é lenta
 A remoção de um elemento, sem alterar a ordem dos
restantes, é lenta

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 A classe até agora ficou assim


class Vector {

private Object buffer[]; // onde se vai armazenar na realidade os elementos


private int nElems; // nº de elementos úteis no vector
private int capMaxima; // nº de elementos máximo no vector
private int incremento; // nº de elementos a adicionar quando estiver cheio

public Vector() { ... }


public Vector( int capInicial) { ... }
public Vector( int capInicial, int incremento ) { ... }

public boolean estaCheio( ) { ... }


public boolean estaVazio() { return nElems == 0; }

public void adicionar( Object x ) { … }


public void inserir( Object x, int idx ) { … }
public Object retirar( int idx ) { … }
public Object retirar( ) { … } // retira o último elemento do vector

private void aumentar( ) { ... }


private void diminuir( ) { ... }
}
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Mas ainda se pode pensar em mais situações


 Procura de elementos
 Remoção de um elemento específico
 Remoção de todos os elementos
 Aceder a um elemento específico
 Alterar um elemento
 Gestão da capacidade do vector
 Criação de sub-vectores
 Ordenação de vectores
 Comparação de vectores

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Procura de elementos
 Devolver o índice da primeira ocorrência de um dado
elemento
 Devolver o índice da primeira ocorrência de um dado
elemento após uma dada posição (procura próximo)
 Devolver o índice da última ocorrência de um dado
elemento
 Devolver o índice da última ocorrência de um dado
elemento antes de uma dada posição (procura
anterior)
 Saber apenas se um elemento está presente no
vector
 Saber quantas ocorrências de um elemento existem
no vector
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Devolver o índice da primeira ocorrência de um


dado elemento
 Devolver o índice da primeira ocorrência de um
dado elemento após uma dada posição (procura
próximo)
class Vector {

public int procura( Object e, int posIni ){


for( int i = posIni; i < nElems; i++ )
if( buffer[ i ].equals( e ) )
return i;
return –1;
}

public int procura( Object e ) {


return procura( e, 0 );
}

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Devolver o índice da última ocorrência de um


dado elemento
 Devolver o índice da última ocorrência de um
dado elemento antes de uma dada posição
(procura anterior)
class Vector {

public int procuraFim( Object e ) {


return procuraFim( e, nElems-1 );
}

public int procuraFim( Object e, int posFim ) {


for( int i = posFim; i >= 0; i-- )
if( buffer[ i ].equals( e ) )
return i;
return -1;
}
}
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Saber apenas se um elemento está presente no


vector
 Podem-se utilizar os métodos anteriores

class Vector {

public boolean estaPresente( Object e ){


return procura( e ) != -1;
}

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Saber quantas ocorrências de um elemento


existem no vector
 Podem-se utilizar os métodos anteriores

class Vector {

public int numRepeticoes( Object e ){


int nVezes = 0;
int proxIdx = procura( e );
while( proxIdx != -1 ) {
nVezes++;
proxIdx = procura( e, proxIdx + 1 );
}
return nVezes;
}
}
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Remoção de um elemento específico


 Além de remover pelo índice podemos querer
remover um dado elemento

class Vector {

public void retirar( Object e ) {


int idx = procura( e );
if( idx != -1 )
retirar ( idx );
}

public void retirarTodas( Object e ) {


int idx = procura( e );
while( idx != -1 ) {
retirar( idx );
idx = procura( e, idx );
}
}
}

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Remoção de todos os elementos


 E desta forma limpar completamente o vector

class Vector {

public void limpar( ) {


nElems = 0;
}

}
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Aceder a um elemento específico


 Para poder analisar o seu conteúdo
 Alterar um elemento
 Para poder actualizar o seu conteúdo

class Vector {

public Object get( int idx ) {


return buffer[ idx ];
}

public void set( int idx, Object e ){


buffer[ idx ] = e;
}

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Gestão da capacidade do vector


 Saber quantos elementos contém
 Saber a capacidade máxima
 Saber se está cheio (já feito)
 Saber se está vazio (já feito)
 Saber quantas posições livres tem
 Alterar o incremento
 Definir uma capacidade mínima
 Redimensionar o vector
 Reduzir o vector ao tamanho ocupado actual
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Saber quantos elementos contém


 Saber a capacidade máxima
 Saber quantas posições livres tem
 Alterar o incremento

class Vector {

public int getTamanho( ) { return nElems; }


public int getCapacidade( ) { return capMaxima; }
public int getEspacoLivre( ) { return getCapacidade() – getTamanho(); }

public void setIncremento( int novoInc ) {


incremento = novoInc <= 0? incremento: novoInc;
}

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Definir uma capacidade mínima


 Para garantir espaço para um número de elementos
que se sabe à partida que vai ser necessário
 Uma solução é ir incrementando a capacidade (do valor do
incremento) até esta ser maior que a capacidade
especificada

class Vector {

public void assegurarCapacidade( int capMinima ) {

while( capMaxima < capMinima )


aumentar();

}
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Redimensionar o vector
 Para reservar logo todo o espaço que se sabe vai ser
ocupado
 Evitando assim perda de tempo por realocações sucessivas
nos aumentos do vector
class Vector {

public boolean setTamanho( int tamanho ) {


// se já tem mais elementos que o tamanho pedido
// não pode reduzir senão perde informação
if( tamanho < getTamanho() )
return false;

Object novoBuffer[] = new Object[ tamanho ];


for( int i = 0; i < nElems; i++ )
novoBuffer[ i ] = buffer[ i ];
novoBuffer = buffer;
capMaxima = tamanho;
return true;
}

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Reduzir o vector ao tamanho ocupado actual


 Útil quando se sabe que o vector não vai crescer mais

class Vector {

public void ficarTamanhoActual( ) {


setTamanho( getTamanho( ) );
}

}
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Criação de sub-vectores
 Entende-se por isto retornar um subvector com
elementos que pertencem ao vector
 Cria-se um vector com os elementos desde um índice
inicial até ao índice final
 Por norma o índice inicial é incluído mas o final não
 Exemplo dos elementos 2 a 5 eram incluídos os

elementos 2, 3 e 4
 A razão para isto vai ser dada quando se falarem de
iteradores

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Criação de sub-vectores
 Entende-se por isto retornar um subvector com
elementos que pertencem ao vector
 Cria-se um vector com os elementos desde um índice
inicial até ao índice final

class Vector {

Vector subVector( int ini, int fim ) {

// criar o vector com a capacidade desejada


Vector sub = new Vector ( fim – ini );

for( ; ini < fim; ini++ )


sub.adicionar( buffer[ ini ] );

return sub;
}

}
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Ordenação de vectores
 Basta aplicar um dos algoritmos de ordenação
existentes

class Vector {

public void ordenar( ) { ... }

Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Comparação de vectores
 Um vector pode ser comparado com outro
 Considera-se um vector igual a outro se:
 Tiverem o mesmo tamanho
 Tiverem em cada posição os mesmos elementos
 Considera-se um vector menor que outro
 Se na primeira posição em que são diferentes o elemento no
vector for menor que o elemento no outro vector
 Se terminar primeiro com todos os elementos iguais até ai
Vectores dinâmicos
2007/08
A.E.D.
F.S.B.

 Comparação de vectores
class Vector {

public int compareTo( Vector outro ) {

for( int i = 0; i < nElems; i++ ){


if( i >= outro.nElems )
return 1;
if( ! buffer[ i ].equals( outro.buffer[ i ] ) )
return buffer[i].compareTo( outro.buffer[ i ] );
}

// se chega aqui é porque buffer terminou


// se outro.buffer também chegou ao fim é porque são iguais
if( nElems == outro.nElems )
return 0;
else
return -1;

// também bastava fazer, mas não ficava tão explicito


// return nElems - outro.nElems;
}

}
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Uma lista ligada é composta por vários nós


 Cada nó contém o elemento a guardar
 Cada nó conhece o seu sucessor
 Armazena uma referência para o seu sucessor

 O último nó tem uma referência null

 Em vez de, como nos vectores, se reservar logo uma


quantidade de memória, nas listas ligadas reserva-se a
memória apenas quando é necessário
 O nome de lista ligada deriva do facto de os nós estarem
“ligados” uns aos outros

Listas ligadas
2007/08
A.E.D.
F.S.B.

 É possível que um nó, além de armazenar o


elemento, associe a este uma chave
identificadora
 Essa chave servirá depois para simplificar as
operações de pesquisa e ordenação
 Assim a estrutura de uma lista ligada pode ser:

Chave: identifica o
conteúdo do nó
O último nó (ou
12 22 41 67
cabeça * cauda) tem a
referência a null
Conteúdo ou dados: a
Cabeça: indica qual é Referência para o nó informação que se
o 1º nó da lista seguinte pretende armazenar
(onde ela começa)
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Para os exemplos seguintes vai-se utilizar uma


lista ligada de Objectos, sem chave
 As classes necessárias para representar uma
lista como a referida seriam:
 A classe Lista propriamente dita
 Uma classe que represente os nós da lista

Listas ligadas
2007/08
A.E.D.
F.S.B.

 A análise do problema sugere a existência de 3


classes:
 Lista
 Nó
 Classe a armazenar (nos exemplos um Object)
 De referir que a classe Nó é uma classe que
existe apenas na implementação da classe Lista
 Para garantir o encapsulamento o resto da aplicação
não deverá ter conhecimento desta classe.
 Podem ser usadas duas técnicas para garantir esse
encapsulamento
Listas ligadas :: análise
2007/08
A.E.D.
F.S.B.

 A primeira passa por declarar todas os métodos,


construtor incluído, com o nível de acesso por
defeito. Como ambas as classes são do mesmo
package não há problema
Assim as classe do package podem
declarar objectos da classe No
class No { No entanto o resto do programa
No( ) { … } conhece a classe No
No prox;
Object item;
} Referência para o nó seguinte

Conteúdo do nó

Listas ligadas :: declaração


2007/08
A.E.D.
F.S.B.

 A segunda passa por declarar a classe No como


private dentro da classe Lista.
A classe No é uma classe local
da classe Lista
public class Lista {

private int nElementos; Só a classe Lista conhece a


private No cabeca; classe No
private class No { nº de elementos da lista
No( ) { … }
No prox; primeiro nó da lista
Object item; Referência para o nó seguinte
}
Conteúdo do nó
}
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inicialização de uma lista:


 É necessário indicar que não há elementos
 Isso é feito colocando a cabeça a null

class Lista {

public ListaElemento( ){
cabeca = null;
nElementos = 0;
}

Listas ligadas
2007/08
A.E.D.
F.S.B.

 As operações de adicionar e inserir um elemento


numa lista são bastante semelhantes
 não vamos tratar as operações de modo diferente
 É necessário ter em conta três situações
 Inserir sempre à cabeça
 Inserir sempre na cauda
 Inserir entre dois elementos
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elementos à cabeça


 Com a lista vazia
 Com a lista não vazia

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elementos à cabeça com a lista não vazia


 1º passo, criar um nó para armazenar o elemento
 2º passo, colocar o elemento dentro do nó
 3º passo, o novo nó aponta para o 1º nó da lista
 4º passo, a cabeça passa a apontar para o novo nó
Cabeça *

1º passo 3º passo
Cabeça *
*

2º passo 4º passo
*
Cabeça *
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elementos à cabeça com a lista vazia


 1º passo, criar um nó para armazenar o elemento
 2º passo, colocar o elemento dentro do nó
 3º passo, colocar a cabeça a apontar para o novo nó

Cabeça *

1º passo 2º passo 3º passo

* * Cabeça *

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elementos à cabeça


 Com a lista vazia
 Com a lista não vazia
 Ambas as situações são idênticas
 A única diferença é que, quando há elementos, tem-
se de copiar o valor da cabeça para o novo nó
 Quando a lista está vazia esse valor é null
 Como o último nó deve ter o próximo a null esta acção
também é necessária no 1º elemento
 ou seja, copiar o null da cabeça para o campo próximo
 Não há que fazer distinção entre inserir numa lista
vazia ou numa lista com elementos
 É necessário alterar a cabeça
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Método para criar novo nó

public class Lista {

private class No {
No( Object e ) {
item = e; // o item passa a ser o elemento
prox = null; // o nó é inicializado com
// prox a null (não há próximo)
}
}

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elemento à cabeça

public void inserirCabeca( Object e ){


No novoNo = new No( e );

novoNo.prox = cabeca;
cabeca = novoNo;
nElementos++;
}

Lista aLista = new Lista();

aLista.inserirCabeca( umElemento );
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elementos na cauda


 Com a lista vazia
 Com a lista não vazia

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elementos na cauda com a lista não vazia


 1º e 2º passo, criar nó e armazenar o elemento
 3º passo, descobrir a cauda
 4º passo, a cauda passa a apontar para o novo nó

Cabeça * *

ultimo

Cabeça * *

ultimo
Cabeça * *

ultimo
Cabeça *

ultimo
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elementos na cauda com a lista vazia


 1º e 2º passo, criar nó e armazenar o elemento
 3º passo, a cabeça aponta para o novo nó

Antes Depois

Cabeça Cabeça *
*

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elementos na cauda


 Com a lista vazia
 Com a lista não vazia
 Não é a mesma coisa,
 Com a lista vazia tem-se de alterar a cabeça
 Com a lista não vazia tem-se de encontrar o último
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elemento na cauda

public void inserirCauda( Object e ){


No novoNo = new No( e );

if( cabeca == null ) {


cabeca = novoNo;
nElementos++;
return;
}

No ultimo = cabeca;
while( ultimo.prox != null )
ultimo = ultimo.prox;

ultimo.prox = novoNo;
nElementos++;
}

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Para inserir elementos a meio, vamos considerar


dois casos:
 Inserção ordenada
 Colocando o novo elemento no seu lugar
 Inserção não ordenada
 Indicando a posição onde colocar o novo elemento
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Para inserir elementos a meio considerando a


inserção ordenada
 Com a lista vazia
 Com a lista não vazia
 A meio
 Na 1ª posição
 Na última posição

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elementos numa lista por ordem (não


vazia e inserir no meio)
 1º e 2º passo, criar nó e armazenar o elemento
 3º passo, determinar onde colocar o elemento
 4º passo, novo nó aponta para o actual
 5º passo, anterior aponta para o novo nó

Cabeça *
Cabeça *
anterior actual
Cabeça anterior actual
*
anterior actual

Cabeça *
anterior actual
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elementos numa lista por ordem (não


vazia e inserir no início)
 1º e 2º passo, criar nó e armazenar o elemento
 3º passo, determinar onde colocar o elemento
 4º passo, novo nó aponta para actual
 5º passo, cabeça aponta para novo nó

Cabeça * Cabeça *
anterior actual anterior actual

*
Antes Depois

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elementos numa lista por ordem (não


vazia e inserir no fim)
 1º e 2º passo, criar nó e armazenar o elemento
 3º passo, determinar onde colocar o elemento
 4º e 5º passo são idênticos a inserir no meio

Cabeça * Cabeça
anterior actual
null
Cabeça *
anterior actual
anterior actual

Cabeça * *
anteriornull
Antes actual Depois
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elementos numa lista por ordem (vazia)


 1º e 2º passo, criar nó e armazenar o elemento
 3º passo, cabeça aponta para o novo nó

Cabeça null Cabeça *

Antes Depois

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Para inserir de elementos a meio


 Com a lista vazia
 Com a lista não vazia
 A meio
 Na 1ª
 Na última
 Há duas situações especiais:
 Lista vazia
 Inserir na 1ª posição
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elemento a meio com inserção ordenada

public void inserirOrdem( Object e ) {


No novoNo = new No( e );
No actual = cabeca;
No anterior = null;

// procurar o local onde inserir


while( actual != null && actual.item.compareTo( e ) < 0 ) {
anterior = actual;
actual = actual.prox;
}

novoNo.prox = actual;
if( anterior == null )
cabeca = novoNo;
else
anterior.prox = novoNo;
nElementos++;
}

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Para inserir elementos a meio considerando a


inserção não ordenada
 Com a lista vazia
 Com a lista não vazia
 A meio
 Na 1ª posição
 Na última posição
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elementos numa lista por posição (não


vazia e inserir no meio) ( posição 2 )
 1º e 2º passo, criar nó e armazenar o elemento
 3º passo, determinar onde colocar o elemento
 4º passo, novo nó aponta para o actual
 5º passo, anterior aponta para o novo nó
Cabeça *
anterior
actual

Cabeça Cabeça *
*
anterior actual
anterior actual

Cabeça *

anterior actual

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Para inserir elementos a meio por posição


 É muito semelhante a inserir ordenado
 Os casos especiais são os mesmos
 A diferença está em como se descobre a posição
onde inserir o elemento
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elemento a meio indicando a posição


public void inserir( Object e, int idx ) {
if( !eIndiceValido( idx ) )
throw new ArrayIndexOutOfBoundsException( idx );

No novoNo = new No( e );

int posActual = 0;
No actual = cabeca;
No anterior = null;

while( actual != null && posActual < idx ) {


anterior = actual;
actual = actual.prox;
posActual++;
}

novoNo.prox = actual;
if( anterior == null )
cabeca = novoNo;
else
anterior.prox = novoNo;
nElementos++;
}

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elemento numa lista:


 A inserção à cabeça é rápida
 A inserção a meio e no fim demora mais tempo por
ser necessário percorrer a lista
 Há versões que aceleram a inserção no final, guardando
também um ponteiro para a cauda
 Manter uma lista ordenada é relativamente eficaz
(comparando com um vector)
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Procurar um elemento numa lista:


 Numa lista não ordenada
 Numa lista ordenada

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Procurar um elemento numa lista não ordenada


 Percorrer a lista toda até encontrar o elemento a
procurar ( ) ou a lista acabar

Cabeça *
actual É diferente do procurado logo passa ao próximo

Cabeça *
actual É diferente do procurado logo passa ao próximo

Cabeça *
actual É igual ao procurado logo pára

 Quando se encontra devolve-se o índice do nó


 Se não encontrar deve retornar -1
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Procurar um elemento numa lista não ordenada

public int procurar( Object e ){


No actual = cabeca;
int pos = 0;
while( actual != null && !actual.item.equals( e ) ) {
actual = actual.prox;
pos++;
}

return actual == null ? -1: pos;


}

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Procurar um elemento numa lista ordenada


 Percorrer a lista toda até encontrar o elemento a
procurar ( ) ou a lista acabar
 Ou atingir um elemento maior que ele
Cabeça *

actual É menor que o procurado logo passa ao próximo

Cabeça *

actual É maior que o procurado logo pára

 Quando encontrar retorna o índice do elemento


 Ou -1 caso não o encontre
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Procurar um elemento numa lista ordenada

public int procurar( Object e ) {

No actual = cabeca;
int pos = 0;
while( actual != null && actual.item.compareTo( e ) < 0 ) {
actual = actual.prox;
pos++;
}

if( actual != null && actual.item.equals( e )


return pos;
return -1;
}

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Remover um elemento de uma lista:


 Remover do meio
 Remover do fim
 Remover o 1º
 Remover o último
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Remover um elemento do meio de uma lista:


 1º passo, encontrar o elemento ( ) a eliminar
 2º passo, ligar o anterior ao próximo do actual

Cabeça *
anterior
actual
Cabeça *
1º passo
anterior actual

Cabeça *
anterior actual

Cabeça * 2º passo
anterior actual
Cabeça *

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Remover um elemento do fim de uma lista:


 1º passo, encontrar o elemento ( ) a eliminar
 2º passo, ligar o anterior ao próximo do actual

Cabeça *
anterior
actual
Cabeça * 1º passo
anterior actual

Cabeça *
anterior actual

Cabeça * * 2º passo
anterior actual
Cabeça *
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Remover o 1º elemento de uma lista:


 1º passo, encontrar o elemento ( ) a eliminar
 2º passo, cabeça fica a apontar para o próximo do
actual

1º passo Cabeça *
anterior
actual

2º passo Cabeça *

actual

3º passo Cabeça *

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Remover o último elemento de uma lista:


 1º passo, encontrar o elemento ( ) a eliminar
 2º passo, cabeça fica a apontar para o próximo do
actual

1º passo Cabeça *
anterior
actual

2º passo Cabeça null *


anterior
actual

Cabeça null
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Remover um elemento numa lista:


 Remover do fim
 Igual a remover do meio
 Funcionamento idêntico porque o próximo do actual é null

 Remover o último
 Igual a remover no início
 Funcionamento idêntico porque o próximo do actual é null

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Remover um elemento numa lista:


public void retirar( int idx ){
if( !eIndiceValido( idx ) )
throw new ArrayIndexOutOfBoundsException( idx );

No actual = cabeca;
No anterior = null;
int pos = 0;

while( actual != null && pos < idx ) {


anterior = actual;
actual = actual.prox;
pos++;
}

if( anterior != null )


anterior.prox = actual.prox;
else
cabeca = actual.prox;

nElementos--;
}
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Remover todos os elementos de uma lista


 Necessário percorrer todos os elementos um a um e
eliminá-los
 No final a cabeça aponta para null

1 Cabeça * 5 Cabeça *
anterior actual anterior actual

2 Cabeça * 6 Cabeça *
anterior actual anterior actual

3 Cabeça * 7 Cabeça

anterior actual anterior actual

4 Cabeça * 8 Cabeça null

anterior actual

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Remover todos os elementos de uma lista


 Em Java basta colocar a cabeça a null
 Os restantes elementos são depois eliminados
automaticamente pelo Garbage Colector

public void limpar( ){


cabeca = null;
}
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Ainda se podem pensar em mais situações


 Várias procuras (como nos vectores)
 Remoção de um elemento especifico
 Aceder a um elemento específico
 Alterar um elemento
 Criação de sub-listas
 Inserção de sublistas
 Ordenação de listas
 Comparação de listas
 Iteração sobre a lista

 Em relação aos vectores


 Não são necessários métodos de gestão de
capacidade

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Procura de elementos
 Devolver o índice da primeira ocorrência de um dado
elemento (já feito)
 Devolver o índice da primeira ocorrência de um dado
elemento após uma dada posição (procura próximo)
 Devolver o índice da última ocorrência de um dado
elemento
 Devolver o índice da última ocorrência de um dado
elemento antes de uma dada posição (procura
anterior)
 Saber apenas se um elemento está presente na lista
 Saber quantas ocorrências de um elemento existem
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Devolver o índice da primeira ocorrência de um


dado elemento

class ListaElemento {

public int procura( Object e ) {


return procura( e, 0);
}

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Devolver o índice da primeira ocorrência de um


dado elemento após uma dada posição (procura
próximo)
public int procura( Object e, int posIni ) {

if( !eIndiceValido( posIni ) )


throw new ArrayIndexOutOfBoundsException( posIni );

No actual = cabeca;
int pos = 0;

while( pos < posIni ) { // ir até posição inicial


actual = actual.prox;
pos++;
}

while( actual != null && !actual.item.equals( e ) ) {


pos++;
actual = actual.prox;
}

return actual == null? -1: pos;


}
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Devolver o índice da última ocorrência de um


dado elemento
 Devolver o índice da última ocorrência de um
dado elemento antes de uma dada posição
(procura anterior)
 Estes métodos são complicados de fazer já que não
se pode percorrer a lista no sentido inverso
 A não ser que esta seja duplamente ligada

Listas ligadas
2007/08
A.E.D.
F.S.B.

public int procuraFim( Object e ) {


return procuraFim( e, nElementos );
}

public int procuraFim( Object e, int posFim ) {


if( !eIndiceValido( posFim ) )
throw new ArrayIndexOutOfBoundsException( posFim );

No actual = cabeca;
int pos = 0;
int ultimaPos = -1;

while( actual != null && pos < posFim ) {


if( actual.item.equals( e ) )
ultimaPos = pos;
pos++;
actual = actual.prox;
}

return ultimaPos;
}
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Saber se um elemento está presente na lista


public boolean estaPresente( Object e ) {
return procura( e ) != -1;
}

 Saber quantas ocorrências de um elemento


existem
public int numRepeticoes( Object e ) {
No actual = cabeca;
int nVezes = 0;

while( actual != null ) {


if( actual.item.equals( e ) )
nVezes++;
actual = actual.prox;
}
return nVezes;
}

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Remoção de um elemento específico


 Além de remover pelo índice podemos querer
remover um dado elemento

public void retirar( Object e ){


Esta solução,
// solução pouco eficaz!!!
int idx = procura( e );
adaptada dos vectores funciona,
retirar( idx ); mas é muito pouco eficaz
} nas listas

Uma solução eficaz teria de repetir o código da pesquisa


e incluir o código da eliminação
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Aceder a um elemento específico


 Para poder analisar o seu conteúdo

public Object get( int idx ) {


if( !eIndiceValido( idx ) )
throw new ArrayIndexOutOfBoundsException( idx );

No actual = cabeca;
int pos = 0;
while( actual != null && pos < idx ) {
pos++;
actual = actual.prox;
}
return actual.item;
}

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Alterar um elemento
 Para poder actualizar o seu conteúdo

public Object set( Object e, int idx ) {


if( !eIndiceValido( idx ) )
throw new ArrayIndexOutOfBoundsException( idx );

No actual = cabeca;
int pos = 0;
while( actual != null && pos < idx ) {
pos++;
actual = actual.prox;
}
Object antigo = actual.item;
actual.item = e;
return antigo;
}
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Criação de sub-listas
 Pegando na lista original devolver uma sub-lista
public Lista subLista( int ini, int fim ) {
Lista res = new ListaElemento();

if( !eIndiceValido( ini ) || !eIndiceValido( fim ))


throw new ArrayIndexOutOfBoundsException( );

No actual = cabeca;
int pos = 0;
// percorrer até à posição inicial
while( actual != null && pos < ini ) {
pos++;
actual = actual.prox;
}

// copiar até à posição final


while( actual != null && pos < fim ) {
res.inserirCauda( actual.item );
pos++;
actual = actual.prox;
}
return res;
}

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserção de sublistas
 Inserir uma lista completa dentro de outra lista
 Exemplo na posição 2
Deslocar na lista original
Cabeça *
até à posição de inserção
anterior actual

Cabeça Deslocar na lista a inserir


*
até à última posição
ultimo ultimo ultimo

Cabeça * Actualizar os ponteiros

anterior actual

Cabeça

ultimo
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserção de sublistas
 Inserir uma lista completa dentro de outra lista
 Exemplo na posição 2
 Cuidado, a segunda Lista ficou alterada!!!

Cabeça *

anterior ultimo actual

Cabeça

Listas ligadas
2007/08
A.E.D.
F.S.B.

public void insereLista( Lista umaLista, int idx ) {


 Inserção de sublistas
if( !eIndiceValido( idx ) )
throw new ArrayIndexOutOfBoundsException( idx );

// ver se origem está vazia


if( umaLista.cabeca == null )
return;

// ver posição onde inserir a nova lista


No actual = cabeca, anterior = null;
int pos = 0;
while( actual != null && pos < idx ) {
pos++;
anterior = actual;
actual = actual.prox;
}
// percorrer a lista a inserir até ao fim
No fimLista = umaLista.cabeca;
while( fimLista.prox != null )
fimLista = fimLista.prox;

if( anterior == null )


cabeca = umaLista.cabeca;
else
anterior.prox = umaLista.cabeca;
fimLista.prox = actual;
nElementos += umaLista.nElementos;
}
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserção de sublistas
 Inserir uma lista completa dentro de outra lista
 Atenção:
 A solução anterior implica que a lista que foi inserida não
pode mais ser utilizada!
 Uma solução melhor seria a inserção copiar o conteúdo da
lista a inserir e não a própria lista
 Isto leva à necessidade de ter um método para
criar uma cópia de uma lista

public Lista clone(){


Lista list = new Lista();
No actual = cabeca;
while( actual != null )
list.inserirCauda( actual.item );
return list;
}

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir uma lista completa (cópia) dentro de outra


public void insereLista( Lista umaLista, int idx ) {
lista if( !eIndiceValido( idx ) )
throw new ArrayIndexOutOfBoundsException( idx );

// ver se origem está vazia


if( umaLista.cabeca == null )
return;

// criar a cópia da lista a inserir


Lista list = umaLista.clone();
// ver posição onde inserir a nova lista
No actual = cabeca, anterior = null;
int pos = 0;
while( actual != null && pos < idx ) {
pos++;
anterior = actual;
actual = actual.prox;
}
// percorrer a lista a inserir até ao fim
No fimLista = umaLista.cabeca;
while( fimLista.prox != null )
fimLista = fimLista.prox;

if( anterior == null )


cabeca = umaLista.cabeca;
else
anterior.prox = umaLista.cabeca;
fimLista.prox = actual;
nElementos += umaLista.nElementos;
}
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Ordenar listas:
 Como o acesso a um elemento pode ser demorado os
algoritmos estudados tornam-se demasiado ineficazes
 Quase nunca se ordenam listas
 Estas devem ser preenchidas de forma ordenada
 Caso seja necessário cria-se uma lista ordenada a partir de
uma desordenada

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Comparar listas:
 Duas listas são iguais se tiverem o mesmo
comprimento e em cada nó os elementos são iguais
 Fazer as funções de comparação como nos vectores
public int compareTo( Lista outra ) {
No actualA = cabeca;
No actualB = outra.cabeca;
while( actualA != null && actualB != null &&
actualA.item.equals(actualB.item )
){
actualA = actualA.prox;
actualB = actualB.prox;
}
if( actualA == null && actualB == null )
return 0;
if( actualA == null ) // A terminou logo A é menor
return -1;
if( actualB == null ) // B terminou, logo A é maior
return 1;
return actualA.item.compareTo( actualB.item );
}
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Vantagens das listas


 Fácil de manter ordenada
 A memória só é alocada quando se precisa dela
 Inserção de elementos relativamente rápida

 Desvantagens das listas


 Acesso relativamente lento aos elementos
 A memória pode ficar fraccionada

 Os “relativamente” referem-se sempre em comparação com


os vectores

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Algumas variantes nas listas:


 Manter um ponteiro para a cauda
 Optimiza as inserções na cauda
Cauda

Cabeça *

 Listas duplamente ligadas


 Cada nó tem uma referência para o nó seguinte e outra para
o nó anterior
 Optimiza as operações que necessitam de percorrer a lista

Cabeça * *
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Algumas variantes nas listas:


 Listas circulares
 O último nó aponta para o primeiro
 Permite tratar a lista tomando qualquer nó como sendo a
cabeça

Cabeça

Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elementos na cauda numa lista com


ponteiro para a cauda
 1º e 2º passo, criar nó e armazenar o elemento
 3º passo, nó da cauda aponta para novo nó
 4º passo, a cauda passa a ser o novo nó
Cauda

Cabeça * *

Cauda

Cabeça *

Cauda

Cabeça *
Listas ligadas
2007/08
A.E.D.
F.S.B.

 Inserir elementos na cauda numa lista com


ponteiro para a cauda

public void inserirCauda(Object e ) {


No novoNo = new No( e );

if( cabeca == null ) // lista vazia


cabeca = novoNo;
else
cauda.prox = novoNo;

cauda = novoNo;
nElementos++;
}
Pilha - stack
2007/08
A.E.D.
F.S.B.

 Uma pilha, ou stack, é um conjunto de


elementos em que cada novo elemento é
adicionado no topo dos restantes elementos
 Quando se retira um elemento retira-se sempre
o que está no topo
 Uma pilha tem assim um comportamento de
 Último a entrar, primeiro a sair
 LIFO – Last In First Out

Pilha - stack
2007/08
A.E.D.
F.S.B.

 Uma pilha tem um comportamento de


 Último a entrar, primeiro a sair
 LIFO – Last In First Out

Pilha Pilha Pilha Pilha Pilha Pilha


Pilha - stack
2007/08
A.E.D.
F.S.B.

 A interface de uma pilha seria:

interface Pilha {
public void push( Object novo );
public Object pop( );

boolean estaVazia( );
boolean estaCheia( );
}

 A pilha defina apenas um comportamento e não


uma estrutura de dados
 A pilha pode ser implementada recorrendo quer
a vectores quer a listas

Pilha - stack
2007/08
A.E.D.
F.S.B.

 Para ser implementada por um vector basta usar


os métodos
 adicionar
 remover
 Por isso existem várias implementações de
vectores que chamam a esses métodos
 Push
 na classe vector em C++ é push_back
 na classe Vector em Java é add
 Pop
 na classe vector em C++ é pop_back
 a classe Vector em Java não tem uma directa
Pilha - stack
2007/08
A.E.D.
F.S.B.

 Para ser implementada por uma lista basta usar


os métodos
 adicionarCabeca
 removerCabeca
 Porque inserir e remover à cabeça, nas listas sem ponteiro
para a cauda, é mais eficiente

Fila - queue
2007/08
A.E.D.
F.S.B.

 Uma fila, ou queue, é um conjunto de elementos


em que cada novo elemento é adicionado na
cauda da fila
 Quando se retira um elemento retira-se sempre
o que está na frente da fila
 Uma fila tem assim um comportamento de
 Primeiro a entrar, primeiro a sair
 FIFO – First In First Out
Fila - queue
2007/08
A.E.D.
F.S.B.

 Uma fila tem um comportamento de


 Primeiro a entrar, primeiro a sair
 FIFO – First In First Out

Fila

Fila Fila Fila Fila Fila Fila Fila Fila Fila

Fila - queue
2007/08
A.E.D.
F.S.B.

 A interface de uma fila seria:

interface FilaElemento {
public void enqueue( Object novo );
public Object dequeue( );

boolean estaVazia( );
boolean estaCheia( );
}

 A fila define apenas um comportamento e não uma


estrutura de dados
 A fila pode ser implementada recorrendo quer a vectores
quer a listas
 Embora com listas seja mais eficaz
Fila - queue
2007/08
A.E.D.
F.S.B.

 Devido ao facto da remoção de um elemento


nos vectores (mantendo a ordem dos restantes)
ser uma tarefa muito pouco eficaz raramente se
procede à implementação de uma fila através de
um vector

 Para implementar uma fila usando as listas


basta usar os métodos
 adicionarCauda
 removerCabeca
 Ou vice-versa

Fila - queue
2007/08
A.E.D.
F.S.B.

 Normalmente constrói-se uma classe Queue e


internamente usa-se uma lista (ou vectores)
class Queue implements Fila {

private Lista minhaLista = new Lista( );

public Queue( ) {}

public void enqueue( Object novo ){


minhaLista.adicionaCauda( novo );
}

public Object dequeue( ) {


return minhaLista.remove( 0 );
}

public boolean estaVazia( ) { return minhaLista.estaVazia(); }


public boolean estaCheia( ) { return false; }

}
Árvores
2007/08
A.E.D.
F.S.B.

 Uma árvore é composta por vários nós


 Cada nó contém o elemento a guardar
 Cada nó tem, no máximo, n sucessores
 Armazena uma referência para cada um dos seus sucessores
 Se não tiver um dado sucessor a referência é null
 Reparar que cada nó pode ser considerado uma sub-árvore

** *

*** *** *** *** *** ***

Árvores
2007/08
A.E.D.
F.S.B.

 Nas árvores é frequente que um nó associe ao elemento


que armazena uma chave identificadora
 Essa chave servirá depois para simplificar as operações de
pesquisa e ordenação
 Assim a estrutura de uma árvore costuma ser:

chave

chave chave chave


** *

chave chave chave chave chave chave


*** *** *** *** *** ***
Árvores
2007/08
A.E.D.
F.S.B.

 Nomenclatura a usar nas árvores:


 Raiz: nó superior da árvore
 Pai: nó ao qual está ligado
 Filho: nó que lhe está ligado
 Ancestral: qualquer nó que esteja acima, no mesmo caminho
 Descendente: qualquer nó que esteja abaixo, no mesmo
caminho

Raiz

Ancestral de Pai de
** *

** *** *** *** *** ***


Filho de
* * * Descendente de

Árvores
2007/08
A.E.D.
F.S.B.

 Nomenclatura a usar nas árvores:


 Folha: nó sem filhos (ou nó externo ou terminal)
 Nó interno: nó que não é folha, isto é, tem pelo menos um filho
 Grau do nó: número máximo de filhos de um nó
 Grau da árvore: maior dos graus dos nós
 A uma árvore de grau n chama-se árvore n-ária

Árvore de grau 3 ou árvore ternária

Nó interno Nó de grau 3

** *

** *** *** *** *** ***


Folha
***
Árvores
2007/08
A.E.D.
F.S.B.

 Nomenclatura a usar nas árvores:


 Profundidade do nó: distância (em nós) desde a raiz
 Altura da árvore: maior das profundidades dos nós

Profundidade Árvore de altura 3


0

1
** *

2
** *** *** *** *** ***
3
***

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Uma árvore binária é uma árvore de grau dois


 Os ramos de cada nó costumam designar-se por
ramo esquerdo e ramo direito (ou sub-árvore
esquerda e sub-árvore direita)

Árvore Binária

esquerdo direito

* * * * * * * * * *
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Uma árvore binária (de objectos) pode ser


representada pela seguinte classe
public class ArvBin {

private No raiz; // raiz da árvore


private int nElems; // nº de elementos da árvore

public ArvBin( ) { … }

private class No {
int chave; // elemento opcional
Object oItem;
No esq;
No dir;
No pai;

No( int chv, Object novo ) { … }


}

Árvores binárias
2007/08
A.E.D.
F.S.B.

 O construtor da árvore binária pode ser o


seguinte:

ArvBin( ) {
nElems = 0;
raiz = null;
}

 O construtor do nó pode ser o seguinte:

No( int chv, Object novo ){


esq = null;
dir = null;
pai = null;
chave = chv;
oItem = novo;
}
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Para a inserção de elementos numa árvore


vamos considerar um tipo especial de árvores
binárias:
 Binary search trees (Árvores binárias de procura)
 Neste tipo de árvores os elementos estão
ordenados segundo a seguinte regra:
 os elementos à esquerda de um nó são sempre não
superiores (menores ou iguais) ao elemento do
próprio nó
 os elementos à direita de um nó são sempre não
inferiores (maiores ou iguais) ao elemento do próprio

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Para os exemplos seguintes iremos considerar


uma árvore binária de inteiros

public class ArvBinInt {


private No raiz; // raiz da árvore
private int nElems; // nº de elementos da árvore

public ArvBinInt( ) { … }

private class No {
// reparar que não tem chave
int oElemento;
No esq;
No dir;
No pai;

No( int novo ) { … }


}
}
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Exemplo de uma árvore binária de procura com


inteiros

Raiz
20

18 28
*

10 20 25
*

5 15 19 21 27
* * * * * * * * *

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Para inserir um elemento numa árvore iremos


considerar alguns casos:
 Árvore preenchida com um nó:
 Elemento com valor inferior ao do nó inicial
 Elemento com valor superior ao do nó inicial
 Árvore preenchida com vários nós:
 alguns exemplos
 Árvore vazia
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Inserir um elemento numa árvore preenchida


com apenas um nó, supondo um elemento maior
que o nó inicial
 1º e 2º passos, criar um novo nó e inserir o elemento
(28) no novo nó
 3º passo, como é maior tem de ir para o lado direito,
logo tem-se de alterar o lado direito da raiz

Raiz Raiz

20 20
* * *

28 28
* * * *

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Inserir um elemento numa árvore preenchida


com apenas um nó, supondo um elemento
menor que o nó inicial
 1º e 2º passos, criar um novo nó e inserir o elemento
(18) no novo nó
 3º passo, como é menor tem de ir para o lado
esquerdo, logo tem-se de alterar o lado esquerdo da
raiz

Raiz Raiz

20 20
* * *

18 18
* * * *
Árvores binárias
2007/08
A.E.D.
F.S.B.

 A inserção de um elemento quando existe


apenas um nó pode ser sumariada até aqui pelo
seguinte método:

void inserir( int novo ) {


No novoNo = new No( novo );

No actual = raiz;

if( novo > actual.oElemento )


actual.dir = novoNo;
else
actual.esq = novoNo; !
novoNo.pai = actual; pleta
}
in com
são
ver

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Inserir um elemento numa árvore preenchida


com vários nós, exemplo 1, valor 5
 1º e 2º passos: criar um novo nó e inserir o elemento
(5) no novo nó
 3º passo, determinar onde inserir
Raiz
Como 5 é menor que 20
segue para a esquerda 20
Como 5 é menor que 18
segue para a esquerda 18 28
Como 5 é menor que 10 *
segue para a esquerda
5 anterior 10 20 25
* * actual * * * *

Como actual é null pára 15 21


* * * *
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Inserir um elemento numa árvore preenchida


com vários nós, exemplo 1, valor 5
 1º e 2º passos: criar um novo nó e inserir o elemento
(5) no novo nó
 3º passo, determinar onde inserir
Raiz
 4º passo, como 5 é menor que o
elemento (10) fica no 20

lado esquerdo
18 28
*
anterior
10 20 25
actual * * *

5 15 21
* * * * * *

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Inserir um elemento numa árvore preenchida


com vários nós, exemplo 2, valor 19
 1º e 2º passos: criar um novo nó e inserir o elemento
(19) no novo nó
 3º passo, determinar onde inserir
Raiz
Como 19 é menor que 20
segue para a esquerda 20

Como 19 é maior que 18


segue para a direita 18 28
Como 19 é menor que 20 *
segue para a esquerda
19 10 anterior 20 25
* * actual * * *
Como actual é null pára
5 15 21
* * * * * *
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Inserir um elemento numa árvore preenchida


com vários nós, exemplo 2, valor 19
 1º e 2º passos: criar um novo nó e inserir o elemento
(19) no novo nó
 3º passo, determinar onde inserir
Raiz
 4º passo, como 19 é menor que o
elemento (20) fica no 20

lado esquerdo
18 28
*

10 anterior 20 25
* *

5 15 19 21
* * * * * * * *

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Inserir um elemento numa árvore preenchida


com vários nós, exemplo 3, valor 27
 1º e 2º passos: criar um novo nó e inserir o elemento
(27) no novo nó
 3º passo, determinar onde inserir
Raiz Como 27 é
Como 27 é maior que 20 menor que 28
segue para a direita 20 segue para
a esquerda

18 28
Como 27 é maior que 25 *
segue para a direita
27 anterior
10 20 25
* * actual
* *
Como actual é
5 15 19 21 zero pára
* *
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Inserir um elemento numa árvore preenchida


com vários nós, exemplo 3, valor 27
 1º e 2º passos: criar um novo nó e inserir o elemento
(27) no novo nó
 3º passo, determinar onde inserir
 4º passo, como 27 é maior que o Raiz
elemento (25) fica no lado 20
direito
18 28
*
anterior
10 20 25
actual
*

5 15 19 21 27
* * * * * * * * * *

Árvores binárias
2007/08
A.E.D.
F.S.B.

O método de inserção de um elemento pode ser




actualizado com os exemplos anteriores:


void inserir( int novo ) {
!
No novoNo = new No( novo );
pleta
No anterior = null;
i n com
No actual = raiz; o
ersã
while( actual != null ) { V
anterior = actual;
if( novo > actual.oElemento )
actual = actual.dir;
else
actual = actual.esq;
}

if( novo > anterior.oElemento )


anterior.dir = novoNo;
else
anterior.esq = novoNo;

novoNo.pai = anterior;
}
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Inserir um elemento numa árvore vazia


 1º e 2º passos, criar um novo nó e inserir o elemento
(20) no novo nó
 3º passo, a raiz aponta para o novo nó

 É diferente dos outros uma vez que é necessário


alterar a raiz !!!

Raiz Raiz

20 20
* * * *

Árvores binárias
2007/08
A.E.D.
F.S.B.

 O método de inserção na sua versão final:


void inserir( int novo ) {
No novoNo = new No( novo );
No anterior = null;
No actual = raiz;

while( actual != null ) {


anterior = actual;
if( novo > actual.oElemento )
actual = actual.dir;
else
actual = actual.esq;
}

if( anterior == null )


raiz = novoNo;
else if( novo > anterior.oElemento )
anterior.dir = novoNo;
else
anterior.esq = novoNo;

novoNo.pai = anterior;
}
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Para procurar um elemento numa árvore iremos


considerar alguns casos:
 Nó existe
 Nó não existe
 Mas como vimos nas listas e vectores o melhor
será devolver um iterador
 Caso contrário o que se retornaria?
 Nas árvores não há índices
 Só se se retornasse o ponteiro para o nó
 Mas queremos manter o nó como interno à classe e não
público

Árvores binárias :: Iteradores


2007/08
A.E.D.
F.S.B.

 As árvores, como já se vê, não podem ter um


comportamento como uma lista ou um vector, no
que se refere a iteradores
 Qual é o próximo de um elemento?
 O da esquerda ou o da direita?
 No caso das árvores binárias de pesquisa vamos
assumir que o próximo é o seguinte em termos de
ordem
 Assim nas árvores binárias de pesquisa podemos ter um
iterador bidireccional
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Procurar o elemento 20

Como 20 é igual a 20 Raiz


retorna um iterador com
actual
o ponteiro actual 20

18 28
*

10 20 25
*

5 15 19 21 27
* * * * * * * * * *

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Procurar o elemento 10

Raiz
Como 10 é menor que 20
segue para a esquerda 20
Como 10 é menor que 18
segue para a esquerda
18 28
Como 10 é igual a 10 retorna
um iterador com o actual *
actual
10 20 25
*

5 15 19 21 27
* * * * * * * * * *
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Procurar o elemento 21

Raiz
Como 21 é maior que 20
segue para a direita 20
Como 21 é menor que 28
segue para a esquerda
18 28
*

10 20 25 Como 21 é menor que 25


* segue para a esquerda
actual
5 15 19 21 27
* * * * * * * * * *
Como 21 é igual a 21 retorna
um iterador com o actual

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Procurar o elemento 30

Raiz
Como 30 é maior que 20
segue para a direita 20
Como 30 é maior que 28
segue para a direita
18 28
*
actual
10 20 25 Como actual é null pára e
* devolve um iterador com o
nó a null (não encontrou)
5 15 19 21 27
* * * * * * * * * *
Árvores binárias
2007/08
A.E.D.
F.S.B.

 A função de procura pode ser assim


implementada:

IteratorBidirec procura( int x ) {


No actual = raiz;

while( actual != null && actual.oElemento != x ){


if( x > actual.oElemento )
actual = actual.dir;
else
actual = actual.esq;
}

return new IteratorArvBinInt( actual );


}

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Quanto à procura numa árvore pode-se dizer o


seguinte:
 É mais rápida (em média) que numa lista
 Depende fortemente da disposição dos nós da árvore

Raiz
Raiz
5
18 *
10
*
15
10 20 *
* 18
*
5 15 19 19 20
* * * * * * * * *
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Árvore equilibrada (balanced)


 quando o lado esquerdo tem o mesmo número de nós
do lado direito ou diferença de um, no máximo
 Nestes casos a pesquisa tem o seu
desempenho óptimo
Raiz
Raiz
Equilibrada 5 Não equilibrada
18 *
10
*
15
10 20 *
* 18
*
5 15 19 19 20
* * * * * * * * *

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Nas árvores há mais operações que não são


directas:
 Determinar o menor elemento
 Determinar o maior elemento
 Determinar o elemento seguinte (sucessor) a um dado
elemento
 Determinar o elemento anterior (predecessor) a um
dado elemento
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Determinar o menor elemento


 Percorrer a árvore toda, sempre pelo lado esquerdo

Raiz
20

18 28
*

10 20 25
*
menor
5 15 19 21 27
* * * * * * * * * *

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Determinar o menor elemento


 Percorrer a árvore toda, sempre pelo lado esquerdo

IteratorBidirec menor( ) {
No actual = raiz;
if( raiz == null )
return new IteratorArvBinInt( null );

while( actual.esq != null )


actual = actual.esq;

return new IteratorArvBinInt( actual );


}

IteratorBidirec inicio( ){
return menor();
}
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Determinar o maior elemento


 Percorrer a árvore toda, sempre pelo lado direito

Raiz
20
maior
18 28
*

10 20 25
*

5 15 19 21 27
* * * * * * * * * *

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Determinar o maior elemento


 Percorrer a árvore toda, sempre pelo lado direito

IteratorBidirec maior( ){
No actual = raiz;
if( raiz == null )
return new IteratorArvBinInt( null );

while( actual.dir != null )


actual = actual.dir;

return new IteratorArvBinInt( actual );


}

IteratorBidirec fim( ){
return maior( );
}
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Para casos em que a recursividade é útil, é


conveniente ter um método que retorne a árvore
esquerda (ou direita) a partir de um nó

public ArvBinInt ramoEsquerdo( IteratorBidirec pos ) {

ArvBinInt esquerda = new ArvBinInt();


esquerda.raiz = ((IteratorArvBinInt)pos).oNo.esq;
return esquerda;

public ArvBinInt ramoDireito( IteratorBidirec pos ) {

ArvBinInt direita = new ArvBinInt();


direita.raiz = ((IteratorArvBinInt)pos).oNo.dir;
return direita;

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Determinar o elemento seguinte (sucessor)


 Se houver lado direito é o menor do lado direito
 Se não houver é o ancestral que tenha o elemento
nos seus descendentes esquerdos
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Determinar o elemento seguinte (sucessor)


 Se houver lado direito é o menor do lado direito
 Exemplo: sucessor do 18

Raiz
22

18 28
*

10 20 25
*
Seguinte
5 15 19 23 27
* * * * * * * * * *

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Determinar o elemento seguinte (sucessor)


 Se não houver [direito] é o ancestral que tenha o
elemento nos seus descendentes esquerdos
 Exemplo: sucessor do 27

Raiz

22
Seguinte
18 28
*

10 20 25
*

5 15 19 23 27
* * * * * * * * * *
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Determinar o elemento seguinte (sucessor)


 Se houver lado direito é o menor do lado direito
 Se não houver é o ancestral que tenha o elemento
nos seus descendentes esquerdos
 Como se determina o menor do lado esquerdo?
 Tem-se de repetir o procedimento já feito para
determinar o menor
 Mas agora a partir de uma dada posição

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Determinar o elemento seguinte (sucessor)


 Primeiro determinar onde está o elemento cujo
sucessor se quer determinar
 Depois procurar o sucessor desse nó

public IteratorBidirec sucessor( int valor ) {

IteratorBidirec pos = procura( valor );


return sucessor( pos );

}
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Determinar o elemento seguinte (sucessor)


 Se houver lado direito é o menor do lado direito
 Se não houver é o ancestral que tenha o elemento
nos seus descendentes esquerdos
public IteratorBidirec sucessor( IteratorBidirec pos ) {
// converter o iterador para "um dos nossos"
IteratorArvBinInt iter = (IteratorArvBinInt)pos;
if( iter.oNo.dir != null )
return ramoDireito( iter ).menor( );

No actual = iter.oNo;
No ancestral = actual.pai;
while( ancestral != null && ancestral.dir == actual ) {
actual = ancestral;
ancestral = ancestral.pai;
}

return new IteratorArvBinInt( ancestral );


}

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Determinar o elemento anterior (predecessor)


 Se houver lado esquerdo é o maior do lado esquerdo
 Se não houver é o ancestral que tenha o elemento
nos seus descendentes direitos
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Determinar o elemento anterior (predecessor)


 Se houver lado esquerdo é o maior do lado esquerdo
 Exemplo: predecessor do 22

Raiz
22

18 28
Anterior *

10 20 25
*

5 15 19 23 27
* * * * * * * * * *

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Determinar o elemento anterior (predecessor)


 Se não houver [esquerdo] é o ancestral que tenha o
elemento nos seus descendentes direitos
 Exemplo: predecessor do 25

Raiz
Anterior
22

18 28
*

10 20 25
* *

5 15 19 27
* * * * * * * *
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Determinar o elemento anterior (predecessor)


 Primeiro determinar onde está o elemento cujo
predecessor se quer determinar
 Depois procurar o predecessor desse nó

public IteratorBidirec predecessor( int valor ) {

IteratorBidirec pos = procura( valor );


return predecessor( pos );

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Determinar o elemento anterior (predecessor)


 Se houver lado esquerdo é o maior do lado esquerdo
 Se não houver é o ancestral que tenha o elemento
nos seus descendentes direitos

public IteratorBidirec predecessor( IteratorBidirec pos ) {


// converter o iterador para "um dos nossos"
IteratorArvBinInt iter = (IteratorArvBinInt)pos;
if( iter.oNo.esq != null )
return ramoEsquerdo( iter ).maior( );

No actual = iter.oNo;
No ancestral = actual.pai;
while( ancestral != null && ancestral.esq == actual ) {
actual = ancestral;
ancestral = ancestral.pai;
}

return new IteratorArvBinInt( ancestral );


}
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Para eliminar um elemento de uma árvore


vamos considerar os seguintes casos:
 Eliminar um nó sem filhos
 Eliminar um nó com 1 filho
 Eliminar um nó com 2 filhos
 Eliminar a raiz
 Eliminar o último elemento da árvore

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Eliminar um nó sem filhos:


 1º passo, encontrar o nó a eliminar (15)
 2º passo, Pai do nó fica a apontar para null
 3º passo, libertar memória do nó
Raiz

depois antes 22

18 28
*
10 10 20 25
* *
5 15 5 15 19 23 27
* * * * * * * * * * * * * *
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Eliminar um nó com um filho:


 1º passo, encontrar o nó a eliminar (28)
 2º passo, o Pai fica a apontar para o filho do nó
 3º passo, libertar memória do nó

Raiz
22 22
Actual 28
*
18 28
* 25

10 20 25
23 27
*
* * * *
5 15 19 23 27
* * * * * * * * * *

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Eliminar um nó com dois filhos:


 1º passo, encontrar o nó a eliminar (18)
 2º passo, encontrar o sucessor do nó
 3º passo, copiar o conteúdo do sucessor para o nó

Raiz
Depois Antes 22
Actual Actual
19 18 25

20 10 20 23 27
Sucessor * Sucessor * * * * *

19 5 15 19
* * * * * * * *
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Eliminar um nó com dois filhos:


 1º passo, encontrar o nó a eliminar (18)
 2º passo, encontrar o sucessor do nó
 3º passo, copiar o conteúdo do sucessor para o nó
 4º passo, eliminar sucessor
Raiz
22
Actual
19 25

10 20 23 27
Sucessor * * * * * *

5 15 19
* * * * * *

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Eliminar um nó com dois filhos (EXEMPLO 2)


 1º passo, encontrar o nó a eliminar (18)
 2º passo, encontrar o sucessor do nó
 3º passo, copiar o conteúdo do sucessor para o nó

Raiz
Depois Antes
Actual Actual 22

19 18 25

21 10 21 23 27
Sucessor * Sucessor * * * * *

19 5 15 19
* * * * * *
20 20
* * * *
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Eliminar um nó com dois filhos (EXEMPLO 2)


 1º passo, encontrar o nó a eliminar (18)
 2º passo, encontrar o sucessor do nó
 3º passo, copiar o conteúdo do sucessor para o nó
 4º passo, eliminar sucessor
Raiz

Actual Antes Depois Actual 22

19 19 25

21 10 21 23 27
Sucessor * * * * * *

19 5 15 20
* * * * * * *
20
* *

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Eliminar a raiz
 Igual a eliminar um nó qualquer com dois pormenores:
 É necessário alterar o ponteiro da raiz
 O Pai da raiz aponta para null

 Eliminar o último
 Igual a eliminar a raiz
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Método para eliminar um elemento de uma


árvore:
public void eliminar( IteratorBidirec pos ){
No actual, aApagar, filho, pai ;
actual = ((IteratorArvBinInt)pos).oNo;

// descobrir qual o que se apaga: o actual ou o seu sucessor?


if( actual.esq == null || actual.dir == null ) aApagar = actual;
else aApagar = ((IteratorArvBinInt)sucessor( pos )).oNo;

if( aApagar.esq != null ) filho = aApagar.esq;


else filho = aApagar.dir;

pai = aApagar.pai;
if( filho != null ) filho.pai = pai;

if( pai == null ) raiz = filho;


else if( pai.esq == aApagar ) pai.esq = filho;
else pai.dir = filho;

if( aApagar != actual )


actual.oElemento = aApagar.oElemento; // copiar também a chave se houver
nElementos--;
}

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Devido à sua complexidade este método merece


ser explicado em pormenor:

public void eliminar( IteratorBidirec pos ){


No actual, aApagar, filho, pai ;
actual = ((IteratorArvBinInt)pos).oNo;

Variáveis:
actual é o nó que contém o elemento a apagar
aApagar é o nó que vai ser libertado
filho é o filho do nó que vai ser libertado
pai é o pai do nó que vai ser libertado
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Devido à sua complexidade esta função merece


ser explicada em pormenor:

if( actual.esq == null || actual.dir == null ) aApagar = actual;


else aApagar = ((IteratorArvBinInt)sucessor( pos )).oNo;

se ao nó actual falta pelo menos um filho é este o nó que vai ser libertado
senão o nó a apagar é o seu sucessor (ver exemplos)

if( aApagar.esq != null ) filho = aApagar.esq;


else filho = aApagar.dir;

o nó que se vai libertar tem, no máximo, um filho: o esquerdo ou o direito

pai = aApagar.pai;
if( filho != null ) filho.pai = pai;

Determinar o Pai do nó a libertar (se nó a apagar for a raiz, pai fica a null)
se houver um Filho actualiza-se o ponteiro Pai deste

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Devido à sua complexidade esta função merece


ser explicada em pormenor:

if( pai == null ) raiz = filho;


else if( pai.esq == aApagar ) pai.esq = filho;
else pai.dir = filho;

se Pai é null é porque se vai apagar a Raiz, logo actualiza-se a Raiz.


se não for tem-se de ver se o nó a libertar é o filho direito ou esquerdo,
para se actualizar o respectivo ponteiro

if( aApagar != actual )


actual.oElemento = aApagar.oElemento; // copiar também a chave se houver

Se o nó a libertar e o nó que continha o elemento forem diferentes é


necessário copiar o conteúdo do nó a libertar para o nó que continha o
elemento (caso haja chave é necessária copiá-la também)
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Por vezes é conveniente pensar que uma árvore


é composta por uma raiz e por várias sub-
árvores
 Assim pode-se usar a recursividade
 De seguida irá ver-se como se poderiam
implementar os métodos anteriores recorrendo à
recursividade
 Depois iremos acrescentar métodos cuja
implementação iterativa é complicada mas
recursivamente é simples

Árvores binárias
2007/08
A.E.D.
F.S.B.

 O único senão com os métodos recursivos é que


eles lidam directamente com os nós
 Mas nos cabeçalhos dos métodos NÃO se
podem colocar nós, porque estes são internos à
classe
 A solução é criar um método público de fachada
e outro recursivo como privado
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Inserir um elemento recursivamente:


 Se o novo elemento for maior que o da raiz colocar na
sub-árvore direita (se houver)
 Se o novo elemento for menor que o da raiz colocar o
elemento na sub-árvore esquerda (se houver)
 Caso não haja a sub-árvore inserir o novo elemento
na raiz

 Repare-se que a “raiz” não representa só a raiz da


árvore mas também a raiz de qualquer sub-árvore

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Inserir um elemento recursivamente:


 Vamos precisar de uma função pública para inserir

public void insere( int novo ) {


No novoNo = new No( novo );
insereRec( raiz, novoNo );
}
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Inserir um elemento recursivamente:


 E outra privada com a recursividade

private void insereRec( No raiz, No novoNo ) {


// ver se pode inserir nesta raiz
if( raiz == null )
raiz = novoNo;
else if( novoNo.oElemento > raiz.oElemento && raiz.dir != null )
insereRec( raiz.dir, novoNo );
else if( novoNo.oElemento <= raiz.oElemento && raiz.esq != null )
insereRec( raiz.esq, novoNo );
else {
// inserir neste nó
if( novoNo.oElemento > raiz.oElemento ) raiz.dir = novoNo;
else raiz.esq = novoNo;
novoNo.pai = raiz;
}
}

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Inserir um elemento recursivamente:


 Em relação à versão iterativa não traz qualquer
vantagem, nem em termos de execução nem em
termos de simplicidade
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Procurar um elemento recursivamente:


 Se o elemento for igual à raiz retornar a raiz
 Se a raiz for zero não encontrou
 Se o elemento for maior que o da raiz procurar na
sub-árvore direita
 Se o elemento for menor que o da raiz procurar na
sub-árvore esquerda

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Procurar um elemento recursivamente:


 só se apresenta a função recursiva

private IteratorBidirec procurarRec( No raiz, int procurado ){

if( raiz == null || raiz.oElemento == procurado )


return new IteratorArvBinInt( raiz );

if( procurado > raiz.oElemento )


return procurarRec( raiz.dir, procurado);
else
return procurarRec( raiz.esq, procurado);
}
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Procurar um elemento recursivamente:


 Em relação à versão iterativa não traz qualquer
vantagem em termos de execução e pouca em termos
de simplicidade

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Procurar o menor elemento recursivamente:


 Se houver sub-árvore esquerda retornar o menor da
sub-árvore esquerda
 Se não houver é o próprio elemento

 Procurar o maior elemento recursivamente:


 Se houver sub-árvore direita retornar o menor da sub-
árvore direita
 Se não houver é o próprio elemento
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Procurar o menor/maior elemento


recursivamente:
private IteratorBidirec menorRec( No raiz ){
if( raiz == null || raiz.esq == null )
return new IteratorArvBinInt( raiz );

return menorRec( raiz.esq );


}

private IteratorBidirec maiorRec( No raiz ){


if( raiz == null || raiz.dir == null )
return new IteratorArvBinInt( raiz );

return maiorRec( raiz.dir );


}

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Procurar o menor/maior elemento


recursivamente:
 Em relação à verão iterativa traz uma ligeira
vantagem em termos de simplicidade mas nenhuma
em termos de execução
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Sucessor e Predecessor:
 Estas não se prestam a serem recursivas uma vez
que a solução não pode ser dividida em casos
semelhantes

 Eliminar um elemento recursivamente:


 Esta também não se presta a ser recursiva
 Existem versões recursivas mas fazem uso de duas funções:
 Uma que procura qual o nó a remover e o remove caso
tenha um filho no máximo
 Uma que remove o nó caso tenha os dois filhos

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Funções em que se ganha se feitas


recursivamente:
 Eliminar uma árvore
 Percorrer todos os elementos de uma árvore:
 Por ordem
 Pré-ordem
 Pós-ordem
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Eliminar uma árvore:


 Necessário percorrer todos os elementos um a um
 Antes de eliminar um nó tem-se de eliminar os seus
filhos
 Mas para eliminar um dos filhos tem-se de eliminar os
filhos do filho
 Etc
 Por isso a recursividade é muito útil neste caso
 A alternativa seria muito complicada

Árvores binárias
2007/08
A.E.D.
F.S.B.

Raiz
 Eliminar uma árvore: 22

Eliminar primeiro a
sub-árvore esquerda 18 28
*
10 20 25
*
5 15 19 23 27
* * * * * * * * * *

Raiz Raiz Raiz


Eliminar primeiro a 18 10 5
sub-árvore esquerda
* *
10 20 5 15
* * * * * eliminar a raiz
5 15 19 Eliminar primeiro a
* * * * * * sub-árvore esquerda
Árvores binárias
2007/08
A.E.D.
F.S.B.

Depois eliminar a
 Eliminar uma árvore: sub-árvore direita
Raiz
Raiz Raiz Raiz
18
10 15 10
* * * **
10 20
5 15 5 15
eliminar a raiz
*
* * * * * * * *
Depois eliminar a 5 15 19
eliminar a raiz
sub-árvore direita * * * * * *
Raiz Raiz Raiz Raiz
eliminar a raiz
20 19 20 18
* * * ** * *
19 19 20
10
* * * *
Eliminar primeiro a 5 15 19
eliminar a raiz eliminar a raiz
sub-árvore esquerda * * * * * *

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Eliminar uma árvore:

Raiz
Depois eliminar a
22 sub-árvore direita

18 28
* * *
10 20 25
** * *
5 15 19 23 27
* * * * * * * * * *

e etc ...
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Eliminar uma árvore:


 Necessário percorrer todos os elementos um a um
 Antes de eliminar um nó tem-se de eliminar os seus
filhos
public void limpar( ) {
limpar( raiz );
}

private void limpar( No no ) {


if( no == null )
return;

limpar( no.esq );
limpar( no.dir );

no = null;
}

// em Java basta por raiz = null

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Percorrer todos os elementos de uma árvore por


ordem:
 Listar primeiro por ordem os valores da esquerda
 Depois listar o valor do nó
 A seguir listar por ordem os valores da direita

22 Raiz

18 28
*

10 20 25
* Listagem:
27 5 10 15 18 19 20 22 23 25 27 28
5 15 19 23
* * * * * * * * * *
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Percorrer todos os elementos de uma árvore por


ordem:
 Listar primeiro por ordem os valores da esquerda
 Depois listar o valor do nó
 A seguir listar por ordem os valores da direita
public void listarOrdem( ){
listarOrdem( raiz );
}

private void listarOrdem( No no ){


if( no == null )
return;

listarOrdem( no.esq );
System.out.println( no.oElemento );
listarOrdem( no.dir );
}

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Percorrer todos os elementos de uma árvore por


ordem:
 Solução iterativa:
 Determinar o menor e a partir dai determinar o sucessor
 Embora de pouca dificuldade seria menos eficaz

public void listarOrdemIterativa( ){


if( raiz == null )
return;

IteratorBidirec proximo = menor( );


while( proximo.hasItem() ){
System.out.println( proximo.oElemento );
proximo = sucessor( proximo );
}
}
Árvores binárias
2007/08
A.E.D.
F.S.B.

 Percorrer todos os elementos de uma árvore:


 Pré-ordem
 Processa-se o nó antes dos filhos
 Para cada nó imprimir o valor dele

 Depois imprimir o valor de cada um dos filhos

 Pós-ordem
 Processa-se o nó depois dos filhos
 Para cada nó imprimir o valor dos filhos

 Depois imprimir o valor do próprio nó

Árvores binárias
2007/08
A.E.D.
F.S.B.

 Percorrer todos os elementos de uma árvore por


pré-ordem e pós-ordem
private void listarPreOrdem( No no ){ private void listarPosOrdem( No no ){
if( no == null ) if( no == null )
return; return;

System.out.println( no.oElemento ); listarPosOrdem( no.esq );


listarPreOrdem( no.esq ); listarPosOrdem( no.dir );
listarPreOrdem( no.dir ); System.out.println( no.oElemento );
} }
22 Raiz Listagem pré-ordem:
22 18 10 5 15 20 19 28 25 23 27
18 28
*

10 20 25
Listagem pós-ordem:
*
5 15 10 19 20 18 23 27 25 28 22
5 15 19 23 27
* * * * * * * * * *
Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 Basicamente é um vector, fixo, onde se armazenam


elementos
 Cada elemento tem de estar associado a uma chave
 Pode ser um número (int ou long)
 Ou outro tipo qualquer, como uma string
 Assume-se que não existem dois elementos com a
mesma chave!
 Um elemento não é colocado na primeira posição livre
do vector, mas sim num índice determinado a partir da
sua chave
 É ao cálculo desse índice que se dá o nome de hash
 à função que faz essa conversão chama-se função de hashing

Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 As tabelas de hash são muito eficientes nas


operações de pesquisa
 As inserções são também muito rápidas
 Não permitem a ordenação de dados
2007/08
A.E.D.
F.S.B. Tabelas de Hash – Endereçamento directo
 Nestas tabelas o vector tem tantas posições
como o número de elementos a guardar
 Cada elemento tem assim uma posição pré-
determinada em função da sua chave

Elementos possíveis * 0 Tabela de hash de


1
1 1 Acesso directo
* 2
0 6 3 3

2 * 4
5 * 5
3
4 6 6
8 * 7
7
8 8

2007/08
A.E.D.
F.S.B. Tabelas de Hash – Endereçamento directo
 As operações de inserção, procura e eliminação
são assim extremamente rápidas
 Só dão para tabelas em que se sabe à partida
quantos elementos podem existir
 Só dão para números de elementos
relativamente pequenos
2007/08
A.E.D.
F.S.B. Tabelas de Hash – Endereçamento directo
 Classe para representar uma tabela de hash
com endereçamento directo
 Como se vai ver, nestes casos, não é necessário
guardar a chave junto com o elemento
 Para simplificar vai-se usar chaves inteiras

class TabelaHash {

// usar um array internamente


private Object aTabela[];

2007/08
A.E.D.
F.S.B. Tabelas de Hash – Endereçamento directo
 Criar uma tabela
 É necessário indicar o número de posições

class TabelaHash {

// usar um array internamente


private Object[] aTabela;

public TabelabHash( int nPos ){


// criar a tabela propriamente dita
aTabela = new Object[ nPos ];

// inicializar todas as posições a null (vazias)


// em java não é preciso
for( int i=0; i < aTabela.length; i++ )
aTabela[ i ] = null;
}
}
2007/08
A.E.D.
F.S.B. Tabelas de Hash – Endereçamento directo
 Inserir na tabela
 É necessário indicar o elemento e a chave
 A inserção é directa!

boolean inserir( int chave, Object oElem ) {

if( aTabela[chave] != null ) ) // já existe


return false;

aTabela[ chave ] = oElem;


return true;
}

2007/08
A.E.D.
F.S.B. Tabelas de Hash – Endereçamento directo
 Procurar na tabela
 É necessário indicar a chave a procurar
 A procura é directa!

Object procura( int chave ) {

return aTabela[ chave ];


}
2007/08
A.E.D.
F.S.B. Tabelas de Hash – Endereçamento directo
 Eliminar da tabela
 É necessário indicar a chave a eliminar
 A eliminação é directa!

boolean elimina( int chave ){

if( aTabela[ chave ] == null ) // ver se elemento não existe


return false;

aTabela[ chave ] = null;


return true;
}

2007/08
A.E.D.
F.S.B. Tabelas de Hash – Endereçamento directo
 Limpar a tabela

void limpar( ) {

for( int i = 0; i < aTabela.length; i++ )


aTabela[ i ] = null;

}
Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 Mas, e se o número de elementos a colocar na


tabela for maior que o número de elementos da
tabela?
 Tem-se de usar uma conversão da chave para a
posição da tabela
 É a esta conversão que se chama hashing
 A escolha de uma boa função de hashing é
fundamental

Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 Para o exemplo seguinte vai-se usar a função


 Hash1( x ) = x % 13

Tabela de hash
Elementos possíveis
0 13 Hash1( 13 ) = 0
1 1 Hash1( 1 ) = 1
2 28 Hash1( 28 ) = 2
54 2
20 50 1 3 3 Hash1( 3 ) = 3
17 3 * 4
31
6 * 5
0 22
8 6 6 Hash1( 6 ) = 6
32
43 * 7
5 13
8 8 Hash1( 8 ) = 8
4 28
* 9
7 23
* 10
* 11
* 12
Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 Para o exemplo seguinte vai-se usar a função


 Hash2( str ) = strlen( str ) % 7

Tabela de hash
Elementos possíveis
0 Métodos
e * 1
de 2 Ambientes

Micro 3 Computação
Desenho
Métodos 4 Programação
Gráfica
Computação 5 Micro
Análise
Ambientes * 6
Matemática Programação Hash2( “Métodos” ) = 0
Hash2( “Ambientes” ) = 2
Disciplina
Hash2( “Computação” ) = 3
Hash2( “Programação” ) = 4
Hash2( “Micro” ) = 5

Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 E o que acontece se dois elementos ficarem na


mesma posição?
 Dá-se uma colisão!
 Como se resolve?
 Primeiro vamos tecer algumas considerações
sobre funções de hashing
Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 As funções de hashing:
 Devem ser rápidas
 Devem evitar ao máximo as colisões:
 Cada posição da tabela deve ter a mesma probabilidade de
ser escolhida
 A escolha de uma dada função de hashing não é pois
uma decisão directa

Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 As funções de hashing numéricas:


 São muito utilizadas funções do género de Hash1
 Hash( x ) = x % valor

 Obtêm-se melhores resultados se valor for um


número primo
 Isso implica que se deve escolher um tamanho de tabela
(valor) que seja um número primo

 Ver na bibliografia outros tipos de funções


Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 As funções de hashing não numéricas:


 Faz-se a conversão para um valor numérico (exemplo
de hash2) e depois aplicam-se as funções numéricas.
 A função que faz a conversão terá de produzir valores
que sejam o mais diferentes possíveis de modo a
resultarem menores colisões
 A função hash2 não é muito razoável, para dicionários
por exemplo.
 como a tabela é pequena é muito provável que muitas strings
fiquem na mesma posição

Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 As funções de hashing não numéricas:


 Para strings pode-se também processar alguns (ou
todos) os caracteres de modo a produzir um valor

long hashing( String str ){


long resultado = 0;
for( int i=0; i < str.length(); i++) // ver todos os caracteres
resultado = resultado*16+str.charAt( i );
return resultado % 31; // aplicar o método numérico
}

long hashing( String str ) {


long resultado = 0;
for( int i=0; i<3 && i < str.length(); i++ ) // ver 3 caracteres
resultado = resultado*16+str.charAt( i );
return resultado % 31; // aplicar o método numérico
}
Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 Voltando ao problemas das colisões


 Como se resolve?
 Uma solução seria, em cada posição do vector,
colocar uma lista ligada de elementos
 Inserções à cabeça
 Cada novo elemento é adicionado à cabeça dessa lista
 Inserções ordenadas
 Cada novo elemento é adicionado na posição correcta da
lista
 O critério de ordenação será a chave

2007/08
A.E.D.
F.S.B.
Tabelas de Hash – inserção à cabeça
 Para o exemplo seguinte vai-se usar a função
 Hash1( x ) = x % 13

Tabela de hash
Elementos possíveis
* 0
1 14 1 *
2
54 * 2
20 50 1 3 3 *
17 3 * 4
31
6 * 5
0 22
8 6 19 6 *
32
43 * 7
5 14
8 8 *
4 19
* 9
7 23
* 10
* 11
* 12
2007/08
A.E.D.
F.S.B.
Tabelas de Hash – inserção ordenada
 Para o exemplo seguinte vai-se usar a função
 Hash1( x ) = x % 13

Tabela de hash
Elementos possíveis
* 0
1 1 14 *
2
54 * 2
20 50 1 3 3 *
17 3 * 4
31
6 * 5
0 22
8 6 6 19 *
32
43 * 7
5 14
8 8 *
4 19
* 9
7 23
* 10
* 11
* 12

Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 Inserção ordenada vs Inserção à cabeça


 A inserção à cabeça é mais rápida
 Não é preciso percorrer a lista para saber onde colocar o
elemento
 A inserção ordenada pode detectar se elemento já
existe
 Como percorre a lista pode detectar se já lá está, sem perda
de processamento
 A procura com inserção ordenada é mais rápida
 Já que não é preciso percorrer a lista toda para determinar se
o elemento lá está ou não
 A inserção ordenada nem sempre é possível
Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 Estrutura para representar uma tabela de hash


com listas
 Nestes caso são necessárias duas estruturas:
 Uma para a lista
 outra para a tabela
 Para efeitos de exemplo vai-se elaborar uma tabela
de automóveis
 A chave vai ser a Matrícula

Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 Classes para representar uma tabela de hash


com listas

class TabelaAuto {

private ListaAuto[] aTabela;

class ListaAuto {

private class No {
String chave; // matricula
Automovel oAuto;
No prox;
}

}
Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 Criar uma tabela de hash


 É necessário indicar o número de posições

class TabelaAuto {

private ListaAuto[] aTabela;

public TabelaAuto( int nPos ) {


// criar o array de listas
aTabela = new ListaAuto[ nPos ];

// inicializar cada posição da tabela


// com uma lista vazia
for( int i=0; i < aTabela.length; i++ )
aTabela[ i ] = new ListaAuto();
}

Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 Inserir na tabela
 É necessário indicar o elemento e a chave
 A inserção demora um tempo constante se for à
cabeça
 Se for ordenada varia de acordo com a ocupação da
lista
public boolean inserir( String chave, Automovel oAuto ) {

// fazer o hash da chave


int key = hashAuto( chave );

// ver se já existe na lista (não admite repetições)


if( aTabela[ key ].estaPresente( chave ) )
return false;

// adicionar a chave à lista


aTabela[ key ].inserirCabeca( oAuto );
return true;
}
Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 Função de hash
 É necessário criar uma função para fazer o hashing
da matricula
 Seria necessário testar várias funções para ver qual
daria os melhores resultados

private int hashAuto( String chave ) {

long resultado = 0;

// vêem-se 6 caracteres
for( int i = 0; i < 6 && i < chave.length(); i++ )
resultado = resultado * 32 + chave.charAt( i );

// aplicar o método numérico


return resultado % aTabela.length;
}

Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 Procurar na tabela
 É necessário indicar a chave a procurar
 A procura demora o tempo de procura numa lista
 Melhores resultados se for inserção ordenada

public Automovel procura( String chave ) {

int key = hashAuto( chave );

return aTabela[ key ].procura( chave );


}
Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 Eliminar da tabela
 É necessário indicar a chave a eliminar
 A eliminação demora o tempo de eliminação de uma
lista
 Melhores resultados se for inserção ordenada (por causa da
pesquisa do elemento)

public boolean elimina( String chave ) {

int key = hashAuto( chave );

return aTabela[ key ].elimina( chave );


}

Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 Destruir a tabela

public void limpar( ) {

for( int i = 0; i < aTabela.length; i++ )


aTabela[ i ].limpar();

}
Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 Usar a tabela

public static void main( String[] args ){

TabelaAuto osCarros = new TabelaAuto( 31 );

lerficheiro( “auto.txt”, osCarros );


fazerPesquisas( osCarros );
}

Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 Usar a tabela
int lerFicheiro( String nomefich, TabelaAuto carros ) {

// vai retornar o nº de carros lidos


int nAutos = 0;

// abertura do ficheiro …
while( temInput )
{
// ler dados do carro
Automovel carro = new Automovel();
carro.setMatricula( … );
carro.setProprietario( … );

// adicionar o carro à tabela de hash


carros.inserir( carro.getMatricula(), carro );
nAutos++;
}

return nAutos;
}
Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 Usar a tabela

void fazerPesquisas( TabelaAuto carros ) {

String matricula;
Automovel carro;

consola.print( “Introduza Matricula a pesquisar” );


matricula = consola.readString();
while( matricula.length() == 6 ) {
carro = carros.procura( matricula );
if( carro != null )
mostraInfoAuto( carro );
else
consola.println(“Não existe nenhum
carro com essa matricula\n\n”);

consola.print( “Introduza Matricula a pesquisar” );


matricula = consola.readString( );
}
}

Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 E se se pretender uma tabela para pesquisar,


mas desta vez usando o proprietário como
chave?
 É preciso fazer outra tabela?
 Não, e essa é uma das vantagens de usar chaves
 Desde que seja do mesmo tipo a mesma tabela dá
 Assume-se que não existem dois proprietários com o
mesmo nome! Ou que o mesmo proprietário tenha
dois automóveis!
Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 Usar a tabela
int lerFicheiro( String nomefich, TabelaAuto carros ) {

int nAutos=0;

while( temInput ) {

Automovel carro = new Automovel();


carro.setMatricula( … );
carro.setProprietario( … );

// inserir, agora com o proprietário como chave


carros.inserir( carro.getProprietario(), carro );

nAutos++;
}

return nAutos;
}

Tabelas de Hash
2007/08
A.E.D.
F.S.B.

 Usar a tabela

void fazerPesquisas( TabelaAuto carros ) {


String propri;
Automovel carro;

consola.print( “Introduza proprietário a pesquisar” );


propri = consola.readString( );
while( propri.length >= 3 ) {
carro = carros.procura( propri );
if( carro != null )
mostraInfoAuto( carro );
else
consola.println(“Não existe nenhum carro
com esse proprietario\n\n”);

consola.print( “Introduza proprietário a pesquisar” );


propri = consola.readString();
}
}

Você também pode gostar