CAPÍTULO 11 ALOCAÇÃO DINÂMICA DA MEMÓRIA
11.2 Listas
Para exemplificar a aplicação de ponteiros, apresentaremos a mais simples estrutura de dados cuja implementação através de alocação dinâmica é muito útil. Trata-se das listas simplesmente encadeadas que podem ser definidas como uma seqüência de elementos ligados através de ponteiros, sendo o número máximo de elementos da lista não fixado a priori. Usualmente, cada elemento da lista é uma variável do tipo record, com um campo contendo um ponteiro e os demais campos contendo os dados que o programa vai manipular. O primeiro elemento é associado a alguma variável do tipo ponteiro e o campo do tipo pointer do último elemento da lista tem conteúdo nil. Isto é indispensável para que se possua referência sobre o início e o final da lista.
Uma lista de inteiros, por exemplo, poderia ser representada graficamente da seguinte forma:
Inicio
o →
9
o →8
o →5
o →3
o ¬Para se criar a lista do exemplo anterior, necessitamos das seguintes definições de tipos e declarações de variáveis:
type TPonteiro = ^TElemento;
TElemento = record Dado : integer;
Prox : TPonteiro;
end;
var Inicio, p : TPonteiro;
Com estas definições e declarações, realizamos as seguintes etapas:
1. Para que se tenha referência ao primeiro elemento da lista e como no início a lista está vazia, fazemos Inicio := nil.
Inicio
o ¬
2. Para dar entrada no primeiro Dado, criamos um registro referenciado por p^, através da chamada do procedimento New(p).
Inicio
o ¬
P
o → o
3. Agora damos entrada no primeiro Dado, com readln(p^.Dado), e atribuímos p^.Prox := Inicio, cujo valor corrente é nil.
Inicio
o ¬
P
p
o → 3 o ¬
4. Em seguida, fazemos Inicio := p, para que Inicio referencie sempre o início da lista.
Inicio
o ¬
↓
o →
3
o ¬5. Para dar entrada em outro Dado, criamos um novo registro com New(p), lemos este outro Dado com readln(p^.Dado) e atualizamos p^.Prox := Inicio e Inicio := p.
Inicio
o ¬
o ¬
3
o ¬ ↓ ↑
5
oEvidentemente as etapas de índices 2 a 5 devem estar numa estrutura de repetição, permitindo que se forneçam todos os elementos da lista. A etapa 5 indica que o início da lista, o elemento referenciado por Inicio, é sempre o último elemento a dar entrada.
O procedimento seguinte traduz para o Pascal as idéias discutidas acima.
procedure CriaLista;
begin
Inicio := nil;
writeln('Digite os números ( -1 para encerrar)');
repeat New(p);
readln(p^.Dado);
if p^.Dado <> -1 then
begin
p^.Prox := Inicio;
Inicio := p;
end;
until p^.Dado = -1;
end;
Para se realizar uma pesquisa nesta lista, inicializamos p com o ponteiro Inicio (pois este aponta para o início da lista) e percorremos a lista até encontrar o elemento ou até que o valor de p seja nil, o que acontecerá quando o elemento pesquisado não for elemento da lista.
function Pesquisa(x : integer; var p, Anterior : TPonteiro) : boolean;
var Achou : boolean;
begin
Achou := false;
p := Inicio;
Anterior := Inicio;
while (p <> nil) and not Achou do if p^.Dado = x
then
Achou := true else
begin
Anterior := p;
p := p^.Prox;
end;
Pesquisa := Achou;
end;
Note que o procedimento retorna tanto o ponteiro que está apontando para o elemento (quando a busca é bem sucedida), como o ponteiro que aponta para o elemento anterior. Este ponteiro, Anterior, é útil para se efetuarem inserções e deleções em listas ordenadas.
O processamento de uma lista se faz de maneira natural. Fazemos p apontar para o início da lista e a percorremos realizando o processamento desejado até que não haja mais elementos, o que acontecerá quando o conteúdo de p for nil. Para exemplificar, apresentamos o procedimento abaixo que exibe todos os elementos da lista.
procedure EscreveLista;
begin
p := Inicio;
while p <> nil do begin
write(p^.Dado,' ');
p := p^.Prox;
end;
end;
A inserção de um elemento numa lista ordenada requer um procedimento que determine a posição de inserção, procedimento semelhante à função Pesquisa discutido acima.
procedure Posicao(var x : integer; var p, Anterior : TPonteiro);
begin
p := Inicio;
while (p <> nil) and (p^.Dado > x) do begin
Anterior := p;
p := p^.Prox;
end;
end;
Se a posição de inserção for anterior ao último elemento da lista, a inserção é feita antes do elemento apontado pelo ponteiro p obtido pelo procedimento Posicao. Neste caso, transfere-se o registro apontado por p para um ponteiro auxiliar Aux, faz-se o ponteiro deste elemento apontar para o registro apontado por Aux e armazena-se no campo denominado Dado do elemento apontado por p o elemento que se quer inserir. A figura a seguir indica graficamente estas operações se quiséssemos inserir x = 6.
p
o ¬
Inicio ↓
o → 9 o → 8 o → 5 o → 3 o ¬
Aux
o 5 o
p
o ¬
Inicio ↓
o → 9 o → 8 o → 6 o
3 o ¬
Aux ↓
o → 5 o
Se a inserção deve ser feita no final lista, p terá conteúdo nil e então o elemento deve ser inserido após o elemento apontado pelo ponteiro Anterior obtido também pelo procedimento Posicao. Neste caso, cria-se um novo elemento apontado por p, atribui-se o valor a ser inserido no campo Dado deste elemento e aponta-se o ponteiro deste elemento para o elemento apontado pelo campo ponteiro de Anterior.
Estas idéias estão implementadas no procedimento a seguir.
procedure InsereLista(x : integer);
var Aux, p, Anterior : TPonteiro;
begin
Posicao(x, p, Anterior);
if p <> nil then
begin
Aux^.Prox := p^.Prox;
p^.Prox := Aux;
Aux^.Dado := p^.Dado;
p^.Dado := x;
end else
begin New(p);
p^.Dado := x;
p^.Prox := Anterior^.Prox;
Anterior^.Prox := p;
end;
end;
Para finalizar, discutiremos a remoção de um elemento da lista. Se o elemento a remover é o primeiro elemento da lista, basta atualizar o ponteiro Inicio, apontando-o para o segundo elemento da lista. Se o elemento a remover não é o primeiro da lista, basta apontar o ponteiro do elemento apontado por Anterior para o elemento seguinte àquele apontado por p e liberar a variável apontada por p através do procedimento predefinido Dispose.
procedure Remove(var x : integer);
var Aux , p, Anterior : TPonteiro;
begin
if Pesquisa(x, p, Anterior) then
begin
if (Anterior = Inicio) and (p = Inicio) then
Inicio := p^.Prox else
Anterior^.Prox := p^.Prox;
Dispose(p);
end;
end;
Bibliografia
Dijkstra, E. W., A Discipline of Programiming. Prentice-Hall. New Jersey, 1975.
Evaristo, J. e Crespo, S, Aprendendo a Programar Programando numa Linguagem Algorítmica Executável (ILA). Book Express, Rio de Janeiro, 2000.
Evaristo, J., Aprendendo a Programar Programando em Linguagem C. Book Express, Rio de Janeiro, 2001.
Evaristo, J., Aprendendo a Programar Programando em Turbo Pascal. Editora da Universidade Federal de Alagoas (EDUFAL). Alagoas, 1996.
Evaristo, J., Introdução à Algebra (com aplicações à Ciência da Computação). Editora da Universidade Federal de Alagoas (EDUFAL). Alagoas, 1999.
Farrer, Harry e outros, Pascal Estruturado. Editora Guanabara. Rio de Janeiro, 1985.
Grogono, Peter, Programming in Pascal. Addison-Wesley. Massachusetts, 1985.
Knuth, D. E., The Art of Computer Programming, volume 2, Seminumerical Algorithms, Addison-Wesley Publishing Company. USA, 1988.
Kowaltowski, T. & Lucchesi, C., Conceitos Fundamentais e Teoria da Computação. Anais do II WEI. Minas Gerais, 1994
Mecler, Ian & Maia, Luiz Paulo, Programação e Lógica com Turbo Pascal. Editora Campus.
Rio de Janeiro, 1989.
Norton, P., Introdução à Informática. Makron Books. Sãp Paulo, 1996.
Rangel, J. L., Os Programas de Graduação em Linguagens de Programação. Anais do II WEI. Minas Gerais, 1994.
Schmitz, Eber Assis & Teles, A. S. de Souza, Pascal e Técnicas de Programação. LTC
Editora. Rio de Janeiro, 1988.
Szwarcfiter, J. L. & Markenzon, Estruturas de Dados e seus Algoritmos. LTC Editora. Rio de Janeiro, 1994.
Wirth, N., Algorithms & Data Structures. Prentice-Hall. New-Jersey, 1986.