Universidade Castelo Branco - Algoritmos e Estruturas de Dados II 1. Recursão 1.1 Definição Um objeto é dito recursivo se ele consistir parcialmente ou for definido em termos de si próprio. Recursões ocorrem na matemática, informática, no dia a dia... Exemplos de recursão em definições matemáticas: – Números naturais: • 0 é um número natural; • O sucessor de um número natural é um número natural; – Função Fatorial (n!) para inteiros positivos: • 0! = 1 • n>0: n! = n * (n - 1)! 1.2 Algoritmos Recursivos Um algoritmo que para resolver um problema divide-o em subproblemas mais simples, cujas soluções requerem a aplicação dele mesmo, é chamado recursivo. Em programação, uma subrotina (procedimento ou função) é recursiva quando ela chama a si mesma. Suponha uma rotina recursiva R formada por um conjunto de comandos C (que não contém chamadas a R) e uma chamada (recursiva) à R: 1.3 Tipos de Recursão Recursão Direta É quando em uma subrotina existe uma chamada para a própria subrotina, independentemente dos valores dos parâmetros: Prof. Cláudio H. S. Grecco 1 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II Recursão Indireta As subrotinas são conectadas através de uma cadeia de chamadas sucessivas que acaba retornando à primeira que a desencadeou: 1.4 Funcionamento da Recursão A subrotina permite que seja dado um nome a um conjunto de comandos. Um desses comandos pode ser a chamada à própria subrotina. As subrotinas possuem objetos locais sem significado fora dela (variáveis, parâmetros, constantes, tipos e subrotinas). Toda vez que tal subrotina for executada recursivamente, um novo conjunto de variáveis locais e parâmetros são criados. Ainda que variáveis e parâmetros tenham o mesmo nome os identificadores se referem ao conjunto criado mais recentemente (seus valores são diferentes). Como ocorre nos comandos repetitivos, as chamadas recursivas possibilitam a não terminação (looping). Para tal, deve ser condicionado uma expressão lógica que, em algum instante, tornar-se-á false e permitirá que a recursão termine: R [C,TR], sendo T um teste lógico Técnica básica para garantir o término: definir uma função f(x), sendo x um conjunto de variáveis, tal que f(x) 0 implica em uma condição de terminação; garantir que f(x) descresce a cada passo da repetição. R [C, (f(x)>0)R(x-1)], onde x decresce a cada chamada Na prática, ao definir rotinas recursivas, o problema é dividido da seguinte forma: – solução trivial: dada por definição, isto é, não necessita de recursão (resolvida pelo conjunto de comandos C); – solução geral: parte do problema que em essência é igual ao problema original, porém menor (resolvida pela chamada recursiva R). Prof. Cláudio H. S. Grecco 2 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II Um teste define, a cada momento, se o problema terá solução trivial ou geral. Em termos matemáticos, a recursão é uma técnica que, através de substituições sucessivas, reduz o problema a um caso de solução trivial Exemplo de Recursão: fatorial Cálculo da Fatorial (números naturais positivos): – solução trivial: 0! = 1 (condição de parada) – solução geral: n! = n*(n-1)! Algoritmo em Pascal para cálculo da fatorial FUNCTION fat(n: INTEGER):INTEGER; BEGIN IF n= 0 THEN fat:= 1 ELSE fat:= n * fat (n-1); END; 1.5 Profundidade da Recursão Profundidade é o número de vezes que uma rotina recursiva chama a si própria, até obter o resultado. Muitas vezes a profundidade de uma recursão não é tão clara, até mesmo para definições simples. Exemplo: profundidade da fatorial de 4: – Valores de n: da chamada é 4, do 1o nível é 3, do 2o nível é 2 do 3o nível é 1 e do 4o nível é 0. Prof. Cláudio H. S. Grecco 3 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II 1.6 Exemplo de recursão: Torres de Hanoi Enunciado do problema “Há muito tempo atrás, no alto das montanhas de Hanoi, havia um mosteiro onde habitavam sacerdotes brâmanes; entre eles, era praticado um ritual para predizer o fim do mundo. Conta a lenda, que no mosteiro havia três torres, sendo que na primeira delas estavam empilhados 64 discos de ouro em tamanhos decrescentes. Os sacerdotes acreditavam que quando eles terminassem de transferir todos os discos da primeira torre para a terceira (usando a segunda), sem nunca colocar um disco maior sobre um menor, então, neste dia, o mundo acabaria!” Para facilitar, vamos supor a existência de três discos: O objetivo é transferir os três discos da torre A para a torre C, usando a torre B como auxiliar. Somente o primeiro disco de uma torre pode ser deslocado para outra, e um disco maior nunca pode ser colocado sobre outro menor. Processo para transferir os três discos: Matematicamente é possível demonstrar que o tempo necessário para mover n discos é da ordem de n! (ou maior). Prof. Cláudio H. S. Grecco 4 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II • Solução usando recursão: – solução trivial: Se n=1, transfira o disco da torre A para a torre C; – solução geral: a) transfira n-1 discos da torre A para a torre B, usando C como auxiliar; b) transfira o último disco da torre A para a torre C; c) transfira n-1 discos da torre B para torre C, usando A como auxiliar. Algoritmo em Pascal para a solução do problema PROCEDURE Hanoi(n:INTEGER; Org,Aux,Dst:CHAR); BEGIN IF n= 1 THEN writeln(‘Mova disco 1 da torre ’,Org, ‘ para ‘, Dst) ELSE BEGIN Hanoi(n-1,Org,Dst,Aux); writeln(‘Mova disco ‘,n,’ da torre ‘,Org, ‘ para ‘, Dst); Hanoi(n-1,Aux,Org,Dst); END; END; Quando a chamada Hanoi (3,’A’,’B’,’C’) for executada, será produzida a seguinte saída: Mova disco 1 da torre A para C Mova disco 2 da torre A para B Mova disco 1 da torre C para B Mova disco 3 da torre A para C Mova disco 1 da torre B para A Mova disco 2 da torre B para C Mova disco 1 da torre A para C 1.7 Uso da Recursão Nem sempre é recomendado o uso de recursão. Alguns problemas são impossíveis de solucionar com recursão. Problemas: – Custo de tempo e espaço: Cada vez que a subrotina é chamada, todas as variáveis locais são recriadas (gerenciamento do registro de ativação e espaço por ele ocupado); – Dificuldade na depuração de programas, particularmente se a recursão for profunda. Prof. Cláudio H. S. Grecco 5 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II • Por que usar recursão? Se bem empregada, torna o algoritmo muito elegante, isto é, claro, simples e conciso. Algoritmos recursivos são apropriados quando o problema for definido em termos recursivos. Programas onde a recursão deve ser evitada podem ser evitados por um esquema que exiba o padrão da sua composição: R [TC,R] R [C,TR] – a característica desses programas é possuir uma só chamada a R no final (chamada de recursão de cauda). – os programas que se encaixam nesse esquema, como o exemplo do cálculo da fatorial, possuem uma solução iterativa (não recursiva) mais eficiente. 1.8 Recursão de Cauda Na recursão de cauda a chamada recursiva está no final do código, tendo como função criar um laço que será repetido até a condição de parada. Para eliminar a recursão de cauda: – Se uma rotina R(x) tem como última instrução uma chamada recursiva R(y), então troca-se R(y) pela atribuição xy, seguido de um desvio para o início de R. – Utiliza-se uma repetição condicionada à expressão de teste usada na versão recursiva. 1.9 Versão Iterativa do Cálculo de fatorial FUNCTION fat(n:INTEGER):INTEGER; VAR result:INTEGER; BEGIN result:=1; WHILE n>0 DO BEGIN result:= result*n; n:= n-1; END; fat := result; END; – Embora fatorial seja mais eficiente de forma iterativa, é um excelente exemplo para se entender o que é recursão. Prof. Cláudio H. S. Grecco 6 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II 2. Listas Lineares São estruturas dinâmicas caracterizadas por manter uma seqüência ordenada de elementos. São compostas por elementos que podem ser dados primitivos ou estruturados. Os elementos são chamados de nodos. – Seja uma lista X, formada por nodos X1,X2, ... Xn, tal que: 1) Existem “n” nodos na lista (n>=0); 2) X1 é o primeiro nodo; 3) Xn é o último nodo; 4) Para todo i,j entre 1 e n, se i<j, então o elemento Xi antecede Xj; 5) Caso i = j -1, Xi é o antecessor de Xj e Xj é o sucessor de Xi. 6) Quando a lista não possui nodos (n=0) a lista é dita vazia. • Exemplos de listas: lista de clientes de uma banco, lista de chamada, lista de compras, etc. 2.1 Operações sobre Listas Percurso Operação que permite utilizar cada um dos elementos de uma lista linear. A lista pode ser percorrida de várias formas, do início ao fim, de trás para frente, etc. Busca Operação que procura um elemento específico. A busca pode ser feita de duas formas: ou o elemento é identificado por sua posição relativa ou pelo seu conteúdo. Inserção de Elementos por Posição Retirada de Elementos por Posição Prof. Cláudio H. S. Grecco 7 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II 2.2 Alocação de Memória • Estática – Quantidade total de memória utilizada pelos dados é definida em tempo de compilação de forma imutável; – Durante toda execução a quantidade de memória utilizada não varia; – Ideal para uso com listas seqüencias. • Dinâmica – O programa é capaz de criar novas variáveis enquanto executa; – Áreas de memória passam a existir durante a execução do programa; – Ideal para uso com listas encadeadas. 2.3 Lista Seqüencial Serve para armazenar uma lista no computador colocando seus elementos em células de memória consecutivas, um após o outro (contigüidade física). Com isso, cada célula tem um endereço único e ocupa o mesmo espaço de memória. Dado o endereço inicial da área alocada e o índice de um elemento qualquer, é possível acessá-lo imediatamente. Quando são necessárias inserções ou remoções no meio da lista é necessária a movimentação de elementos. Características de uma lista seqüencial: – – – – Contém nodos representados por endereços contíguos (de igual distância); Armazenados na memória um ao lado do outro; Lista pode ser implementada através de um vetor; Número de elementos da lista pode variar durante a execução (deve-se armazenar a quantidade de elementos na lista); – Com o uso de alocação estática, a quantidade máxima de elementos é determinada no código-fonte; – Representação física e lógica são iguais. 2.4 Estrutura Física e Lógica • Estrutura Lógica é como os dados são vistos pelos programadores. • Estrutura Física é como os dados são efetivamente armazenados na memória. Prof. Cláudio H. S. Grecco 8 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II 2.5 O Tipo Lista em Pascal • Componentes da lista: – Número de nodos da lista (total) – Vetor de nodos (memo) – Tamanho total da Lista (MAX) • Declaração em Pascal: CONST MAX = 50; SUCESSO = 1; LISTACHEIA = 2; LISTAVAZIA = 3; POSICAOINVALIDA = 4; TYPE Elem = INTEGER; Lista= RECORD total: integer; memo: ARRAY [1..MAX] OF Elem; END; 2.6 Trabalhando com Listas em Pascal • Criando Listas: PROCEDURE CriaLista (VAR l: Lista); BEGIN l.total :=0; END; Prof. Cláudio H. S. Grecco 9 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II • Inserindo no Fim de Listas: FUNCTION IncluiFim (VAR l: Lista; no: Elem):BYTE; BEGIN IF l.total = MAX THEN IncluiFim := LISTACHEIA ELSE BEGIN l.total:= l.total+1; l.memo[l.total] := no; IncluiFim := SUCESSO; END; END; Inserindo no Início de Listas: FUNCTION IncluiInicio (VAR l: Lista; no: Elem):BYTE; VAR i: integer; BEGIN IF l.total = MAX THEN IncluiInicio := LISTACHEIA ELSE BEGIN l.total:= l.total+1; FOR i:=l.total DOWNTO 2 DO l.memo[i] := l.memo[i-1]; l.memo[1]:=no; IncluiInicio := SUCESSO; END; END; Inserindo em Posição Específica: FUNCTION IncluiPos(VAR L:Lista;no:Elem; pos:INTEGER):BYTE; VAR i:integer; BEGIN IF l.total=MAX THEN IncluiPos:=LISTACHEIA ELSE IF (pos>0) AND (pos<=l.total+1) THEN BEGIN l.total:=l.total+1; FOR i:=l.total DOWNTO pos+1 DO l.memo[i]:=l.memo[i-1]; l.memo[pos]:=no; IncluiPos:=SUCESSO; END ELSE IncluiPos:=POSICAOINVALIDA; END; Prof. Cláudio H. S. Grecco 10 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II Removendo do Fim: FUNCTION RetiraFim (VAR l: Lista; VAR no:Elem):BYTE; BEGIN IF l.total = 0 THEN RetiraFim := LISTAVAZIA ELSE BEGIN no := l.memo[l.total]; l.total:= l.total-1; RetiraFim := SUCESSO; END; END; Removendo do Início: FUNCTION RetiraInicio (VAR l: Lista; VAR no: Elem):BYTE; VAR i: integer; BEGIN IF l.total = 0 THEN RetiraInicio := LISTAVAZIA ELSE BEGIN no:=l.memo[1]; l.total:= l.total-1; FOR i:=1 TO l.total DO l.memo[i] := l.memo[i+1]; RetiraInicio := SUCESSO; END; END; Removendo em Posição Específica: FUNCTION RetiraPos(VAR L:Lista;VAR no:Elem; pos:INTEGER):BYTE; VAR i:integer; BEGIN IF l.total=0 THEN RetiraPos:=LISTAVAZIA ELSE IF (pos>0) AND (pos<=l.total) THEN BEGIN no:=l.memo[pos]; l.total:=l.total-1; FOR i:=pos TO l.total DO l.memo[i]:=l.memo[i+1]; RetiraPos:=SUCESSO; END ELSE RetiraPos:=POSICAOINVALIDA; END; Prof. Cláudio H. S. Grecco 11 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II 2.7 Alternativa de Implementação Para facilitar as inserções e remoções no início da lista, pode-se inserir a partir do meio do vetor. CONST MAX = 50; TYPE Elem = INTEGER; Lista= RECORD inicio,fim: integer; memo: ARRAY [1..MAX] OF Elem; END; – – – – – Caso início = 0 e fim = 0 a lista está vazia; Caso início = 1 e fim = MAX a lista está cheia; Na inicialização o fim e o início são zerados; Na primeira inserção: início é igual a MAX DIV 2 e fim é igual a início; Mesmo assim, pode ser necessária a movimentação. 2.8 Conclusão sobre Lista Seqüencial A representação física da lista pode variar conforme o extremo das inserções/remoções. Uma posição específica para consulta pode ser calculada (acesso aleatório). Uma lista seqüencial facilita a transferência de dados, pois armazena tudo em área contígüa de dados, além disso, inserções e remoções podem exigir movimentação de dados. A lista seqüencial mantém um espaço de memória ocioso, devido a alocação estática e os limites devem ser testados constantemente. 2.9 Disciplinas de Acesso Considerando-se somente operações de acesso, inserção e remoção, restritas aos extremos da listas, temos os seguintes casos especiais: Pilha: lista linear onde os acessos, inserções e remoções são realizados em um único extremo. Listas do tipo LIFO (Las-tin Fist-out, último a entrar é o primeiro a sair); Fila: lista linear onde as inserções são feitas em um extremo e os acessos e remoções no outro. Listas do tipo FIFO (First-in First-out, primeiro a entrar é o primeiro a sair); Fila Dupla ou DEQUE (Double Ended Queue): lista linear onde as inserções, remoções e acessos são feitos em qualquer extremo. Casos especiais: Entrada Restrita e Saída Restrita. Prof. Cláudio H. S. Grecco 12 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II 2.10 Pilhas (Stacks) Nas pilhas, as inserções e remoções ocorrem em um mesmo extremo da lista, denominado topo. Os elementos são removidos em ordem inversa àquela em que foram inseridos (LIFO - Last-in First-out). • Operações Básicas: – Inicializa Pilha – Insere Pilha (empurra / push) – Remove Pilha (puxa / pop) – Consulta Topo (top) – Vazia (isempty) – Cheia (isfull) 2.11 Implementando Pilhas em Pascal • Componentes da Pilha: – um vetor, para armazenar os elementos contidos; – um índice, para indicar a posição do topo. Prof. Cláudio H. S. Grecco 13 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II • Registro em Pascal: CONST MAX = 50; TYPE Elem = CHAR; Pilha= RECORD topo: INTEGER; memo: ARRAY[1..MAX] OF Elem; END; • Inicializando a Pilha: PROCEDURE Inicializa(VAR p:Pilha); BEGIN p.topo:=0; END; • Verificando Limites: FUNCTION Vazia(VAR p:Pilha):BOOLEAN; BEGIN IF p.topo=0 THEN Vazia := TRUE ELSE Vazia := FALSE; END; FUNCTION Cheia(VAR p:Pilha):BOOLEAN; BEGIN IF p.topo=MAX THEN Cheia := TRUE ELSE Cheia := FALSE; END; • Empilhando: PROCEDURE Insere(VAR p:Pilha; no:Elem); BEGIN IF NOT Cheia(P) THEN BEGIN p.topo:=p.topo+1; p.memo[p.topo] := no; END ELSE WRITELN(‘Pilha Cheia!’); {Stack Overflow} END; • Desempilhando: FUNCTION Remove(VAR p:Pilha):Elem; BEGIN IF NOT Vazia(P) THEN BEGIN Remove:= p.memo[p.topo]; p.topo:=p.topo-1; END ELSE WRITELN(‘Pilha Vazia!’); {Stack Underflow} END; Prof. Cláudio H. S. Grecco 14 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II • Consulta o Topo: FUNCTION Topo(VAR p:Pilha):Elem; BEGIN IF NOT Vazia(P) THEN Topo:= p.memo[p.topo] ELSE WRITELN(‘Pilha Vazia!’); END; 2.12 Exemplos do Uso de Pilha • Exemplo 1: Controle de Subrotinas – sempre que uma subrotina é chamada, uma pilha é utilizada para armazenar o valor de retorno. • Exemplo 2: Imprimir número decimal em binário – dividir um inteiro sucessivamente por dois, até obter um quociente igual a zero. Então tomar os restos da divisão em ordem inversa. Exemplo 2 PROCEDURE DEC2BIN(n:INTEGER); VAR resto, quoc:INTEGER; p:pilha; BEGIN inicializa(p); REPEAT resto:= n MOD 2; insere(P,resto); n := n DIV 2; UNTIL N=0; WHILE NOT vazia(P) DO BEGIN resto := remove(P); WRITE(resto); END; WRITELN; END; Prof. Cláudio H. S. Grecco 15 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II 2.12 Filas (Queues) As inserções ocorrem em um extremo ficando as remoções restritas a outro. Os elementos são removidos na mesma ordem em que foram inseridos (FIFO-First-in First-out). • Operações Básicas: – – – – – Inicializa Lista Insere Lista (enfileira / enqueue) Remove Lista (desenfileira / dequeue) Vazia (isempty) Cheia (isfull) 2.13 Implementação de Filas • Componentes da Fila: – vetor para armazenar os elementos; – variável apontando para o primeiro elemento; – variável apontando para à primeira posição livre. • Registro em Pascal: TYPE Fila= RECORD começo, fim: INTEGER; memo: ARRAY[1..MAX] OF Elem; END; Prof. Cláudio H. S. Grecco 16 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II • Problema de Implementação: – Cada vez que um elemento é removido, o começo desloca-se para à direita. Após a remoção de todos os elementos não será mais possível inserir na lista. Solução para o problema – Uso de lista circular: o fim do vetor emenda no início; – Cada posição liberada por um elemento disponível torna-se prontamente disponível; – É necessário que logicamente a posição 1 esteja após a posição MAX; 2.14 Implementação de Filas em Pascal • Componentes da Fila: – – – – vetor para armazenar os elementos; variável apontando para o primeiro da fila; variável apontando para o último da fila; quantidade de elementos da fila. • Registro em Pascal: CONST MAX= 50; TYPE Elem= REAL; TYPE Fila= RECORD comeco, final, total: INTEGER; memo: ARRAY[1..MAX] OF Elem; END; • Inicializando Fila: PROCEDURE Inicializa(VAR f:Fila); BEGIN f.total:=0; f.comeco:=1; f.final:=1; END; Prof. Cláudio H. S. Grecco 17 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II • Verificando Limites: FUNCTION BEGIN Vazia := END; FUNCTION BEGIN Cheia := END; Vazia(VAR f:Fila):BOOLEAN; (f.total = 0); Cheia(VAR f:Fila):BOOLEAN; (f.total = MAX); Insere Elemento na Fila: PROCEDURE Insere(VAR f:Fila; no:Elem); BEGIN IF NOT Cheia(f) THEN BEGIN f.memo[f.final] := no; f.total :=f.total +1; f.final:=(f.final MOD MAX)+1; END ELSE WRITELN(‘Fila Cheia!’); END; Remove Elemento da Fila: FUNCTION Remove(VAR f:Fila):Elem; BEGIN IF NOT Vazia(f) THEN BEGIN Remove:=f.memo[f.comeco]; f.total:=f.total -1; f.comeco:=(f.comeco MOD MAX)+1; END ELSE WRITELN(‘Fila Vazia!’); END; 2.15 Exemplo do Uso de Filas • Colorindo Regiões Gráficas – Algoritmos para colorir regiões de desenhos (matrizes de pontos); – Região é um conjunto de pontos conectados entre si e que têm a mesma cor; – Diz-se que dois pontos Pi e Pj estão conectados entre si, se e somente se, é possível partir de Pi incrementando (ou decrementando) sua abcissa (ou ordenada) e chegar ao ponto Pj. quatro pontos conectados a P0 Prof. Cláudio H. S. Grecco 18 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II 2.16 Algoritmo para Colorir Regiões • Para colorir uma região R, faça: – obtenha um ponto inical P0, de cor C0, seguramente pertencente à região R; – obtenha uma nova cor C1 para a região R; – coloque P0 em uma fila V, inicialmente vazia; – enquanto a fila não esvaziar: • Remova um ponto P da fila V; • Insira em V todos os pontos conectados a P, cuja cor seja C0; • altere a cor de P para C1. • O desenho será representado por uma matriz NxN – cada elemento da matriz representa um ponto; – cada pixel é discriminado pelas coordenadas da sua posição na matriz; – cada elemento da matriz armazena um número correspondente à cor do ponto. TYPE Ponto = RECORD ABCISSA: INTEGER; ORDENADA: INTEGER; END; Elem = PONTO; Ou então PROCEDURE InsPonto (VAR V:Fila; X,Y:INTEGER); BEGIN insere(V,X); insere(V,Y); END; PROCEDURE RemPonto (VAR V:Fila; VAR X,Y:INTEGER); BEGIN X:=remove(V); Y:=remove(V); END; Prof. Cláudio H. S. Grecco 19 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II PROCEDURE Colorir(VAR M:Imagem; X,Y, Cor:INTEGER); VAR V:Fila; C0: INTEGER; BEGIN C0:=M[X,Y]; inicializa(V); InsPonto(V,X,Y); WHILE NOT vazia(V) DO BEGIN RemPonto (V,X,Y); IF M[X+1,Y]=C0 THEN InsPonto(V,X+1,Y); IF M[X-1,Y]=C0 THEN InsPonto(V,X-1,Y); IF M[X,Y+1]=C0 THEN InsPonto(V,X,Y+1); IF M[X,Y-1]=C0 THEN InsPonto(V,X,Y-1); M[X,Y]:=Cor; END; END; 2.17 Filas Duplas (Deques) São filas de duas extremidades, onde as inserções, remoções e consultas são permitidas apenas nos extremos (Double-Ended-Queue). Além do tipo convencional existem dois tipos: – Entrada Restrita: a inserção só pode ser efetuada em uma das extremidades (no início ou no final); – Saída Restrita: a remoção só pode ser efetuada em um dos extremos (início ou final). 2.18 Implementação de Fila Dupla A Fila Dupla é representada por uma lista circular. • Registro em Pascal CONST MAX= 50; TYPE Elem= REAL; TYPE Deque= RECORD comeco, final, total: INTEGER; memo: ARRAY[1..MAX] OF Elem; END; • Além do insere da fila (InsereFinal) e do remove da fila (RemoveInício) é possível criar o InsereInicio e o RemoveFinal • As funções Vazia e Cheia são idênticas Prof. Cláudio H. S. Grecco 20 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II 3. Lista Encadeada 3.1 Alocação de Memória • Estática A quantidade total de memória utilizada pelos dados é definida em tempo de compilação de forma imutável e durante toda execução a quantidade de memória utilizada não varia. Ideal para uso com listas seqüenciais. • Dinâmica – O programa é capaz de criar novas variáveis enquanto executa; – Áreas de memória passam a existir durante a execução do programa; – Ideal para uso com listas encadeadas. 3.2 Alocação Dinâmica em Pascal Tipo de alocação onde cada variável possui um número indefinido de bytes alocados na memória. Durante a execução é alocada ou desalocada memória para a variável com qualquer tamanho. As variáveis locais em Pascal são alocadas dinamicamente, porém de forma automática. É necessário o uso de uma variável do tipo Ponteiro, para a utilização de alocação dinâmica. – Um ponteiro é uma variável que ocupa 4bytes e aponta para um endereço de memória; – Na definição de um ponteiro é indicado o tipo que irá conter no endereço. • Definição de um ponteiro: – Var nome_do_ponteiro:^tipo_base; – Exemplo: VAR pint: ^INTEGER; {aponta p/ inteiro} • Alocar um ponteiro (reservar espaço de memória): – New(p); – Procedimento que aloca memória do tamanho do tipo_base. • Desalocar um ponteiro (liberar espaço alocado): – Dispose(p); – Procedimento que desaloca memória alocada pelo procedimento New(p). Prof. Cláudio H. S. Grecco 21 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II 3.3 Ponteiros em Pascal Quando um ponteiro não aponta para endereço algum (não foi alocada memória para ele ainda) contém o valor NIL ( palavra reservada que representa um ponteiro nulo). Caso seja definido em um programa um ponteiro P, as seguintes definições são válidas: – P contém o endereço de memória apontado pelo ponteiro; – P^ representa o conteúdo do ponteiro. Caso se utilize @n, sendo n uma variável qualquer diferente de ponteiro, retorna o endereço de n. 3.4 Exemplos do uso de Ponteiro • Exemplo 1: TYPE PTR=^INTEGER; VAR p: PTR; BEGIN NEW(p); IF p=NIL THEN WRITELN(‘ERRO: Falta de Memória’) ELSE BEGIN p^:= 1500; WRITELN(p^); DISPOSE(p); END; END. • Exemplo 2: VAR n:INTEGER; p:^INTEGER; BEGIN n:= 486; p:= @n; WRITELN(‘Conteúdo:’,p^); END. Prof. Cláudio H. S. Grecco 22 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II 3.5 Ponteiros em Pascal • Outras subrotinas em Pascal para alocação dinâmica: – GetMem(p, nbytes): procedimento que aloca uma área de memória com tamanho nbytes; – FreeMem(p,nbytes): procedimento que desaloca uma área de memória com tamanho nbytes; – MemAvail: retorna o número de bytes disponível na memória; – MaxAvail: retorna o número de butes do maior bloco disponível na memória; – SEG(variável): função que retorna o segmento da variável passada; – OFS(variável): função que retorna o deslocamento (offset) da variável passada; 3.6 Implementando um Lista Encadeada Ao invés de manter os elementos agrupados numa área contígua de memória, na lista encadeada os elementos podem ocupar quaisquer células. Para manter a relação da ordem linear, junto com cada elemento é armazenado o endereço do próximo (um ponteiro para o elemento seguinte). Os elementos são armazenados em blocos de memória denominados nodos que contêm: – um elemento da lista; – um ponteiro para o próximo elemento; Para acessar a lista encadeada basta saber o endereço do primeiro elemento; o último elemento da lista aponta para NIL, indicando o fim da lista. Característica de uma lista encadeada: – Facilidade de inserção e remoção no meio da lista, pois não necessita realizar movimentações; – A lista cresce indeterminadamente, enquanto houver memória livre; – Para acessar um elemento específico da lista, deve-se percorrer todos os anteriores; – Espaço adicional é perdido para guardar os ponteiros; Prof. Cláudio H. S. Grecco 23 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II 3.7 Estrutura Física e Lógica 3.8 O Tipo Lista Encadeada em Pascal • Componentes da lista: – Nodos da lista (composto por um elemento e um ponteiro para o próximo); – Endereço do primeiro nodo. • Declaração em Pascal: CONST SUCESSO = 1; FALTADEMEMORIA = 2; LISTAVAZIA = 3; TYPE Elem = INTEGER; PtrNodo = ^Nodo; Nodo = RECORD memo: Elem; elo: PtrNodo; END; ListaEnc = ^Nodo; 3.9 Trabalhando com Lista Encadeada • Criando Listas Encadeadas: FUNCTION CriaLista (VAR l:ListaEnc); BEGIN l:=NIL; END; Prof. Cláudio H. S. Grecco 24 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II • Inserindo no Início da Lista: FUNCTION InsereInicio (VAR l:ListaEnc; no:Elem):BYTE; Var pn : PtrNodo; BEGIN NEW (pn); IF pn = NIL THEN InsereInicio:=FALTADEMEMORIA ELSE BEGIN pn^.memo:=no; pn^.elo:=l; l:= pn; InsereInicio:=SUCESSO; END; END; • Removendo do Início: PROCEDURE RemoveInicio(VAR l:ListaEnc;VAR no:Elem):BYTE; Var pn : PtrNodo; BEGIN IF l = NIL THEN RemoveInicio:=LISTAVAZIA ELSE BEGIN pn:=l; l:= pn^.elo; no:= pn^.memo; DISPOSE (pn); RemoveInicio:=SUCESSO; END; END; • Removendo do Início: PROCEDURE RemoveInicio(VAR l:ListaEnc;VAR no:Elem):BYTE; Var pn : PtrNodo; BEGIN IF l = NIL THEN RemoveInicio:=LISTAVAZIA ELSE BEGIN pn:=l; l:= pn^.elo; no:= pn^.memo; DISPOSE (pn); RemoveInicio:=SUCESSO; END; END; 3.10 Lista Encadeada com Descritor Algumas dificuldades da lista encadeada podem ser resolvidas com a adição de um descritor. O uso do descritor serve para: – determinar o número de elementos, pois, sem utilizar descritor, deve-se percorrer toda a lista; Prof. Cláudio H. S. Grecco 25 Universidade Castelo Branco – - Algoritmos e Estruturas de Dados II acessar o último elemento da lista, pois, sem empregar o descritor, deve-se visitar todos os elementos intermediários; • Descritor contém: – endereço do primeiro elemento; – endereço do último elemento; – quantidade de elementos da lista. 3.11 Lista Encadeada com Descritor em Pascal • Declaração em Pascal: TYPE Elem = INTEGER; PtrNodo = ^Nodo; Nodo = RECORD memo: Elem; elo: PtNodo; END; ListEnc = RECORD primeiro, ultimo :PtrNodo; total :Integer; END; 3.12 Encadeamento com Descritor em Pascal • Criando Listas Encadeadas com Descritor: PROCEDURE CriaLista (VAR l:ListaEnc); BEGIN l.primeiro:=NIL; l.ultimo:=NIL; l.total:=0; END; • Determinando a quantidade de Elementos: FUNCTION TamLista (l:ListaEnc):INTEGER; BEGIN TamLista:=l.total; END; Prof. Cláudio H. S. Grecco 26 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II • Inserindo no Início da Lista Encadeada com Descritor: FUNCTION InsereInicio (VAR l:ListaEnc; no:Elem):BYTE; Var pn : PtrNodo; BEGIN NEW (pn); IF pn = NIL THEN InsereInicio:=FALTADEMEMORIA ELSE BEGIN pn^.memo:=no; pn^.elo:=l.primeiro; l.primeiro:= pn; IF l.total= 0 THEN l.ultimo := pn; l.total:=l.total+1; InsereInicio:=SUCESSO; END; END; • Inserindo no Fim da Lista Encadeada com Descritor: FUNCTION InsereFim (VAR l:ListaEnc; no:Elem):BYTE; Var pn : PtrNodo; BEGIN NEW (pn); IF pn = NIL THEN InsereFim:=FALTADEMEMORIA ELSE BEGIN pn^.memo:=no; pn^.elo:=NIL; IF l.total= 0 THEN l.primeiro := pn ELSE l.ultimo^.elo:=pn; l.ultimo:=pn; l.total:=l.total+1; InsereFim:=SUCESSO; END; END; 3.13 Lista Duplamente Encadeada Algumas dificuldades da lista encadeada (com ou sem descritor) podem ser resolvidas com o duplo encadeamento, neste caso, a lista só pode ser percorrida em um sentido, pois possui um elo apenas para o próximo elemento.Na lista duplamente encadeada cada nodo possui: – um elemento; – um ponteiro para o próximo elemento; – um ponteiro para o primeiro elemento. Quando um nodo não possui próximo ou anterior o elo respectivo aponta para NIL. Prof. Cláudio H. S. Grecco 27 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II 3.14 Lista Duplamente Encadeada em Pascal • Declaração em Pascal TYPE Elem = INTEGER; PtrNodo = ^Nodo; Nodo = RECORD memo: Elem; anterior, posterior: PtrNodo; END; ListaDEnc = RECORD primeiro, ultimo :PtrNodo; total :Integer; END; • Inserindo no Início de um Lista Duplamente Encadeada FUNCTION InsereInicio (VAR l:ListaDEnc; no:Elem):BYTE; Var pn : PtrNodo; BEGIN NEW (pn); IF pn = NIL THEN InsereInicio:=FALTADEMEMORIA ELSE BEGIN pn^.memo:=no; pn^.anterior:=NIL; IF l.total=0 THEN l.ultimo:=pn ELSE l.primeiro^.anterior:=pn; pn^.posterior:=l.primeiro; l.primeiro:=pn; l.total:=l.total+1; InsereInicio:=SUCESSO; END; END; • Inserindo no Fim da Lista Encadeada com Descritor FUNCTION InsereFim (VAR l:ListaDEnc; no:Elem):BYTE; Var pn : PtrNodo; BEGIN NEW (pn); IF pn = NIL THEN InsereFim:=FALTADEMEMORIA ELSE BEGIN pn^.memo:=no; pn^.posterior:=NIL; IF l.total = 0 THEN l.primeiro := pn ELSE l.ultimo^.posterior:=pn; pn^.anterior:=l.ultimo; l.ultimo:=pn; l.total:=l.total+1; InsereFim:=SUCESSO; END; END; Prof. Cláudio H. S. Grecco 28 Universidade Castelo Branco - Algoritmos e Estruturas de Dados II 3.15 Outras formas de Encadeamento • Encadeamento Circular Em uma lista com encadeamento circular o campo de ligação do último nodo armazena o endereço do primeiro. A variável ponteiro para lista pode apontar para o último elemento, com isso, o acesso ao primeiro e o último é rápido. • Encadeamento Duplo Compacto Técnica que permite armazenar tanto o endereço do nodo anterior quanto do posterior na mesma variável ponteiro. Realiza-se um Ou-Exclusivo entre os dois endereços. Prof. Cláudio H. S. Grecco 29