Escolar Documentos
Profissional Documentos
Cultura Documentos
ACH2023 - ALGORITMOS E
ESTRUTURAS DE DADOS I
atualizada em 02/06/2015
1.
Introduo
Esta apostila apresenta uma coletnea de algoritmos sobre uma variedade de estruturas de dados de memria
principal estudadas na disciplina ACH2023 Algoritmos e Estruturas de Dados I, a saber:
Listas Lineares
Sequenciais
Ligadas
Implementao Esttica
Implementao Dinmica
Tcnicas Especiais: Cabea, Sentinela, Circularidade, Encadeamento Duplo
Filas
Implementao Esttica
Implementao Dinmica
Deques (Filas de duas Pontas)
Implementao Dinmica
Pilhas
Implementao Esttica
Implementao Dinmica
Implementao de Mltiplas Pilhas em um Vetor
Aplicaes
Matrizes Esparsas
Listas Generalizadas
Listas no Lineares
rvores Binrias
rvores de Busca Binria
rvores AVL
1.1.
Notao utilizada
Para simplificar o cdigo apresentado ao longo desta apostila, tornando-o o mais genrico possvel,
pressupe-se as duas definies a seguir:
// tamanho mximo do vetor esttico
# define MAX 50
2.
Listas Lineares
Uma lista linear uma srie de elementos ordenados na qual cada elemento exceto o primeiro possui um e
apenas um antecessor, e cada elemento exceto o ltimo possui um e apenas um sucessor.
2.1.
a lista linear na qual a ordem (lgica) dos elementos da lista coincide com sua posio fsica (em memria).
Ou seja, elementos adjacentes da lista ocupam posies contguas de memria.
A forma mais comum de implementao de uma lista sequencial atravs de um vetor de elementos (A) do
tipo REGISTRO de tamanho mximo possvel definido pela constante MAX.
O vetor conjugado a um contador de nmero de posies efetivamente ocupadas (nroElem). A ocupao do
vetor se d sempre em posies contguas, ou seja, nroElem-1 indica o ltimo elemento existente na estrutura.
typedef struct {
REGISTRO A[MAX];
int nroElem;
} LISTA;
Os registros contm campos de informaes variadas que so dependentes da aplicao. Por exemplo, um
registro de aluno conteria campos como nome, idade, NroUSP etc. Para efeito de demonstrao da busca em
estruturas ordenadas e outras operaes de identificao de elementos, definimos tambm um campo chave
em cada registro.
typedef struct {
int chave;
// outros campos...
} REGISTRO;
Vantagens:
Acesso direto a qualquer elemento com base no seu ndice. O tempo constante O(1). No entanto, em
muitas aplicaes o ndice do dado procurado no conhecido, o que faz desta uma vantagem apenas
relativa.
Se a lista estiver ordenada pela chave em questo, a busca por uma chave pode ser efetuada atravs de
busca binria O(lg n), que excelente.
Desvantagens:
Mesmo em uma estrutura ordenada, o pior caso de insero e excluso (na frente da lista) exige
movimentao de todos os n elementos da lista, ou seja, O(n). Isso pode ser inadequado para aplicaes em
que ocorrem muitas atualizaes, e principalmente por causa disso que a lista sequencial muitas vezes
substituda por uma estrutura de dados mais complexa.
Implementao esttica, exigindo que o tamanho do vetor seja previamente estabelecido. Embora
algumas linguagens de programao (como C) permitam a expanso posterior de um vetor deste tipo, esta
operao requer cpia de reas de dados inteiras em memria, a um custo O(n) que inviabiliza sua aplicao
no caso geral.
2.2.
Se a insero e excluso em listas sequenciais podem acarretar grande movimentao de dados, uma soluo
bvia permitir que os dados ocupem qualquer posio disponvel (e no necessariamente a posio fsica
correta), e ento criar um esquema para preservar a ordem dos elementos e gerenciamento de ns livres /
ocupados.
Uma lista linear ligada (ou simplesmente lista ligada) uma lista linear na qual a ordem (lgica) dos
elementos da lista (chamados ns) no necessariamente coincide com sua posio fsica (em memria).
Pode ser implementada de forma esttica (usando-se um vetor) ou, em linguagens de programao que
oferecem suporte alocao dinmica, com uso de ponteiros.
2.2.1.
A lista formada pelo vetor de registros (A), um indicador de incio da estrutura (inicio) e um indicador de
incio da lista de ns disponveis (dispo). Na prtica, inicio e dispo so as entradas de duas listas que
compartilham o mesmo vetor, sendo uma para os elementos efetivos da lista, e a outra para armazenar as
posies livres.
typedef struct {
REGISTRO A[MAX];
int inicio;
int dispo;
} LISTA;
Cada registro contm, alm dos campos exigidos pela aplicao, um campo prox que contm um ndice para
o prximo elemento na srie (vazio ou ocupado, conforme descrito a seguir). Um campo prox com valor -1
ser usado para designar que o elemento em questo no possui sucessor.
typedef struct {
int chave;
int prox;
} REGISTRO;
Ao ser criada, a lista possui inicio = -1 (que indica que a lista est vazia) e dispo = 0 (ou seja, a primeira
posio do vetor est disponvel). Alm disso, os campos prox de cada registro (exceto o ltimo) apontam
para o registro seguinte, constituindo uma lista de registros vazios encabeada por dispo. O campo prox do
ltimo registro recebe o valor -1 indicando que no h mais elementos depois daquele ponto.
A lista est cheia quando no h mais ns disponveis (i.e., quando dispo == -1). A lista esta vazia quando
no h elemento inicial (i.e., quando inicio == -1).
// Inicializao
void inicializarListaLigadaEstatica(LISTA *l) {
l->inicio = -1;
l->dispo = 0;
for(int i=0; i < MAX 1; i++)
l->A[i].prox = i + 1;
l->A[MAX 1].prox = -1;
}
// Exibio da lista
void exibirLista(LISTA l) {
int i = l.inicio;
while (i > -1) {
printf("%d ", l.A[i].chave);
i = l.A[i].prox;
}
}
// Busca sequencial
int buscaSeqOrd(int ch, LISTA l, int *ant) {
int i = l.inicio;
*ant= -1;
while (i != -1)
{
if(l.A[i].chave >= ch) break;
*ant = i;
i= l.A[i].prox;
}
if(i==-1) return -1;
If(l.A[i].chave==ch) return(i);
else return -1;
}
O gerenciamento de ns livres e ocupados exige a criao de rotinas especficas para alocar e desalocar
um n da lista apontada por dispo. A alocao envolve descobrir o ndice de uma posio vlida no vetor na
qual novos dados possam ser inseridos, alm de retirar esta posio da lista de disponveis. A desalocao
envolve a devoluo de uma posio lista de disponveis para que possa ser reutilizada.
2.2.2.
Para evitar a necessidade de definio antecipada do tamanho mximo da estrutura de implementao esttica
(i.e., o vetor), podemos tirar proveito dos recursos de alocao dinmica de memria das linguagens de
programao como C, deixando o gerenciamento de ns livres / ocupados a cargo do ambiente de
programao. Esta tcnica constitui implementao dinmica de listas ligadas, e requer o uso das funes
disponibilizadas em malloc.h:
# include <malloc.h>
Em uma lista ligada de implementao dinmica, no h mais uso de vetores. Cada elemento da lista uma
estrutura do tipo NO, que contm os dados de cada elemento (inclusive a chave) e um ponteiro prox para o
prximo n da lista. Um nome auxiliar (estrutura) usado para permitir a auto-referncia ao tipo NO que est
sendo definido.
typedef struct estrutura {
int chave;
int info;
estrutura *prox;
} NO;
O tipo LISTA propriamente dito simplesmente um ponteiro inicio apontando para o primeiro n da
estrutura (ou para NULL no caso da lista vazia). O ltimo elemento da lista possui seu ponteiro prox tambm
apontando para NULL. Embora no seja necessrio o encapsulamento deste ponteiro em um tipo LISTA
(afinal, uma lista apenas um ponteiro, que pode ser NULL ou no), esta medida ser adotada aqui por
questes de padronizao em relao aos tipos LISTA anteriores, e tambm porque alguns dos prximos
tipos agregaro mais informaes a esta definio.
typedef struct {
NO* inicio;
} LISTA;
A alocao e desalocao de ns feita dinamicamente pelo prprio compilador C atravs das primitivas
malloc e free, respectivamente.
NO* p = (NO*) malloc(sizeof(NO));
free(p);
Via de regra, malloc() usado em rotinas de insero ou criao de novos ns, enquanto que free() usado na
excluso. Rotinas que no criam ou destroem ns dificilmente precisam usar estas funes.
A nica diferena significativa entre as implementaes esttica e dinmica de listas ligadas est no fato de
que a implementao esttica simula uma lista ligada em vetor, e nos obriga a gerenciar as posies livres e
ocupadas. Isso deixa de ser necessrio no caso da alocao dinmica real.
10
// Inicializao
void inicializarLista(LISTA *l) {
l->inicio = NULL;
}
// Exibio da lista completa
void exibirLista(LISTA l) {
NO* p = l.inicio;
while (p)
{
printf("%d ",p->chave);
p = p->prox;
}
}
// Retornar o primeiro elemento da lista
NO* primeiroElemLista(LISTA l) {
return(l.inicio);
}
// Retornar o ltimo elemento da lista
NO* ultimoElemLista(LISTA l) {
NO* p = l.inicio;
if(p)
while(p->prox) p = p->prox;
return(p);
}
// Retornar o ensimo elemento da lista
NO* enesimoElemLista(LISTA l, int n) {
NO* p = l.inicio;
int i = 1;
if(p)
while((p->prox)&&(i<n))
{
p = p->prox;
i++;
}
if(i != n) return(NULL);
else return(p);
}
11
12
NO* novo;
NO* ant;
ant = ultimoElemLista(*l);
novo = (NO *) malloc(sizeof(NO));
novo->chave = ch;
novo->prox = NULL;
if(!ant) l->inicio = novo;
else ant->prox = novo;
}
// Excluso da chave dada
bool excluirElemLista(int ch, LISTA *l) {
NO* ant;
NO* elem;
elem = buscaSeqOrd(ch, *l, &ant);
if(!elem) return(false);
if(!ant) l->inicio = elem->prox;
else ant->prox = elem->prox;
free(elem);
return(true);
//
//
//
//
nada a excluir
exclui 1o. elemento da lista
exclui elemento que possui ant
excluso fsica
}
// Destruio da lista
void destruirLista (LISTA *l) {
NO* atual;
NO* prox;
atual = l->inicio;
while (atual) {
prox = atual->prox;
free(atual);
atual = prox;
}
l->inicio = NULL;
}
O n sentinela, criado ao final de uma lista, usado para armazenar a chave de busca e assim acelerar o
respectivo algoritmo, uma vez que reduz o nmero de comparaes necessrias pela metade.
typedef struct {
NO* inicio;
NO* sentinela;
} LISTA;
// Inicializao (lista com sentinela)
void inicializarLista(LISTA *l) {
l->sentinela = (NO*) malloc(sizeof(NO));
l->inicio = l->sentinela;
}
13
14
15
O n cabea, criado no incio de uma lista, usado para simplificar o projeto dos algoritmos de insero e
excluso. Pode tambm armazenar a chave de busca (funcionando como n sentinela) quando a lista for
circular.
A circularidade em geral exigncia da aplicao (e.g., que precisa percorrer continuamente a estrutura) mas
pode tambm facilitar inseres e excluses quando combinada com uso de um n cabea.
Os algoritmos a seguir so baseados em listas circulares com n cabea.
// Inicializao da lista circular e com n cabea
void inicializarLista(LISTA *l) {
l->cabeca = (NO*) malloc(sizeof(NO));
l->cabeca->prox = l->cabeca;
}
16
17
Quando necessitamos percorrer a lista indistintamente em ambas as direes, usamos encadeamento duplo
(ligando cada n ao seu antecessor e ao seu sucessor).
typedef struct estrutura {
int chave;
int info;
estrutura *prox;
estrutura *ant;
} NO;
typedef struct {
NO* cabeca;
} LISTA;
18
19
2.3.
Filas
Filas so listas lineares com disciplina de acesso FIFO (first-in, first-out, ou, primeiro a entrar o primeiro a
sair). Sua principal aplicao o armazenamento de dados em que importante preservar a ordem FIFO de
entradas e sadas.
O comportamento de fila obtido armazenando-se a posio das extremidades da estrutura (chamadas aqui
de fim e incio), e permitindo entradas apenas na extremidade fim e retiradas apenas na extremidade
incio.
A implementao pode ser esttica (usando um vetor circular) ou dinmica (com ponteiros) sem diferenas
significativas em termos de eficincia, uma vez que estas operaes s podem ocorrer nas extremidades da
estrutura.
2.3.1.
Implementao dinmica
20
2.3.2.
Implementao Esttica
typedef struct {
int chave;
} RegistroEstat;
typedef struct {
int inicio;
int fim;
RegistroEstat A[MAX];
} Festat;
// Inicializacao da fila esttica
void inicializarFestat(Festat *f) {
f->inicio = -1;
f->fim = -1;
}
// Inserir novo item ao final
bool entrarFestat(int ch, Festat *f) {
if(tamanhoFestat(*f) >= MAX) return(false);
f->fim = (f->fim + 1) % MAX;
f->A[f->fim].chave = ch;
if(f->inicio < 0 ) f->inicio = 0;
return(true);
}
21
2.4.
Deques so filas que permitem tanto entrada quanto retirada em ambas extremidades. Neste caso no faz mais
sentido falar em incio e fim de fila, mas simplesmente incio1 e incio2.
Implementaes estticas e dinmicas so possveis, mas a dinmica mais comum, tirando proveito do
encadeamento duplo para permitir acesso a ambas extremidades em tempo O(1).
typedef struct estrutura {
int chave;
estrutura *prox;
estrutura *ant;
} NO;
typedef struct {
NO* inicio1;
NO* inicio2;
} DEQUE;
// Inicializao do deque
void inicializarDeque(DEQUE *d) {
d->inicio1 = NULL;
d->inicio2 = NULL;
}
22
// j contm dados
// 1a. insero
23
2.5.
Pilhas
Pilhas so listas lineares com disciplina de acesso FILO (first-in, last-out, ou, o primeiro a entrar o ltimo a
sair). Da mesma forma que as filas, sua principal aplicao o armazenamento de dados em que importante
preservar a ordem (neste caso, FILO) de entradas e sadas.
A pilha armazena apenas a posio de uma de suas extremidades (chamada topo), que o nico local onde
so realizadas todas as operaes de entrada e sada. A operao de entrada de dados (sempre no topo da
pilha ) chamada push e a retirada (tambm sempre do topo) chamada pop.
A implementao pode ser esttica (usando um vetor simples) ou dinmica (com ponteiros) sem diferenas
significativas em termos de eficincia, uma vez que a estrutura s admite estas operaes no topo da
estrutura.
2.5.1.
Implementao dinmica
24
2.5.2.
Implementao Esttica
typedef struct {
int chave;
} RegistroEstat;
typedef struct {
int topo;
RegistroEstat A[MAX];
} PESTAT;
// Inicializao da pilha esttica
void inicializarPestat(Pestat *p) {
p->topo = -1;
}
// A pilha esttica est cheia ?
bool pilhaCheia(Pestat p) {
if( p.topo >= MAX - 1 ) return(true);
else return(false);
}
// Inserir no topo da pilha esttica
bool push(int ch, Pestat *p) {
if( tamanhoPestat(*p) >= MAX ) return(false);
p->topo++;
p->A[p->topo].chave = ch;
return(true);
}
// Retirar do topo ou retornar -1 se vazia
int pop(Pestat *p) {
if(p->topo < 0) return (-1);
int ch = p->A[p->topo].chave;
p->topo--;
return(ch);
}
25
2.5.3.
Duas pilhas de implementao esttica que no necessitam de toda sua capacidade simultaneamente podem
ser representadas economicamente em um nico vetor compartilhado.
As pilhas so posicionadas nas extremidades do vetor e crescem em direo ao centro. Supondo-se um vetor
com posies indexadas de 0 at MAX-1, o topo da primeira pilha inicializado com -1 e o topo da segunda
inicializado com MAX, correspondendo as situaes de pilha vazia de cada estrutura. As duas pilhas esto
simultaneamente cheias quando no h mais posies livres entre elas, ou seja, quando (topo2 - topo1 == 1).
typedef struct {
int topo1;
int topo2;
int A[MAX];
} PILHADUPLA;
// Inicializacao da pilha dupla
void inicializarPilhaDupla(PILHADUPLA *p) {
p->topo1 = -1;
p->topo2 = MAX;
}
// Quantos elementos existem na pilha k (1 ou 2)
int tamanhoPilhaDupla(PILHADUPLA p, int k) {
if( k == 1 ) return(p.topo1 + 1);
else return(MAX - p.topo2);
}
// O vetor est cheio ?
bool vetorPilhaCheio(PILHADUPLA p) {
if(p.topo1 == (p.topo2 - 1) ) return(true);
else return(false);
}
26
2.5.4.
O caso geral de representao esttica de 'NP' pilhas compartilhando um nico vetor envolve o controle
individual do topo de cada pilha e tambm da sua base. Cada topo [k] aponta para o ltimo elemento efetivo
de cada pilha (i.e., da mesma forma que o topo de uma pilha comum) mas, para que seja possvel diferenciar
uma pilha vazia de uma pilha unitria, cada base [k] aponta para o elemento anterior ao primeiro elemento
real da respectiva pilha.
Uma pilha k est vazia se base[k] == topo[k]. Uma pilha [k] est cheia se topo[k] == base[k+1], significando
que a pilha [k] no pode mais crescer sem sobrepor-se a pilha [k+1].
# define MAX 15
# define NP 5
// tamanho do vetor A
// nro. de pilhas compartilhando o vetor (numeradas de 0..NP-1)
A especificao da estrutura inclui as NP pilhas reais (numeradas de 0 at NP-1) e mais uma pilha 'extra'
(fictcia) de ndice NP cuja funo descrita a seguir.
typedef struct {
int base[NP+1]; // pilhas [0..NP-1] + pilha[NP] auxiliar
int topo[NP+1];
int A[MAX];
} PILHAS;
27
Na inicializao da estrutura, cada topo igualado a sua respectiva base, o que define uma pilha vazia. Alm
disso, para evitar acmulo de pilhas em um mesmo ponto do vetor (o que ocasionaria grande movimentao
de dados nas primeiras entradas de dados), todas as NP+1 pilhas so inicializadas com suas bases distribudas
a intervalos regulares ao longo da estrutura.
A pilha[0] fica na posio 1. A pilha[NP] - que apenas um artifcio de programao - fica
permanentemente na posio MAX-1, servindo apenas como marcador de fim do vetor (i.e., esta pilha jamais
ser afetada pelas rotinas de deslocamento). A pilha extra ser sempre vazia e ser usada para simplificar o
teste de pilha cheia, que pode se basear sempre na comparao entre um topo e a base da pilha seguinte, ao
invs de se preocupar tambm com o fim do vetor.
// Inicializacao da pilha mltipla
void inicializarPilhas(PILHAS *p) {
int i;
for(i = 0; i <= NP ; i++) {
p->base[i] = ( i * (MAX / NP) ) - 1;
p->topo[i] = p->base[i];
}
}
// Quantos elementos existem na pilha k
int tamanhoPilhaK(PILHAS p, int k) {
return(p.topo[k] - p.base[k]);
}
A utilidade da pilha fictcia de ndice [NP] fica claro na rotina a seguir, a qual no precisa se preocupar com
o caso especial em que k a ltima pilha real (i.e., quando k == NP-1).
// A pilha k esta cheia ?
bool pilhaKcheia(PILHAS p, int k) {
if(p.topo[k] == p.base[k + 1])
return(true);
else
return(false);
}
28
A incluso de um novo elemento no topo de uma pilha k deve considerar a existncia de espao livre e, se for
o caso, movimentar as estruturas vizinhas para obt-lo. No exemplo a seguir, o programa tenta deslocar todas
as pilhas direita de k em uma posio para a direita. Se isso falhar, tenta deslocar todas as pilhas da
esquerda (inclusive k) uma posio para a esquerda. Se isso ainda no resultar em uma posio livre no topo
de k, ento o vetor est totalmente ocupado e a insero no pode ser realizada.
// Inserir um novo item no topo da pilha k
bool pushK(int ch, PILHAS *p, int k) {
int j;
if( (pilhaKcheia(*p, k)) && (k < NP-1) )
// desloca p/direita todas as pilhas de [k+1..NP-1] em ordem reversa
for( j = NP-1; j > k; j--) paraDireita(p, j);
if( (pilhaKcheia(*p, k)) && (k > 0))
// desloca p/esquerda todas as pilhas de [1..k] (mas no a pilha 0)
for( j = 1; j <= k; j++) paraEsquerda(p, j);
if(pilhaKcheia(*p, k)) return(false);
p->topo[k]++;
p->A[p->topo[k]] = ch;
return(true);
}
Observe que este apenas um exemplo de estratgia possvel. Um procedimento talvez mais eficiente poderia
tentar primeiro deslocar apenas a pilha k+1 para direita. Apenas quando isso falhasse poderia ento tentar
deslocar apenas as pilha k-1 e a pilha k para a esquerda, e s em caso de ltima necessidade tentaria
deslocaria vrias pilhas simultaneamente como feito acima.
// Retirar um item da pilha k, ou -1
int popK(PILHAS *p, int k) {
int resp = -1;
if( (p->topo[k] > p->base[k]) )
resp = p->A[p->topo[k]];
p->topo[k]--;
}
return(resp);
}
29
2.6.
Matrizes Esparsas
Uma matriz esparsa uma matriz extensa na qual poucos elementos so no-nulos (ou de valor diferente de
zero). O problema de representao destas estruturas consiste em economizar memria armazenando apenas
os dados vlidos (i.e., no nulos) sem perda das propriedades matriciais (i.e., a noo de posio em linha e
coluna de cada elemento). As implementaes mais comuns so a representao em linhas ou por listas
cruzadas.
2.6.1.
A forma mais simples (porm no necessariamente mais eficiente) de armazenar uma matriz esparsa na
forma de uma tabela (na verdade, uma lista ligada) de ns contendo a linha e coluna de cada elemento e suas
demais informaes. A lista ordenada por linhas para facilitar o percurso neste sentido.
A matriz ser acessvel a partir do ponteiro de incio da lista ligada que a representa.
typedef struct estrutura {
int lin;
int col;
TIPOINFO info;
estrutura *prox;
} NO;
typedef struct {
NO* inicio;
} MATRIZ;
30
A economia de espao desta representao bastante significativa. Para uma matriz de MAXLIN x
MAXCOL elementos, dos quais n so no-nulos, seu uso ser vantajoso (do ponto de vista da complexidade
de espao) sempre que:
(MAXCOL*MAXLIN*sizeof(TIPOINFO)) > (n*(sizeof(NO))
Lembrando que um n composto de dois inteiros, um TIPOINFO e um NO*.
Por outro lado, a representao por linhas no apresenta bom tempo de resposta para certas operaes
matriciais. Em especial, percorrer uma linha da matriz exige que todas as linhas acima dela sejam percorridas.
Pior do que isso, percorrer uma coluna da matriz exige que a matriz inteira (ou melhor, todos os seus
elementos no-nulos) seja percorrida. Considerando-se que matrizes esparsas tendem a ser estruturas de
grande porte, em muitas aplicaes estes tempos de execuo podem ser inaceitveis.
2.6.2.
Com um gasto adicional de espao de armazenamento, podemos representar uma matriz esparsa com tempo
de acesso proporcional ao volume de dados de cada linha ou coluna. Nesta representao, usamos uma lista
ligada para cada linha e outra para cada coluna da matriz. As listas se cruzam, isto , compartilham os
mesmos ns em cada interseco, e por este motivo cada n armazena um ponteiro para a prxima linha
(abaixo) e prxima coluna ( direita).
1
1
2
3
4
5
6
D
E
G H
7
8
31
O acesso a cada linha ou coluna indexado por meio de um vetor de ponteiros de linhas e outro de colunas.
Cada ponteiro destes vetores indica o incio de uma das listas da matriz.
typedef struct {
NO* lin[MAXLIN+1]; // para indexar at MAXLIN
NO* col[MAXCOL+1]; // para indexar at MAXCOL
} MATRIZ;
Para uma matriz de MAXLIN x MAXCOL elementos, dos quais n so no-nulos, a representao por listas
cruzadas ser vantajosa (do ponto de vista da complexidade de espao) sempre que:
(MAXCOL*MAXLIN*sizeof(TIPOINFO)) > {n*(sizeof(NO))+sizeof(NO*)*(MAXLIN+MAXCOL)}
Lembrando que um n composto de dois inteiros, um TIPOINFO e dois ponteiros.
// Inicializao
void inicializarMatriz(MATRIZ *m) {
int i;
for(i=1; i <= MAXLIN; i++)
m->lin[i] = NULL;
for(i=1; i <= MAXCOL; i++)
m->col[i] = NULL;
}
}
// Conta elementos da estrutura
int contaElementos(MATRIZ m) {
int i, t;
NO* p;
t = 0;
for(i=1; i<= MAX; i++) {
p = m.lin[i];
while (p) {
t++;
p = p->proxC;
}
}
return(t);
}
32
if(p2)
{
if(p2->lin < atual) {
p2 = p2->proxL;
continue;
}
if(p2->lin == atual)
v2 = p2->chave;
}
printf("Linha %d*%d=%d\n", v1, v2, v1*v2);
atual++;
}
if(atual < MAXLIN+1) for(; atual <= MAXLIN; atual++)
printf("Linha %d*%d=%d\n", atual,0,0);
}
33
triangular superior
triangular inferior
34
2.7.
Listas Generalizadas
So listas contendo dois tipos de elementos: elementos ditos normais (e.g., que armazenam chaves) e
elementos que representam entradas para sublistas. Para decidir se um n armazena uma chave ou um
ponteiro de sublista, usamos um campo tag cuja manuteno responsabilidade do programador.
Dependendo do valor de tag, armazenamos em um campo de tipo varivel (um union em C) o dado
correspondente. Para evitar a criao de cdigos especiais para o tag, usamos a seguir uma enumerao dos
dois valores possveis (elemLista, inicioLista) para variveis do tipo IDENT. Note no entanto que o uso de
um tipo enumerado no obrigatrio e de fato no tem relao com o uso de union.
typedef enum{elemLista, inicioLista} IDENT;
typedef struct estrutura {
IDENT tag;
union {
int chave;
struct estrutura *sublista;
};
estrutura *prox;
} NO;
// Inicializao
void inicializarLista(NO* *p) {
*p = NULL;
}
// Quantidade de chaves na lista
int contarChaves(NO* p) {
int chaves = 0;
while (p)
{
if( p->tag == elemLista)
chaves++;
else
chaves = chaves + contarChaves(p->sublista);
p = p->prox;
}
return(chaves);
}
35
36
37
3.
Listas No Lineares
Quando existe mais de um caminho possveis pela estrutura, esta dita no linear. Exemplos clssicos de
estruturas deste tipo so as rvores e grafos (estes estudados em Algoritmos e Estruturas de Dados II).
3.1.
rvores
Terminologia
Grau de um n: a quantidade de subrvores do n;
Grau de uma rvore: grau mximo dentre todos os ns da estrutura;
Folhas de uma rvore: ns de grau zero;
Filhos de x: razes das subrvores de x; x o n pai de seus filhos;
Ancestrais de x: todos ns no caminho desde a raiz at x.
Nvel de x: a raiz nvel 1; se um n est no nvel n, seus filhos esto no nvel n+1;
Altura de um n folha sempre 1;
Altura de um n no folha: a altura mxima dentre todas suas subrvores + 1;
Altura de uma rvore a altura de sua raiz.
Uma rvore de grau m dita m-ria. rvores m-rias so de difcil representao e manipulao (por
exemplo, a definio de muitos ponteiros em cada n representa um grande desperdcio de espao ocupado
por ponteiros NULL). Por este motivo, rvores m-rias so geralmente representadas por rvores binrias
(veja definio a seguir) sem perda de propriedades.
Em computao, rvores (e especialmente rvores binrias) so usadas para armazenar dados (chaves e outros
campos de informao) em seus ns da mesma forma que listas sequenciais e listas ligadas.
38
39
Propriedades
- O nmero mximo de ns possveis no nvel i 2 i-1
40
41
- Uma rvore binria de altura mxima para uma quantidade de n ns dita assimtrica. Neste caso, a altura
h = n e seus ns interiores possuem exatamente uma subrvore vazia cada.
42
Implementao Esttica
Embora a implementao dinmica seja mais comum, rvores binrias podem ser representadas em um vetor.
Isso especialmente til em aplicaes que no sofrem grande volume de inseres e excluses; nos demais
casos a representao dinmica continua sendo a preferida.
Na representao esttica, o vetor deve ser grande o suficiente para conter o nmero mximo possvel de ns
para a altura mxima hmax estabelecida, ou seja, deve ter no mnimo 2hmax 1 posies. Isso necessrio
porque mesmo os ns no existentes tero seu espao reservado no vetor, j que a posio relativa de
qualquer pai ou filho no vetor fixa, definida em funo das posies de seus antecessores.
Os ns da rvore so dispostos ao longo do vetor em nveis, da esquerda para a direita, comeando pela raiz
(que ocupa a primeira posio). Como os ns inexistentes deixam posies vazias no vetor, algum tipo de
controle deve ser feito para diferenciar posies livres e ocupadas (e.g., com uso de um campo booleano).
Uma vez que a raiz ocupa a primeira posio do vetor, os outros ns tm suas posies definidas como segue:
- pai(i) = cho (i / 2)
- filho_esq(i) = 2 * i
- filho_dir(i) = (2 * i) + 1
se i == 0, no h pai
se i > n, no h filho esquerdo
se i > n, no h filho direito
Implementao Dinmica
typedef struct estrutura {
int chave;
estrutura *esq;
estrutura *dir;
} NO;
// Inicializao da rvore vazia
void inicializarArvore(NO* *raiz) {
*raiz = NULL;
}
43
44
Pr-ordem:
Em-ordem:
Ps-ordem:
CBAEDF
ABCDEF
ABDFEC
Algoritmos no-recursivos
Os percursos bsicos em rvore binria (em especial, o de pr-ordem e o em ordem) podem ser obtidos com
uma implementao no recursiva usando uma estrutura auxiliar do tipo pilha.
// Percurso pr-ordem no-recursivo
void preOrdemNaoRecurs(NO* p) {
PILHA pi;
inicializarPilha(&pi); // deve ser uma pilha de ponteiros de ns da rvore
while (true) {
while (p) {
visita(p);
if(p->dir) push(p->dir, &pi); // memoriza caminho direita
p = p->esq;
}
if(tamanhoPilha(pi) > 0)
p = pop(&pi);
else break;
}
}
45
Algoritmos recursivos
Os percursos bsicos em rvore binria so facilmente obtidos com uma implementao recursiva.
void preOrdem(NO* p)
{
if(p)
{
visita(p);
preOrdem(p->esq);
preOrdem(p->dir);
}
}
void emOrdem(NO* p)
{
if(p)
{
emOrdem(p->esq);
visita(p);
emOrdem(p->dir);
}
}
void posOrdem(NO* p)
{
if(p)
{
posOrdem(p->esq);
posOrdem(p->dir);
visita(p);
}
}
46
Outros percursos
Em altura:
Em nvel:
DGHFBECA
ABCDEFGH
Os algoritmos para estes percursos no so recursivos. O percurso em nvel, por exemplo, mais facilmente
obtido com uma estrutura auxiliar do tipo fila, usada para armazenar os filhos de cada n visitado e ento
visit-los na ordem em que so retirados da mesma.
// Percorre a rvore em nvel e exibe
void exibirArvoreEmNivel(NO* raiz) {
FILA f;
NO* p = raiz;
inicializarFila(&f);
while( (p) || (f.inicio) )
{
if(p->esq) entrarFila(p->esq, &f);
if(p->dir) entrarFila(p->dir, &f);
printf("%d", p->chave);
p = NULL;
if(f.inicio) p = sairFila(&f);
}
}
47
48
49
50
Uso de sentinela: para reduzir o nmero de comparaes na busca, possvel criar (no momento da
inicializao da rvore) um n sentinela para o qual todos os ponteiros NULL da estrutura so direcionados.
No momento da busca, a chave em questo colocada no n sentinela e assim no se faz necessrio testar a
condio de fim da estrutura, pois a chave ser sempre encontrada (ou na sua posio real ou no sentinela).
// Inicializacao - sentinela
void inicializar(NO* *raiz, NO* sentinela) {
*raiz = sentinela;
}
// Testa se rvore com sentinela vazia
bool vazia(NO* raiz, NO* sentinela) {
if (raiz == sentinela)
return(true);
else
return(false);
}
// Busca binria com sentinela devolvendo o n pai
NO* buscaComSentinela(NO* raiz, NO* sentinela, int ch, NO* *pai) {
NO* atual = raiz;
*pai = NULL;
sentinela->chave = ch;
while (atual->chave != ch)
{
*pai = atual;
if(ch < atual->chave)
atual = atual->esq;
else
atual = atual->dir;
}
if(atual == sentinela)
return(NULL);
else
return(atual);
}
A eficincia da busca em uma ABB com ou sem sentinela determinada pela altura da estrutura. Uma rvore
completa (de altura mnima da ordem lg n) permite uma busca de pior caso O(lg n), ou seja, to eficiente
quanto busca binria em vetor. Por outro lado, uma rvore assimtrica (de altura mxima da ordem n) exige
uma busca de pior caso O(n), ou seja, o mesmo que em uma lista ligada.
rvores completas so entretanto difceis de manter, ou seja, garantir que uma ABB continue sendo completa
depois de cada insero ou excluso pode exigir a movimentao de todos os ns da estrutura em tempo O(n),
ou seja, o mesmo problema que era verificado em listas sequenciais (vetores). No exemplo a seguir a insero
da chave 0 demonstra porque restaurar a condio de rvore completa invivel na prtica.
51
52
53
denominado v, tambm localizado em direo ao n x, que passa a ser a nova raiz da subrvore em questo
(ou seja, substituindo p). A figura a seguir ilustra a modificao da estrutura aps cada uma das rotaes.
54
//
//
//
//
55