Вы находитесь на странице: 1из 121

Universidade Estadual do Oeste do Paran - UNIOESTE Centro de Cincias Exatas e Tecnolgicas - CCET Curso de Cincia da Computao

ESTRUTURAS DE DADOS

CASCAVEL - PR 2011

SUMRIO
UNIDADE 1 INTRODUO S ESTRUTURAS DE DADOS .................................. 1 1.1 1.2 1.3 1.4
1.4.1 1.4.2 1.4.3

INFORMAES GERAIS .................................................................................... 1 TIPOS PRIMITIVOS DE DADOS ......................................................................... 1 MECANISMOS PARA CONSTRUO DE TIPOS .............................................. 2 PROCEDIMENTOS E FUNES ........................................................................ 7
PASSAGEM DE PARMETROS ................................................................................................ 8 PASSAGEM DE PARMETROS POR VALOR .......................................................................... 9 PASSAGEM DE PARMETROS POR REFERNCIA ............................................................. 10

UNIDADE 2 MATRIZES ......................................................................................... 12 2.1 INTRODUO ................................................................................................... 12 2.2 MATRIZES: CASO GERAL ................................................................................ 12 2.3 MATRIZES ESPECIAIS ..................................................................................... 13
2.3.1 2.3.2 MATRIZES DIAGONAIS ........................................................................................................... 14 MATRIZES TRIANGULARES ................................................................................................... 14

2.4 MATRIZES ESPARSAS ..................................................................................... 16 UNIDADE 3 LISTAS, FILAS E PILHAS................................................................. 21 3.1 LISTAS LINEARES ............................................................................................ 21
3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 3.1.10 FUNDAMENTOS ....................................................................................................................... 21 REPRESENTAES ................................................................................................................ 22 REPRESENTAO DE LISTAS POR ALOCAO SEQENCIAL......................................... 22 ACESSAR O K-SIMO N DE UMA LISTA ............................................................................. 23 ALTERAR O VALOR DO K-SIMO N DE UMA LISTA ..........................................................24 INSERIR UM NOVO N ANTES DO K-SIMO N DE UMA LISTA ....................................... 24 REMOVER O K-SIMO N DE UMA LISTA ............................................................................ 25 REPRESENTAO DE LISTAS POR ENCADEAMENTO DOS NS ..................................... 25 ALOCAO DINMICA DE MEMRIA .................................................................................... 26 ENTENDENDO LISTAS REPRESENTADAS ATRAVS DE ALOCAO ENCADEADA DOS NS .................................................................................................................................. 27 3.1.11 ROTINAS BSICAS DE TRATAMENTO DE LISTAS...............................................................28 3.1.12 LISTAS COM DESCRITOR....................................................................................................... 33 3.1.13 LISTAS DUPLAMENTE ENCADEADAS ................................................................................... 36

3.2 PILHAS .............................................................................................................. 38


3.2.1 3.2.2 3.2.3 3.2.4 3.3.1 3.3.2 3.3.3 3.3.4 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 INIT, ISEMPTY E ISFULL ......................................................................................................... 40 UM PRIMEIRO EXEMPLO DO USO DE PILHAS..................................................................... 41 IMPLEMENTAO SEQENCIAL DE PILHAS ....................................................................... 42 ALGORITMOS PARA MANIPULAO DE PILHAS.................................................................43 IMPLEMENTAO SEQENCIAL DE FILAS .......................................................................... 46 PROBLEMAS NA IMPLEMENTAO SEQENCIAL DE FILAS ............................................ 48 SOLUCIONANDO OS PROBLEMAS DA IMPLEMENTAO SEQENCIAL ......................... 49 IMPLEMENTAO CIRCULAR PARA FILAS .......................................................................... 50 INTRODUO........................................................................................................................... 52 USO DE RECURSO NA SOLUO DE PROBLEMAS ......................................................... 53 QUANDO APLICAR RECURSO? ........................................................................................... 54 ELIMINANDO A RECURSO DE CAUDA ................................................................................ 54 PILHAS E ROTINAS RECURSIVAS ......................................................................................... 55

3.3 FILAS ................................................................................................................. 45

3.4 RECURSIVIDADE .............................................................................................. 52

UNIDADE 4 - RVORES .......................................................................................... 57 4.1 RVORES BINRIAS ........................................................................................ 57


4.1.1 RVORES DE BUSCA BINRIA .............................................................................................. 58

4.1.2 4.1.3

OPERAES BSICAS EM RVORES DE BUSCA BINRIA ............................................... 59 ATRAVESSAMENTO EM RVORES BINRIAS ..................................................................... 63

4.2 RVORES BALANCEADAS .............................................................................. 67 4.3 RVORES-AVL.................................................................................................. 67


4.3.1 4.3.2 4.3.3 INCLUSO EM RVORES-AVL ............................................................................................... 68 IMPLEMENTAO DA INCLUSO .......................................................................................... 71 REMOES EM RVORES-AVL............................................................................................. 76

4.4 RVORES HEAP E HEAPSORT ....................................................................... 80


4.2.1 HEAPSORT ................................................................................................................................... 81

4.5 RVORES B ...................................................................................................... 83


4.4.1 RVORES B MULTIDIRECIONAIS .......................................................................................... 85

4.6 OUTROS TIPOS DE RVORES E SUAS REPRESENTAES ...................... 99 UNIDADE 5 PESQUISA DE DADOS ................................................................... 100 5.1 MTODOS DE BUSCA .................................................................................... 100
5.1.1 5.1.2 5.2.1 5.2.2 5.2.3 5.2.4 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 BUSCA LINEAR ...................................................................................................................... 100 BUSCA BINRIA ..................................................................................................................... 101 INTRODUO......................................................................................................................... 102 O PROBLEMA DO CASAMENTO DE CADEIAS.................................................................... 102 O ALGORITMO DA FORA BRUTA ...................................................................................... 103 O ALGORITMO DE KNUTH, MORRIS E PRATT ................................................................... 104 FUNDAMENTOS ..................................................................................................................... 108 APLICABILIDADE DO ESPALHAMENTO .............................................................................. 109 TABELAS DE ESPALHAMENTO ............................................................................................ 111 FUNES DE ESPALHAMENTO .......................................................................................... 112 O MTODO DA DIVISO ....................................................................................................... 112 TRANSFORMAO DE CHAVES ALFANUMRICAS .......................................................... 113 OPERAES BSICAS SOBRE TABELA DE ESPALHAMENTO ........................................ 117

5.2 PROCESSAMENTO EM CADEIAS ................................................................. 102

5.3 ESPALHAMENTOS ......................................................................................... 107

UNIDADE 1 INTRODUO S ESTRUTURAS DE DADOS


1.1 INFORMAES GERAIS Niklaus Wirth afirma que programas de computador podem ser divididos em dois componentes: lgica e dados. A lgica trata como as operaes sero encadeadas de maneira a chegar no resultado esperado. Este componente foi discutido na disciplina de algoritmos. O segundo componente - dados - so os elementos a serem manipulados no programa. Neste ponto torna-se importante o estudo dos dados, principalmente na sua forma de estruturao, armazenamento e manipulao. Este o objetivo da disciplina de Estrutura de Dados. Estudar como os dados so estruturados, como so armazenados e, principalmente, como estes dados podem ser manipulados. Lembrando que a manipulao est condicionada estrutura de dados empregada. 1.2 TIPOS PRIMITIVOS DE DADOS Sero considerados disponveis quatro tipos primitivos de dados: Tipo inteiro real lgico caractere Abreviao int real log car Contedo -5, -2, -1, 0, 1, 3, 100 -120.32, -50.12, 0, 1, 1.32 V ou F A, a, B, b, C, c, !, ?, /

Para cada um desses tipos supomos que utilizada uma representao adequada. No vamos nos preocupar com as restries que seriam impostas por um particular computador ou sistema; tais restries envolvem, por exemplo, valores mximos dos inteiros, preciso dos reais, etc. a) Tipo inteiro: Os valores possveis para um objeto do tipo int so os nmeros inteiros (negativos, zero ou positivos). As operaes permissveis so: Operao soma subtrao multiplicao diviso inteira resto da diviso Smbolo + * div mod

Cada uma dessas operaes recebe como argumentos um par de inteiros e fornece um resultado inteiro. Em particular, div e mod so, respectivamente, o quociente inteiro e o resto da diviso entre dois inteiros; por exemplo, 5 div 2 2, e 5 mod 2 1. Alm disso, podemos comparar dois inteiros para testar se so iguais (=), diferentes (), ou segundo a ordem ( <, , >, ).

2 b) Tipo real: Os objetos do tipo real so os nmeros racionais, isto , nmeros normalmente representados por uma parte inteira e uma parte fracionria. As operaes do tipo real so: Operao soma subtrao multiplicao diviso Smbolo + * /

Cada uma das quatro operaes acima recebe um par de nmeros do tipo real e fornece um resultado do mesmo tipo. Alm disso, como nos inteiros, podemos comparar dois elementos do tipo real conforme =, , <, >, etc. c) Tipo lgico: Este tipo consiste de exatamente dois valores: verdadeiro e falso, sendo as constantes correspondentes V e F. As operaes sobre os valores lgicos so Operao e (conjuno) ou (disjuno) no (negao) Smbolo e ou & ou ou | not, ~ ou !

d) Tipo caractere: Os objetos deste tipo so os chamados caracteres alfanumricos: os dgitos decimais (0 - 9), as letras (A - Z) e alguns sinais especiais (espao em branco, sinais de pontuao, etc.) No tipo caractere podemos realizar comparaes do tipo =, , >, < e ainda a operao de adio (+) que concatena caracteres. 1.3 MECANISMOS PARA CONSTRUO DE TIPOS Veremos, a seguir, alguns mecanismos que permitem a construo de outro tipo a partir dos tipos primitivos. O formato geral para a definio de um tipo o seguinte: tipo nome_do_tipo_definido = definio_do_tipo Poderemos construir os seguintes tipos: vetor; matriz; registro; referncia (ponteiro); enumerao. a) Vetor: O vetor permite a construo de um tipo cujos valores so agregados

3 homogneos de um tamanho definido, isto , suas componentes so todas de um mesmo tipo. O formato de definio para vetor o seguinte: tipo nome_do_vetor = vetor [limite_inferior.. limite_superior] de tipo; onde limite_inferior e limite_superior so constantes do tipo inteiro e tipo o tipo das componentes. Por exemplo: tipo VET_NOME_ALUNOS = vetor [1..10] de caractere; Criamos um tipo cujos elementos so compostos por 10 nomes. Um elemento deste tipo pode conter o nome dos 10 alunos de uma classe. Supondo que uma varivel TURMA_A do tipo VET_NOME_ALUNOS possua os seguintes valores: Ana 1 do vetor. Caso queiramos referenciar Carla usaremos a seguinte notao: TURMA_A[4] O contedo deste elemento Carla. b) Matriz: A matriz, como o vetor, permite a construo de um tipo cujos valores so agregados homogneos de um tamanho definido, isto , suas componentes so todas de um mesmo tipo. O formato de definio para vetor o seguinte: tipo nome_da_matriz = matriz [lim_inf_1..lim_sup_1; lim_inf_2..lim_sup_2] de tipo; onde lim_inf_1, lim_inf_2, lim_sup_1 e lim_sup_2 so constantes do tipo inteiro e tipo o tipo das componentes. Por exemplo: tipo MAT_CLASSES_ALUNOS = matriz [1..3; 1..10] de caractere; Criamos um tipo cujos elementos so compostos por 30 nomes. Um elemento deste tipo pode conter o nome dos 10 alunos de cada uma das 3 classes de uma escola. Supondo que uma varivel TURMAS_QUINTA_SERIE do tipo MAT_CLASSES_ALUNOS possua os seguintes valores: 1 Ana Pedro Paulo Carla Jos Joo Maria Cludia Mrio Iara 2 Juca Roger Ivo Cris Joel Mrcio Mrcia Sara Denise Carlos 3 Jucy Darci Iloir Osmar Daniel Diogo Ana Cludia Josi Julia 1 2 3 4 5 6 7 8 9 10 Pedro 2 Paulo 3 Carla 4 Jos 5 Joo 6 Maria 7 Cludia 8 Mrio 9 Iara 10

Cada elemento num vetor possui um ndice que indica sua posio dentro

4 Cada elemento numa matriz referencial por dois ndices, que indicam sua posio dentro da matriz. Caso queiramos referenciar Mrcia usaremos a seguinte notao: TURMAS_QUINTA_SERIE [2, 7] O contedo deste elemento Mrcia. c) Registro: s vezes temos necessidade de agregados heterogneos, isto , cujas componentes no so necessariamente de um mesmo tipo. O formato de definio para um registro o seguinte: tipo nome_do_registro = registro campo1, campo2, ..., campon : tipo1; campo1, campo2, ..., campon : tipo2; ... campo1, campo2, ..., campon : tipon; fim registro; Por exemplo, se quisermos construir um registro para armazenar o nome da disciplina e a turma qual ser ofertada esta disciplina, teremos: tipo TURMAS_DISCIPLINA = registro DISCIPLINA : caractere; TURMA : inteiro; fim registro; Supondo uma varivel GRADE_DISCIPLINAS do tipo TURMAS_DISCIPLINAS para a qual queiramos inserir a seguinte informao: A disciplina de Clculo Diferencial ser ofertada para a turma nmero 3. A atribuio desta informao para a varivel GRADE_DISCIPLINAS se dar da seguinte maneira: GRADE_DISCIPLINAS.DISCIPLINA Clculo Diferencial; GRADE_DISCIPLINAS.TURMA 3; Noutro exemplo, o tipo frao pode ser assim constitudo: tipo FRACAO = registro NUMERADOR, DENOMINADOR : inteiro; fim registro; A frao 2/7 poderia ser armazenada numa varivel NUM_FRAC do tipo FRACAO, da seguinte forma: NUM_FRAC.NUMERADOR 2; NUM_FRAC.DENOMINADOR 7; d) Referncia (Ponteiro): At agora no nos importamos muito com a maneira pela qual um objeto de um determinado tipo armazenado na memria de um computador. Supomos

5 que a cada varivel corresponde um espao suficiente para guardar cada um de seus possveis valores. Dizemos que a cada varivel alocado um espao - que chamaremos de clula (do tipo da varivel). Vamos supor que esta alocao determinada pela simples ocorrncia da declarao da varivel. Assim, a declarao das variveis x e y como de tipo inteiro ocasiona a alocao de uma clula inteira para cada uma delas. O mecanismo de referncia ou ponteiro permitir uma modalidade dinmica de alocao. A definio de tipo pelo mecanismo de referncia obedece ao seguinte formato: tipo nome_do_ponteiro = ^tipo; Por exemplo: tipo MATRICULA = ^inteiro; Ao declararmos uma varivel GEOGRAFIA do tipo MATRICULA estaremos criando um ponteiro para uma clula de memria. Nesta clula possvel armazenar um nmero inteiro, entretanto esta clula ainda no existe; existe somente um ponteiro que poder apontar para esta clula. A alocao do espao para a varivel geografia compreende duas partes: 1 - Parte de valor: para armazenar um valor do tipo inteiro; 2 - Parte de posio: para armazenar uma indicao da localizao da parte de valor. A simples declarao da varivel GEOGRAFIA como sendo do tipo MATRICULA ocasiona a alocao de espao para a parte de posio, mas no ainda para a parte de valor. Esta ltima ocasionada pelo comando: aloque (GEOGRAFIA); que, alm disso, faz com que na parte de posio seja colocada uma indicao da localizao onde est alocada a parte de valor.

- 1 - Declarao de geografia:

Parte de posio Figura 1.1 Situao aps ser declarada a varivel GEOGRAFIA

- 2 - Aps o comando aloque (GEOGRAFIA):

Parte de posio

Parte de valor

Figura 1.2 Situao aps ser executado o comando aloque

6 O espao alocado pelo comando aloque pode ser liberado pela execuo de: desaloque (GEOGRAFIA); cujo efeito fazer a situao da Figura 1.2 retornar situao da Figura 1.1. J que a varivel GEOGRAFIA tem, por assim dizer, duas partes, vamos usar a seguinte notao: GEOGRAFIA designa a parte de posio (que criada pela declarao da varivel); GEOGRAFIA^ designa a parte de valor (que apontada pela parte de posio).

A indicao da localizao contida na parte de posio no um objeto que possa ser manipulado por operaes. Podemos apenas testar sua igualdade ou no por meio de = ou . Um caso especial a constante nil; se o contedo de GEOGRAFIA nil ento no aponta para nenhuma parte de valor. Os mecanismos de agregao (vetor, matriz e registro) geralmente pressupem que suas componentes sejam armazenadas de maneira contgua em memria. O mecanismo de referncia permite armazenamento disperso dos seus componentes.

e) Enumerao: A enumerao permite definir tipos de dados por meio dos valores que os dados daquele tipo podem assumir. A definio feita indicando-se um conjunto ordenado de identificadores que denotam os valores que os dados daquele tipo devem assumir. O formato geral de uma enumerao : tipo nome_do_conjunto = (valor1, valor2, valor3, ..., valorn); Como exemplo, suponha a definio do tipo ms, que feita por enumerao dos valores vlidos que uma varivel deste tipo pode ter: tipo MES = (jan, fev, mar, abr, maio, jun, jul, ago, set, out, nov, dez); Assim, seria vlida a seguinte construo: var MES_NASC : MES; se MES_NASC = dez ento ... Note que, pelo fato de os valores de definio do tipo formarem um conjunto ordenado, existe uma relao de ordem entre eles. Portanto, vlido o uso de operadores de relao entre eles, isto , vlido afirmarmos que jan < fev < ... < nov < dez, para o tipo antes definido.

7 1.4 PROCEDIMENTOS E FUNES PROCEDIMENTOS Os procedimentos so utilizados quando um conjunto de comandos repete-se ao longo do algoritmo. Ento, para no escrevermos vrias vezes o mesmo bloco de comandos, usamos os procedimentos. Sintaxe: procedimento IDENTIFICADOR (parmetros); Comandos; fim procedimento IDENTIFICADOR; Onde: - IDENTIFICADOR o nome de referncia do procedimento; - parmetros a lista de variveis que sero passadas ao procedimento para serem manipuladas no seu interior. Na definio dos parmetros tambm devem ser declarados seus tipos. Nem todo procedimento utiliza-se de parmetros, portanto um item opcional.

Exemplo: procedimento LINHA (COMPRIMENTO : inteiro); var I : inteiro; para I 1 at COMPRIMENTO faa escreva -; fim para; fim procedimento LINHA;

FUNES As funes constituem um tipo especial de subrotina, bastante semelhante ao procedimento, que tem a caracterstica especial de retornar ao algoritmo chamador um valor associado ao nome da funo. Esta caracterstica permite uma analogia com o conceito de funo na matemtica. A utilizao de outras funes no algoritmo como por exemplo, seno, tangente ou qualquer outra funo especial, pode ser feito declarando-se um procedimento funo. A declarao de uma funo semelhante a de um procedimento. Difere somente na especificao do tipo da mesma, ou seja, do tipo de valor que ser retornado. Apesar de terem sido citadas apenas funes numricas, elas podem ser lgicas ou literais.

8 Sintaxe: funo IDENTIFICADOR (parmetros):tipo de retorno; comandos; fim funo IDENTIFICADOR;

Onde: - IDENTIFICADOR o nome de referncia da funo; - parmetros a lista de variveis que sero passadas funo para serem manipuladas no seu interior. Na definio dos parmetros tambm devem ser declarados seus tipos. Nem toda funo utiliza-se de parmetros, portanto um item opcional. - tipo de retorno o tipo de dado que a funo retornar (inteiro, real, lgico, caractere);

Exemplo: funo E_PAR (N: inteiro): lgico; se (N mod 2 = 0) ento E_PAR Verdadeiro; seno E_PAR Falso; fim se; fim funo E_PAR;

1.4.1 PASSAGEM DE PARMETROS A transferncia de informaes de e para subrotinas utilizando-se variveis globais no constitui uma boa disciplina de programao. Estas transferncias precisam ser mais formalizadas e documentadas a bem da legitimidade, documentao e organizao do programa elaborado. Em algoritmos, a transferncia de informaes de e para subrotinas pode ser feita com a utilizao de parmetros. Esta utilizao formaliza a comunicao entre mdulos. Alm disso, permite que um mdulo seja utilizado com operandos diferentes, dependendo do que se deseja do mesmo. Parmetros de definio so objetos utilizados dentro das subrotinas que em cada ativao representam objetos de nvel mais externo. A forma de se utilizar parmetros em subrotinas foi apresentada anteriormente. A chamada de uma subrotina aparece numa expresso e tem a seguinte forma: NOME DA SUBROTINA (Lista de parmetros de chamada);

9 Exemplo: incio var A, B, C : real; procedimento EXEMPLO (VALOR_1, VALOR_2, VALOR_3 : real); MAIOR_VALOR : real; MAIOR_VALOR VALOR_1; se (VALOR_2 > MAIOR_VALOR) ento MAIOR_VALOR VALOR_2; fim se; se (VALOR_3 > MAIOR_VALOR) ento MAIOR_VALOR VALOR_3; fim se; escreva O maior valor : , MAIOR_VALOR; fim procedimento EXEMPLO; {corpo do programa principal} leia Digite 3 nmeros:; A, B, C; EXEMPLO (A, B, C); fim. 1.4.2 PASSAGEM DE PARMETROS POR VALOR Na passagem de parmetros por valor (ou por cpia) o parmetro real calculado e uma cpia de seu valor fornecida ao parmetro formal, no ato da invocao da subrotina. A execuo da subrotina prossegue normalmente e todas as modificaes feitas no parmetro formal no afetam o parmetro real, pois trabalha-se apenas com uma cpia do mesmo. Exemplo: incio var X : inteiro; procedimento PROC (Y : inteiro); Y Y + 1; escreva Durante = , Y; fim procedimento PROC;

10 X 1; escreva Antes = , X; PROC (X); escreva Depois = , X; fim. O algoritmo anterior fornece o seguinte resultado: Antes = 1; Durante = 2; Depois = 1; Este tipo de ao possvel porque, neste mecanismo de passagem de parmetros, feita uma reserva de espao em memria para os parmetros formais, para que neles seja armazenada uma cpia dos parmetros reais. 1.4.3 PASSAGEM DE PARMETROS POR REFERNCIA Neste mecanismo de passagem de parmetros no feita uma reserva de espao em memria para os parmetros formais. Quando uma subrotina com parmetros passados por referncia chamada, o espao de memria ocupado pelos parmetros reais compartilhado pelos parmetros formais correspondentes. Assim, as eventuais modificaes feitas nos parmetros formais tambm afetam os parmetros reais correspondentes. Uma mesma subrotina pode utilizar diferentes mecanismos de passagem de parmetros, para parmetros distintos. Para diferenciar uns dos outros, convencionou-se colocar o prefixo var antes da definio dos parmetros formais passados por referncia. Se por exemplo uma subrotina tem o seguinte cabealho: procedimento PROC (X, Y : inteiro; var Z : real; J : real) Ento: - X e Y so parmetros formais do tipo inteiro e so passados por valor; - Z um parmetro formal real passado por referncia; - J um parmetro formal real passado por valor. O exemplo do item anterior, alterado para que o parmetro Y do procedimento seja passado por referncia, torna-se: incio var X : inteiro; procedimento PROC (var Y : inteiro); Y Y + 1; escreva Durante = , Y; fim procedimento PROC;

11 X 1; escreva Antes = , X; PROC (X); escreva Depois = , X; fim. O resultado do algoritmo modificado : Antes = 1; Durante = 2; Depois = 2;

12

UNIDADE 2 MATRIZES
2.1 INTRODUO J estamos bastante familiarizados com a idia de matriz. Em matemtica, usual se trabalhar com matrizes de elementos reais como:

1,0 2,0 5,7 M = 1,3 0,0 0,9


que uma matriz real 2x3, isto , com 2 linhas e 3 colunas. Estamos acostumados a somar e multiplicar matrizes como esta acima. Porm, do ponto de vista de estrutura de dados, estamos mais interessados na maneira como temos acesso informao armazenada em uma matriz. Isto feito pela indicao de uma linha e uma coluna, o que identifica a posio onde que elas se cruzam. Assim, atravs dos ndices 1 e 3, identificamos a posio [1, 3], que no caso do exemplo acima contm o valor 5,7. Isto costuma ser indicado por M[1, 3] = 5,7. Para localizarmos um elemento particular precisamos fornecer dois ndices: sua linha e sua coluna. Por esta razo, elas so chamadas matrizes bidimensionais. Quando apenas um ndice suficiente, temos uma matriz unidimensional, que costuma ser chamada de vetor coluna, ou de vetor linha, conforme a representamos:

7 ,0 Vetor Coluna: 0,1 2,3


Vetor Linha: [7,0

0,1 2,3]

comum tambm o caso de matrizes tridimensionais. Por exemplo, uma matriz cujo elemento [i, j, k] d a temperatura em um ponto de um cubo slido. 2.2 MATRIZES: CASO GERAL Os lugares de um teatro costumam ser identificados atravs da fila e da coluna de cada um. O servio de reservas mantm um mapa que indica os lugares ocupados e os ainda vagos. Para fixar idias, vamos considerar um teatro com 15 filas, numeradas de 1 a 15, cada fila com 20 cadeiras; 10 cadeiras esquerda e 10 cadeiras direita. Vamos imaginar o mapa de reservadas como uma matriz bidimensional RES, da maneira seguinte:
-10 1 2 M 15 L Lado Esquerdo -1 0 1 L Lado Direito 100

13 Um par de ndices como [7, -3] identifica a poltrona nmero 3 do lado esquerdo da fila 7; caso esteja reservada, teremos RES [7, -3] = V. Analogamente, RES [2, +5] = F quer dizer que a 5a poltrona do lado direito da 2a fila est livre. Pares com j = 0 (RES [i, 0]) indicam posies no corredor e so casos especiais. A matriz RES poderia ser declarada da seguinte forma: tipo TEATRO = matriz [1..15; -10..10] de lgico; var RES : TEATRO; O procedimento usual da reserva de um lugar pode ento ser descrito atravs de operaes de consulta e de atribuio matriz RES: desejando-se a poltrona i da fila j, faz-se uma consulta. caso RES [i, j] = V, deve-se escolher outro lugar; caso RES [i, j] = F, a reserva concedida e o mapa alterado atravs da atribuio RES [ i, j] V.

Supondo que haja dois espetculos por dia, fcil imaginar os dois mapas de reserva correspondentes como uma matriz RESERVA tridimensional, na qual o novo ndice k 1 ou 2, conforme a sesso desejada. Agora o dado necessrio para se fazer uma reserva uma tripla [i, j, k] e as operaes passam a ser: consulta: RESERVA [i, j, k]; atribuio: RESERVA [i, j, k] Valor; A matriz RESERVA poderia ser declarada da seguinte forma: tipo THEATER = matriz [1..3; 1..15; -10..10] de lgico; var RESERVA : THEATER;

Exerccios: 1) Escreva um procedimento para realizar a reserva de um local no teatro. Considere o caso em que h somente um espetculo ao dia. O procedimento ser executado at que o usurio deseja sair do procedimento. 2) Escreva um procedimento para atender o pedido de cancelamento de reservas. 3) Escreva um procedimento otimizado para realizar a transposio de uma matriz de ordem m x m. 2.3 MATRIZES ESPECIAIS A representao vista at agora guarda todos os elementos da matriz. Freqentemente ocorrem matrizes de nmeros apresentando uma boa parte de elementos nulos. Deixando de guardar estes, podemos fazer uma economia razovel de memria. Se os elementos nulos esto concentrados em uma regio da

14 matriz, como por exemplo acima da diagonal principal, ento podemos lanar mo de representaes especiais, simples e compactas. 2.3.1 MATRIZES DIAGONAIS Consideremos a matriz M [3, 4], de reais abaixo:

3,5 0,0 0,0 0,0 M = 0,0 1,6 0,0 0,0 0,0 0,0 2,5 0,0
Matrizes como M, em que todos os elementos com i j so nulos, so chamadas de matrizes diagonais. Uma vez que somente os elementos da diagonal principal so diferentes de zero, podemos utilizar um vetor para armazenar este tipo de matriz. Uma matriz diagonal com m linhas e n colunas contm m.n elementos. O vetor para armazenar esta matriz ser composto por p elementos, sendo p o menor valor entre m e n. Assim, para a matriz M acima, podemos utilizar um vetor com 3 posies, uma vez que ela possui m = 3 linhas e n = 4 colunas, implicando em p = 3 tipo MATDIAG = vetor [1..3] de real; var M_DIAG : MATDIAG; A consulta a uma matriz diagonal M_DIAG bastante simples, como vemos a seguir (Implementar a consulta atravs de uma funo): funo CONSULTA (M: MATDIAG; I, J : inteiro):real; se (i = j) ento CONSULTA M[I] seno CONSULTA 0,0; fim se; fim funo CONSULTA; A atribuio de valores numa matriz diagonal somente possvel para elementos em que i = j. Qualquer valor, diferente de zero, atribudo a uma posio com i j implica na matriz deixar de ser diagonal. 2.3.2 MATRIZES TRIANGULARES Um outro caso em que se pode economizar memria por meio de uma representao especial o das matrizes triangulares. Por exemplo, as matrizes 5x4

15 abaixo so triangulares, sendo M triangular superior, e N triangular inferior.

0,0 7,1 0,3 5,1 4,3 8,3 0,0 0,0 1,0 2,1 7,7 7,1 2,4 0,0 M = 0,0 0,0 3,5 2,4 N = 3,5 4,2 4,7 0,0 0,0 0,0 4,2 4,5 6,7 0,1 0,0 0,0 0,0 0,0 3,1 4,0 5,1

0,0 0,0 0,0 2,3 5,1

Dos 5 x 4 = 20 elementos da matriz M, apenas os 10 elementos no abaixo da diagonal principal so diferentes de 0,0. Assim, poderamos representar M por: M = [7,1 0,3 5,1 4,3 -1,0 2,1 7,7 3,5 2,4 4,2] J no caso de N, bastaria guardar os 14 elementos no acima da diagonal principal. Vamos nos restringir a matrizes quadradas. Uma matriz dita triangular inferior quando P[i, j] = 0 sempre que i < j. Ento, na 1a linha de P, todos os elementos so nulos, exceto talvez P[1, 1]. Na 2a linha, s P[2, 1] e P[2, 2] que podem ser no nulos. Na linha i, s podem ser diferentes de 0 todos os elementos P[i, 1], P[i, 2], ..., P[i, i]. Assim, o nmero de elementos no nulos em P pode ser obtido por:

m=

n (n + 1) 2

onde: m = nmero de elementos no nulos em P; n = nmero de linhas e colunas em P (matriz quadrada). Veja o exemplo:

1 3 MAT = 1 8 7

0 0 0 0 5 0 0 0 5 (5 + 1) = 15 4 7 0 0 m = 2 3 2 1 0 4 5 2 3

Poderamos, ento, armazenar esta matriz num vetor com m = 15 posies, da seguinte maneira: MAT_V = [1, 3, 5, 1, 4, 7, 8, 3, 2, 1, 7, 4, 5, 2, 3] Mas como vamos saber que o elemento MAT[4, 3] = MAT_V[9] = 2? Para acessar um elemento da matriz, atravs de suas coordenadas i e j,

16 numa matriz triangular inferior, podemos utilizar a seguinte regra: MAT[i, j] = MAT_V[j + (i . (i - 1)) div 2] Assim: MAT[4, 3] = MAT_V[3 + (4 . (4 - 1)) div 2] = MAT_V[3 + 6] = MAT_V[9] = 2

Exerccios: 2) Escreva um algoritmo para inserir elementos numa matriz triangular inferior de ordem 5. tipo MATTI = vetor [1..15] de real; procedimento INSERE_MAT_DIAG_INF (var MATRIZ : MATTI); var I, J : inteiro; para I = 1 at 5 faa para J = 1 at 5 faa se (I >= J) ento escreva Digite o elemento , I, J; leia (MATRIZ[J + (I * (I - 1)) div 2]); fim se; fim para fim para fim procedimento INSERE_MAT_DIAG_INF; 3) Escreva um algoritmo para inserir elementos numa matriz triangular superior de ordem 5. [i, j] k k =

2n(i 1) + i(1 i ) + j , onde n = ordem da matriz 2

2.4 MATRIZES ESPARSAS Matrizes esparsas so aquelas matrizes onde h uma quantidade elevada de elementos nulos. Por exemplo:

6 0 3 0 0 0 ME = 0 0 0 2 0 3 0 1 0 0 0 0

17 Podemos otimizar o uso de memria armazenando somente os elementos no nulos desta matriz num vetor. Mas ao armazenarmos somente o valor perdemos a posio do elemento na matriz. Portanto, devemos armazenar, junto ao valor, seu respectivo ndice. VETME = [(1, 1, 6) (1, 3, 3) (2, 4, 2) (2, 6, 3) (3, 2, 1)] Note que cada elemento deste vetor constitudo por 3 valores: o primeiro valor a linha do elemento, o segundo a coluna e o terceiro o valor armazenado na matriz. VETME trata-se de um vetor onde cada elemento uma tupla e a construo desta tupla pode ser realizada atravs de um registro. Assim, teremos: tipo ELEMENTO = registro LINHA : inteiro; COLUNA : inteiro; VALOR : inteiro; fim registro; tipo VETOR = vetor [1..5] de ELEMENTO; var VETME : VETOR; Na estrutura acima possvel armazenar apenas cinco elementos, pois o vetor esttico. O que acontecer que inserirmos mais um elemento no nulo na matriz? Ser impossvel fazermos isso. Pensando nestes casos, devemos declarar o vetor com um nmero maior de elementos, por exemplo 8 elementos: tipo VETOR = vetor [1..8] de ELEMENTO; Desta maneira teremos 3 posies de folga no vetor que representa a matriz esparsa, sendo possvel inserir at trs elementos novos na mesma. A consulta numa matriz esparsa se d pela busca de um elemento que contenha o ndice procurado. Caso este ndice no exista significa que aquele elemento possui valor 0. Exerccios: 5) Escreva o algoritmo de uma funo para buscar um elemento numa matriz esparsa. incio CONST NUM = 8; tipo ELEMENTO = registro LINHA : inteiro; COLUNA : inteiro; VALOR : inteiro; fim registro;

18 tipo MATESP = vetor [1..NUM] de ELEMENTO; funo BUSCA (I, J : inteiro; MATRIZ : MATESP): inteiro; var D : inteiro; para D = 1 at NUM faa se ((MATRIZ[D].linha = I) e (MATRIZ[D].coluna = J)) ento BUSCA MATRIZ[D].VALOR; exit; seno BUSCA 0; fim se; fim para fim funo BUSCA; fim. Quando inserirmos um novo elemento numa matriz esparsa devemos manter os elementos ordenados pelos seus ndices, de forma que o processo de busca seja o mais rpido possvel. Da mesma maneira, ao atribuirmos o valor nulo para um elemento, devemos remov-lo da matriz, pois a mesma somente armazena os elementos no nulos. Na insero devemos nos preocupar em verificar trs fatos: Nulidade do novo valor; Existncia de espao no vetor, que representa a matriz, para o novo elemento; Ordem dos elementos. Quando um novo valor ser inserido na matriz, e este valor for nulo, simplesmente no ser inserido no vetor. O exemplo a seguir mostra a insero de um elemento no nulo na matriz esparsa. A matriz ME que contm os valores

6 0 3 0 0 0 ME = 0 0 0 2 0 3 0 1 0 0 0 0
pode ser representada por um vetor VETME = [(1, 1, 6) (1, 3, 3) (2, 4, 2) (2, 6, 3) (3, 2, 1) ( , , ) ( , , ) ( , , )]

19 Insero de um novo elemento:

Na insero de um novo elemento devemos, primeiro, verificar se mesmo um novo elemento, ou seja, se no h um elemento no nulo na matriz com o mesmo ndice. Supondo que temos de inserir o novo elemento ME[3, 4] = 5. Este ndice no existe no vetor VETME, portanto trata-se de um novo elemento. Sua insero ser feita anexando-se o elemento na primeira posio livre de VETME, pois h espao no vetor e o elemento a ser inserido tem ndice que o posiciona aps o ltimo elemento: ME[3, 2] = 1. VETME = [(1, 1, 6) (1, 3, 3) (2, 4, 2) (2, 6, 3) (3, 2, 1) (3, 4, 5) ( , , ) ( , , )] Analisemos, agora, a insero de um novo elemento ME[2, 5] = 4. O ndice j existe em VETME? No, ento trata-se de um elemento novo. H espao livre no vetor? Sim, portanto o elemento poder ser inserido. Mas onde? O ndice do elemento [2, 5] mostra que a insero deve ser feita entre os elementos ME[2, 4] = 2 e ME[2, 6] = 3. Mas ali no h posio livre; e nem deveria haver. Ento, para que a insero seja possvel, devemos deslocar os elementos subseqentes a ME[2, 4] = 2 uma posio para a direita, a fim de criar uma posio livre onde ser inserido o elemento ME[2, 5] = 4. VETME = [(1, 1, 6) (1, 3, 3) (2, 4, 2) ( , , ) (2, 6, 3) (3, 2, 1) (3, 4, 5) ( , , )] VETME = [(1, 1, 6) (1, 3, 3) (2, 4, 2) (2, 5, 4) (2, 6, 3) (3, 2, 1) (3, 4, 5) ( , , )]

Alterao de um elemento:

Analisemos, agora, a insero do elemento ME[1, 3] = 0. O ndice j existe em VETME? Caso a resposta seja sim, ento trata-se de uma alterao. Assim, temos de nos preocupar com dois possveis casos: O elemento ser alterado para um valor nulo; O elemento ser alterado para um valor no nulo. O novo valor ser nulo equivale a remover o elemento de VETME. Assim, todos os elementos no nulos direita do elemento devem ser movidos uma posio para a esquerda, sendo que o ltimo dever receber valores nulos. VETME = [(1, 1, 6) (1, 3, 3) (2, 4, 2) (2, 5, 4) (2, 6, 3) (3, 2, 1) (3, 4, 5) ( , , )] VETME = [(1, 1, 6) (2, 4, 2) (2, 5, 4) (2, 6, 3) (3, 2, 1) (3, 4, 5) (3, 4, 5) ( , , )] VETME = [(1, 1, 6) (2, 4, 2) (2, 5, 4) (2, 6, 3) (3, 2, 1) (3, 4, 5) ( , , ) ( , , )] Se o elemento ser alterado para um valor no nulo, basta localiz-lo em VETME e alterar o terceiro elemento da tupla. Por exemplo, o elemento VETME[2, 5] = 8.

20 VETME = [(1, 1, 6) (2, 4, 2) (2, 5, 8) (2, 6, 3) (3, 2, 1) (3, 4, 5) (3, 4, 5) ( , , )] Exerccios: 6) Escreva o algoritmo de uma funo para inserir/alterar elementos numa matriz esparsa.
incio CONST NUM = 8; tipo ELEMENTO = registro LINHA : inteiro; COLUNA : inteiro; VALOR : inteiro; fim registro; tipo MATESP = vetor [1..NUM] de ELEMENTO; funo INSERCAO (I, J, VALOR : inteiro; MATRIZ : MATESP) : lgico; var K : inteiro; se (BUSCA(I, J, K, MATRIZ) = 0) ento se (VALOR <> 0) ento se (HAESPACO(MATRIZ)) ento MATRIZ[K] VALOR; INSERCAO Verdadeiro; seno DESLOCADIREITA (K, MATRIZ); {Abre espao no vetor} MATRIZ[K] VALOR; INSERCAO Verdadeiro; fim se; seno INSERCAO Falso; fim se; seno INSERCAO Verdadeiro; fim se; seno se (VALOR <> 0) ento MATRIZ[K] VALOR; seno DESLOCAESQUERDA (K, MATRIZ); {desloca para esquerda e} {excluiu o ltimo da direita} fim se; fim se; fim funo INSERCAO; {posio est ocupada} {alterar o valor da posio} {posio est vazia} {H o que incluir!} { possvel incluir!}

{K retornar a posio onde est ou ser inserido o elemento}

se (EHULTIMO(I, J, MATRIZ)) ento {Inclui-ltima posio}

fim.

21

UNIDADE 3 LISTAS, FILAS E PILHAS


3.1 LISTAS LINEARES Freqentemente nos deparamos, na soluo de determinados problemas, com conjuntos de dados que se relacionam entre si de alguma forma, refletindo algumas propriedades que estes dados apresentam no problema real. Naturalmente, desejamos que este relacionamento seja preservado, com o objetivo de se poder fazer uso do mesmo, quando estes forem representados no computador. Considere, por exemplo, o caso de um problema que envolva a manipulao de dados de precipitaes pluviomtricas dirias de um perodo de um ms. Se o problema consistir em obter-se, digamos, a precipitao pluviomtrica mdia do perodo, no h nenhuma necessidade de se conhecer o relacionamento existente entre os dados dirios. Se, entretanto, o problema consistir em determinar uma funo que expresse o fenmeno (por exemplo, precipitao x tempo) no perodo, ento necessrio conhecer-se a relao de ordem cronolgica dos dados. 3.1.1 FUNDAMENTOS Uma lista linear uma coleo L: [a1, a2, ..., an], n 0, cuja propriedade estrutural baseia-se apenas na posio relativa dos elementos, que so dispostos linearmente. Se n = 0, dizemos que a lista L vazia; caso contrrio, so vlidas as seguintes propriedades: a1 o primeiro elemento de L; an o ltimo elemento de L; ak, com 1 < k < n, precedido pelo elemento ak-1 e seguido pelo elemento ak+1 em L.

Em outras palavras, a caracterstica fundamental de uma lista linear o sentido de ordem unidimensional dos elementos que a compem. Uma ordem que nos permite dizer com preciso onde a coleo inicia-se e onde termina, sem possibilidade de dvida. Entre as diversas operaes que podemos realizar sobre listas, temos: acessar um elementos qualquer da lista; inserir um elemento numa posio especfica da lista; remover um elemento de uma posio especfica da lista; combinar duas listas em uma nica; particionar uma lista em duas; obter cpias de uma lista; determinar o total de elementos na lista; ordenar os elementos da lista; apagar uma lista outras ... Considerando-se somente as operaes de acesso, insero e remoo,

22 restritas aos extremos da lista, temos casos especiais que aparecem muito freqentemente na modelagem de problemas a serem resolvidos por computador. Tais casos especiais recebem tambm nomes especiais: Pilha: lista linear onde todas as inseres, remoes e acessos so realizados em um nico extremo da lista. Listas com esta caracterstica so tambm denominadas listas LIFO (Last-In/First-Out, ou em portugus: ltimo que entra/primeiro que sai); Fila: lista linear onde todas as inseres so feitas num certo extremo e todas as remoes e acessos so realizados no outro. Filas so tambm denominadas de listas FIFO (First-In/First-Out, ou em portugus: primeiro que entre/primeiro que sai); Fila Dupla: lista linear onde as inseres, remoes ou acessos so realizados em qualquer extremo. Filas duplas so tambm denominadas DEQUE (DoubleEnded QUEue, ou em portugus: fila de extremidade dupla). Uma Fila Dupla pode ainda gerar dois casos especiais: Fila Dupla de Entrada Restrita (se a insero for restrita a um nico extremo) e Fila Dupla de Sada Restrita (se a remoo for restrita a um nico extremo). LISTA

PILHA

FILA

FILA DUPLA

FDER FIGURA 3.1 Casos especiais de listas lineares 3.1.2 REPRESENTAES

FDSR

Existem vrias formas possveis de se representar internamente uma lista linear. A escolha de uma destas formas depender da freqncia com que determinadas operaes sero executadas sobre a lista, uma vez que algumas representaes so favorveis a algumas operaes, enquanto que outras no o so, no sentido de exigir um maior esforo computacional para a sua execuo. A seguir, sero discutidas as duas formas mais freqentes usadas para representar listas lineares: por alocao seqencial e por encadeamento dos ns. 3.1.3 REPRESENTAO DE LISTAS POR ALOCAO SEQENCIAL A representao por alocao seqencial explora a seqencialidade da memria do computador, de tal forma que os ns de uma lista sejam armazenados em endereos contguos, ou igualmente distanciados um do outro. Neste caso, a

23 relao de ordem entre os ns da lista representada pelo fato de que se o endereo do n xi conhecido, ento o endereo do n xi+1 pode ser determinado. Esquematicamente, a representao de uma lista linear por alocao seqencial tem a forma mostrada na Figura 3.2, abaixo: x1 x2 x3 xn-1 xn

... FIGURA 3.2 Esquema de representao por alocao seqencial Esta estrutura a mesma do agregado homogneo (vetor). Assim uma lista de N ns x1, x2, ..., xn definida da seguinte forma: tipo LISTA = vetor [1..N] de tipo; onde tipo o tipo de dado a ser representado em cada n da lista. Para, finalmente, declararmos uma lista do tipo acima definido, escrevemos: var X : LISTA; O comprimento de uma lista (quantidade de ns) pode se modificar durante a execuo do programa; assim, entenderemos uma lista como sendo um vetor com N elementos dentro de um vetor com M elementos, onde M N. A seguir so apresentados os algoritmos para implementar algumas das operaes mencionadas na seo 3.1.1, sobre listas lineares representadas por alocao seqencial. Todas as operaes sero baseadas em listas do tipo anteriormente definido. 3.1.4 ACESSAR O K-SIMO N DE UMA LISTA Esta operao pode ser implementada atravs de uma nica construo: X[K], que pode aparecer em uma expresso qualquer. Nesta operao pode ocorrer, entretanto, que K > FIM ou K 0; isto , uma tentativa de acessar um n que no existe na lista. Para prevenir esta situao, podemos implementar a operao atravs de uma funo que testa a validade de K e retorne um sinal que indique a ocorrncia destas situaes anmalas, da seguinte forma: i) se o retorna da funo F ento K 0 ou K > FIM; (neste caso a operao no foi executada); ii) se o retorna da funo V, ento 0 < K FIM; (neste caso a operao foi executada e VAL contm o dado do k-simo n). Esta conveno tambm ser usada nas demais operaes.
funo ACESSAR (X : LISTA; K, FIM : inteiro; var VAL : tipo):lgico; se ((K <= 0) ou (K > FIM)) ento ACESSAR Falso; seno

24
VAL X[K]; ACESSAR Verdadeiro; fim se; fim funo ACESSAR;

3.1.5 ALTERAR O VALOR DO K-SIMO N DE UMA LISTA funo ALTERAR (var X : LISTA; K, FIM :inteiro; VAL: tipo):lgico; se ((K <= 0) ou (K > FIM)) ento ALTERAR Falso; seno X[K] VAL; ALTERAR Verdadeiro; fim se; fim funo ALTERAR; 3.1.6 INSERIR UM NOVO N ANTES DO K-SIMO N DE UMA LISTA Neste procedimento suporemos que no vetor X haja pelo menos um elemento disponvel para acomodar o novo n; ou seja, assumiremos que FIM < M, onde M o comprimento do vetor X. funo INSERIR (var X : LISTA; K, var FIM :inteiro; VAL: tipo):lgico; var I : inteiro; se ((K <= 0) ou (K > FIM)) ento INSERIR Falso; seno para I = FIM at K faa {contador decrescente} X[I + 1] X[I]; fim para; FIM FIM + 1; X[K] VAL; INSERIR Verdadeiro; fim se; fim funo INSERIR;

25 3.1.7 REMOVER O K-SIMO N DE UMA LISTA funo REMOVER (var X : LISTA; K, var FIM :inteiro):lgico; var I : inteiro; se ((K <= 0) ou (K > FIM)) ento REMOVER Falso; seno para I = K at (FIM 1) faa X[I] X[I + 1]; fim para; FIM FIM - 1; REMOVER Verdadeiro; fim se; fim funo REMOVER; 3.1.8 REPRESENTAO DE LISTAS POR ENCADEAMENTO DOS NS Ao invs de manter os elementos agrupados numa rea contnua de memria, isto , ocupando clulas consecutivas, na alocao encadeada os elementos ocupam quaisquer clulas (no necessariamente consecutivas) e, para manter a relao de ordem linear, juntamente com cada elemento armazenado o endereo do prximo elemento da lista. Desta forma, na alocao encadeada, os elementos so armazenados em blocos de memria denominados ns, sendo que cada n composto por dois campos: um para armazenar dados e outro para armazenar endereo.. Dois endereos especiais devem ser destacados: o endereo do primeiro elemento da lista (L); o endereo do elemento fictcio que segue o ltimo elemento da lista (nil). L = 3FFA 1C34 BD2F 1000 3A7B 14F6 5D4A a1 1C34 a2 BD2F a3 1000 a4 3A7B a5 14F6 a6 5D4A a7 nil ltimo elemento da cadeia, o endereo nulo nil indica que o elemento a7 no tem um sucessor Cada n armazena um elemento e o endereo do prximo elemento da lista Primeiro elemento, acessvel a partir de L. Note que o segundo elemento no ocupa um endereo consecutivo quele ocupado por a1

26 A alocao apresenta como maior vantagem a facilidade de inserir ou remover elementos do meio da lista. Como os elementos no precisam estar armazenados em posies consecutivas de memria, nenhum dado precisa ser movimentado, bastando atualizar o campo de ligao do elemento que precede aquele inserido ou removido. Por exemplo, para remover o elemento a2 da lista representada anteriormente, basta mudar o n no endereo 3FFA de (a1, 1C34) para (a1, BD2F). Como apenas o primeiro elemento acessvel diretamente atravs do endereo L, a grande desvantagem da alocao encadeada surge quando desejamos acessar uma posio especfica dentro da lista. Neste caso, devemos partir do primeiro elemento e ir seguindo os campos de ligao, um a um, at atingir a posio desejada. Obviamente, para lista extensas, esta operao pode ter um alto custo em relao a tempo. 3.1.9 ALOCAO DINMICA DE MEMRIA As estruturas de dados vistas at o momento so organizadas de maneira fixa. Isto , criamos as variveis e estas contam com um tamanho fixo em memria. Arquivos permitem uma estrutura com um nmero indeterminado de elementos, porm sempre arrumados na forma de uma seqncia. O que devemos fazer quando tanto o nmero de elementos quanto sua forma de organizao variam dinamicamente? Para resolver este problema temos necessidade de um mecanismo que permita: criar espao para novas variveis em tempo de execuo; definir ligaes entre estas variveis, de uma forma dinmica. Variveis dinmicas no possuem nome prprio, portanto no so referenciadas por seus nomes. A referncia a uma varivel dinmica feita por ponteiros. Um ponteiro uma varivel cujo contedo o endereo de uma posio de memria. Um ponteiro declarado fornecendo-se o tipo de varivel por ele apontada. Ou seja, P um ponteiro para um tipo T se houver a declarao: P : ^T; Ao iniciar o programa, o valor de P estar indefinido. Existe uma constante predefinida, do tipo ponteiro, chamada nil que no aponta para objeto algum. Note que o significado de nil: no apontar para objeto algum diferente de indefinido que significa varivel no inicializada. P nil;

smbolo usado para representar o nil Figura 3.3 O valor nil A criao de uma varivel dinmica do tipo T feita pelo operador

27 aloque. Assim, o procedimento padro: aloque (P) cria uma varivel do tipo T, sem nome, e coloca em P o endereo desta varivel. Graficamente podemos indicar a gerao de uma varivel dinmica da seguinte forma:

P valor de P o endereo

P^ varivel do tipo T. (denota-se por P^)

Figura 3.4 Gerao de uma varivel dinmica Note que a varivel dinmica anterior referenciada como P^, que significa varivel apontada por P. A remoo de uma varivel criada dinamicamente, apontada por P, pode ser realizada atravs do seguinte comando: desaloque (P); 3.1.10 ENTENDENDO LISTAS REPRESENTADAS ATRAVS DE ALOCAO ENCADEADA DOS NS A estrutura de dados mais simples que pode ser obtida ligando elementos com ponteiros a lista. Na sua forma mais simples, uma lista uma seqncia de elementos encadeados por ponteiros. Esta seqncia pode ter um nmero indeterminado de elementos, cujo primeiro est sendo apontado por uma varivel apontador do incio da lista. Assim, podemos representar graficamente uma lista como: 9 PRIM Figura 3.5 Representao grfica de uma lista Neste exemplo podemos identificar: o apontador para o incio da lista que a varivel PRIM, cujo contedo o endereo do primeiro elemento; uma seqncia com 3 elementos: (9, 6, 12); cada elemento da lista tem dois campos: o primeiro um inteiro e o segundo um apontador para o elemento seguinte na lista; o ltimo elemento da lista no aponta para nenhum elemento e seu campo de 6 12

28 apontador tem o valor nil. 3.1.11 ROTINAS BSICAS DE TRATAMENTO DE LISTAS Vamos supor uma lista como a mostrada no exemplo acima. Ela pode ser definida como: tipo PONTEIRO = ^ELEMENTO; tipo ELEMENTO = registro CHAVE : inteiro; PROX : PONTEIRO; fim registro; var P, PRIM : PONTEIRO; Existem algumas operaes que podem ser realizadas com listas. Destacam-se a criao de uma lista, a procura de um elemento, a insero de um elemento, a retirada de um elemento e a varredura na lista, processando seus elementos. Adotaremos que nesta lista os elementos so inseridos na ordem inversa em que so obtidos. Para melhor compreendermos o problema vamos analisar um exemplo. Suponha a lista: 9 PRIM Figura 3.6 Exemplo de uma lista e desejamos acrescentar um elemento com chave = 5. Para isso devemos fazer: 6 12

1) criar um novo elemento apontado por P: aloque (P)

2) atualizar o campo chave: P^.CHAVE 5;

3) colocar o elemento no incio da lista: P^.PROX PRIM;

29

9 PRIM

12

5 P Figura 3.7 Insero de um novo elemento na lista 4) atualizar o incio da lista: PRIM P; 9 PRIM 6 12

5 P Figura 3.8 Atualizao do ponteiro PRIM completando a insero do novo elemento na lista Assim uma rotina para gerar uma lista de inteiros a partir de um arquivo de entrada, que contm nmeros inteiros, pode ser: procedimento CRIALISTA (MEUARQUIVO : ARQINTEIROS; var PRIM: PONTEIRO); var P : PONTEIRO; NUMERO : inteiro; abra (MEUARQUIVO); PRIM nil; enquanto no fda(MEUARQUIVO) faa aloque (P); copie (MEUARQUIVO, NUMERO); P^.CHAVE NUMERO; P^.PROX PRIM; PRIM P; avance (MEUARQUIVO); fim enquanto; fim procedimento CRIALISTA;

30 A funo abaixo procura um elemento, cujo campo chave seja igual ao dado passado como parmetro, retornando um ponteiro que aponta para aquele dado em memria. funo BUSCA (PRIM: PONTEIRO; DADO: inteiro): PONTEIRO; P: PONTEIRO; NAOACHOU: lgico; P PRIM; NAOACHOU V; enquanto (P <> nil) e (NAOACHOU) faa se P^.CHAVE = DADO ento NAOACHOU F seno P P^.PROX; fim se; fim enquanto; BUSCA P; fim funo BUSCA; Caso o elemento no exista na lista a funo BUSCA retornar nil. Para inserirmos um elemento na lista devemos considerar dois casos: a) a insero deve ser feita aps o elemento aponta por P; b) a insero deve ser feita antes do elemento apontado por P. Caso a - Insero feita aps o elemento apontado por P: Esta representao mostra a varivel P apontando para o elemento cujo campo de informao vale 3 e a varivel AUX, do mesmo tipo de P, apontando para um elemento a ser inserido. A insero implica fazer o campo PROX da varivel apontada por AUX apontar para o sucessor de P^ e fazer P^ apontar para o novo elemento. Ou seja: AUX^.PROX P^.PROX; P^.PROX AUX; Antes: P 5 3 1

PRIM

AUX

2 Figura 3.9 Insero feita aps um elemento apontado por P (Antes)

31 Depois: P 5 3 1

PRIM

AUX

2 Figura 3.10 - Insero feita aps um elemento apontado por P (Depois)

Caso b - Insero feita antes do elemento apontado por P: Procedemos da seguinte maneira: criamos um novo elemento, apontado por AUX. Inserimos esse elemento aps o apontado por P. Copiamos o campo de informao do elemento apontado por P para o novo elemento da lista. Colocamos a nova informao, que est na varivel DADO no elemento apontado por P. Ou seja: AUX^.PROX P^.PROX; P^.PROX AUX; AUX^.CHAVE P^.CHAVE; P^.CHAVE DADO; Antes: P 5 3 1

PRIM

AUX Figura 3.11 - Insero feita antes de um elemento apontado por P (Antes) Depois: P 5 4 1

PRIM

AUX

Figura 3.12 - Insero feita antes de um elemento apontado por P (Depois)

32 Para remover um elemento apontado por P procedemos de maneira similar ao que foi feito anteriormente. Seja a lista: Antes: PRIM 5 3 1 0

P P1 Figura 3.13 Remoo de um elemento apontado por P (Antes)

Depois: 5 3 1 0

PRIM

P P1 Figura 3.14 Remoo de um elemento apontado por P (Depois) Retirar o elemento cujo campo de informao vale 3 o mesmo que copiar para ele o campo de informao do elemento seguinte e retir-lo da lista. Usando-se uma varivel P1, do mesmo tipo de P, temos o seguinte trecho de algoritmo: P1 P^.PROX; P^.CHAVE P1^.CHAVE; P^.PROX P1^.PROX; desaloque (P1); Para percorrer a lista, processando os elementos, vamos considerar que o processamento de cada elemento feito pelo procedimento PROCESSA, que recebe como parmetro o campo de informaes do elemento. Fazemos P apontar para o incio da lista e enquanto houver elemento na lista, chama-se PROCESSA e atualiza-se o valor de P, que passa a apontar para o prximo elemento. P PRIM; enquanto P <> nil faa PROCESSA (P); P P^.PROX; fim enquanto;

33 3.1.12 LISTAS COM DESCRITOR Podemos simplificar a representao de uma lista se reunirmos, em um nico elemento, as referncias ao primeiro e ltimo elemento da lista. primeiro ltimo

Figura 3.15 Um n descritor A este elemento que rene as referncias ao incio e ao fim da lista damos a denominao de n descritor. O acesso aos elementos da lista ser sempre efetuado atravs do seu descritor. O n descritor de uma lista pode conter outras informaes sobre a lista, a critrio do projetista, tais como: quantidade de ns na lista, descrio dos dados contidos nos ns, etc. A figura, a seguir, mostra esquematicamente uma lista encadeada com n descritor, no qual foi includo um campo que indica a quantidade de ns existentes na lista. Nesta nova estrutura, a varivel PRIM aponta para o n descritor e no para o primeiro n da lista. PRIM

Figura 3.16 Uma lista encadeada com n-descritor

O n descritor, neste caso, um dado com a seguinte definio:

tipo DESCRITOR = registro I : PONTEIRO; N : inteiro; F : PONTEIRO; fim registro;

34 Uma lista vazia passa a ser representada, agora, da seguinte forma: PRIM

Figura 3.17 Representao de uma lista vazia, implementada com n descritor Usando esta nova estrutura de lista encadeada, passa a ser necessria a existncia de uma nova operao: criar uma lista vazia. Esta operao consiste em alocar um n do tipo descritor (PRIM), tornar seus dois ponteiros nulos e atribuir o valor zero ao campo N, gerando-se desta forma a situao mostrada na Figura 3.17. O procedimento criar, abaixo, implementa esta operao: procedimento CRIAR (var PRIM : ^DESCRITOR) aloque (PRIM); PRIM^.I nil; PRIM^.N 0; PRIM^.F nil; fim procedimento CRIAR; A seguir so apresentados outros procedimentos para manipulao de listas encadeadas com descritor. O primeiro procedimento, INSERE_ESQ, implementa a operao de insero de um n com o dado VALOR esquerda da lista cujo descritor apontado pela varivel PRIM. Procedimento INSERE_ESQ (PRIM: ^DESCRITOR; VALOR : inteiro); var P : ^ELEMENTO; aloque (P); P^.CHAVE VALOR; se PRIM^.N PRIM^.I PRIM^.F PRIM^.N P^.PROX seno P^.PROX PRIM^.I PRIM^.N fim se; = 0 ento {testa se a lista est vazia} P; P; 1; nil; PRIM^.I; P; PRIM^.N + 1;

fim procedimento INSERE_ESQ;

35 O procedimento seguinte, INSERE_DIR, implementa a operao de insero de um n com o dado VALOR direita da lista cujo descritor apontado por PRIM. Procedimento INSERE_DIR (PRIM: ^DESCRITOR; VALOR : inteiro); var P, Q : ^ELEMENTO; aloque (P); P^.CHAVE VALOR; P^.PROX nil; se PRIM^.N = 0 ento {testa se a lista est vazia} PRIM^.I P; PRIM^.F P; PRIM^.N 1; seno Q PRIM^.F; PRIM^.F P; Q^.PROX P; PRIM^.N PRIM^.N + 1; fim se; fim procedimento INSERE_DIR; Para remover o n da esquerda de uma lista, podemos utilizar o procedimento REMOVE_ESQ, a seguir apresentado. Este procedimento remove o primeiro n da lista, se houver, e retorna o dado que o n removido continha atravs do parmetro VALOR.
Procedimento REMOVE_ESQ (PRIM: ^DESCRITOR; var VALOR : inteiro); var P : ^ELEMENTO; se PRIM^.N = 0 ento {testa se a lista est vazia} ERRO(3); {chama rotina que trata o erro} seno P PRIM^.I; VALOR P^.CHAVE; PRIM^.I P^.PROX; PRIM^.N PRIM^.N 1; desaloque (P); se PRIM^.N = 0 ento {testa se a lista ficou vazia} PRIM^.F nil; fim se; fim se; fim procedimento REMOVE_ESQ;

36 A operao de remoo do n direita da lista com descritor envolve o mesmo problema apresentado para o caso de lista sem descritor: a necessidade de percorrer todos os ns da lista, seqencialmente, a partir do primeiro (da esquerda), at atingir o n da direita. A fim de evitar a necessidade deste caminhamento, podemos estruturar uma lista linear encadeada de forma que o caminhamento sobre a mesma possa ser feito em ambos os sentidos. Uma organizao que implementa este recurso denominada de lista linear duplamente encadeada. 3.1.13 LISTAS DUPLAMENTE ENCADEADAS Uma lista linear duplamente encadeada aquela em que cada n possui dois ponteiros, ao invs de um s. O primeiro usado para indicar o n predecessor, enquanto que o segundo aponta para o n sucessor. A Figura 3.18 mostra esquematicamente uma lista linear duplamente encadeada com 3 ns. PRIM

13

19

Figura 3.18 Uma lista linear duplamente encadeada com 3 ns A definio do tipo dos ns de uma lista duplamente encadeada feita da seguinte forma: tipo ELEMENTO = registro ESQ : PONTEIRO; CHAVE : inteiro; DIR : PONTEIRO; fim registro; Se unirmos as duas extremidades livres da lista, obteremos uma lista circular , conforme mostrado na Figura 3.19. Em uma lista circular, cada n satisfaz a seguinte condio: (P^.DIR)^.ESQ = P = (P^.ESQ)^.DIR onde P a referncia a um n qualquer da lista. Agora, com a nova organizao proposta, a implementao da operao de remoo do n da direita fica simplificada, no sentido de no ser mais necessrio

37 o caminhamento linear sobre a lista. PRIM

13

19

Figura 3.19 Representao de um lista circular com 3 ns O procedimento REMOVE_DIR, apresentado a seguir, implementa esta operao, sobre uma lista circular.
procedimento REMOVE_DIR (PRIM: ^DESCRITOR; var VALOR : inteiro); var P, Q, R : ^ELEMENTO; se PRIM^.N = 0 ento {testa se a lista est vazia} ERRO(3); {chama rotina que trata o erro} seno P PRIM^.F; {se lista no vazia, remove} VALOR P^.CHAVE; se PRIM^.N = 1 ento {testa se a lista tem s um n} PRIM^.I nil; PRIM^.F nil; seno Q P^.ESQ; R PRIM^.I; Q^.DIR P^.DIR; R^.ESQ Q; PRIM^.F Q; fim se; PRIM^.N PRIM^.N 1; desaloque (P); fim se; fim procedimento REMOVE_DIR;

38 3.2 PILHAS A pilha uma das estruturas de dados mais teis em computao. Uma infinidade de problemas clssicos da rea podem ser resolvidos com o uso delas. Uma pilha um tipo especial de lista linear em que todas as operaes de insero e remoo so realizadas numa mesma extremidade, denominada topo. Cada vez que um novo elemento deve ser inserido na pilha, ele colocado no seu topo; e em qualquer momento, apenas aquele posicionado no topo da pilha pode ser removido. Devido a esta disciplina de acesso, os elementos so sempre removidos numa ordem inversa quela em que foram inseridos, de modo que o ltimo elemento que entra exatamente o primeiro que sai. Da o fato de estas listas serem tambm denominadas LIFO (Last-In/First-Out). O exemplo mais comum do quotidiano uma pilha de pratos, onde o ltimo prato colocado o primeiro a ser usado (removido). Uma pilha suporta trs operaes bsicas, tradicionalmente denominadas como: Top: acessa o elemento posicionado no topo da pilha; Push: insere um novo elemento no topo da pilha; Pop: remove um elemento do topo da pilha.

Push

Pop Top

Figura 3.20 Uma pilha de pratos Sendo P uma pilha e x um elemento qualquer, a operao Push (P, x) aumenta o tamanho da pilha P, acrescentando o elemento x no seu topo. A operao Pop(P) faz com que a pilha diminua, removendo e retornando o elemento existente no seu topo. Das trs operaes bsicas, a nica que no altera o estado da pilha Top(P); ela simplesmente retorna uma cpia do elemento existente no topo da pilha, sem remov-lo. Observe a seguir, como estas operaes interagem para alterar o estado de uma pilha P, inicialmente vazia, cuja extremidade esquerda foi escolhida como topo. Operao Push (P, a) Push (P, b) Push (P, c) Pop (P) Pop(P) Push (P, d) Push (P, e) Estado da Pilha P: [ ] P: [a] P: [b, a] P: [c, b, a] P: [b, a] P: [a] P: [d, a] P: [e, d, a] P: [e, d, a] Resultado c b

39 Operao Top (P) Pop (P) Pop(P) Estado da Pilha P: [d, a] P: [a] Resultado e e d

Para melhor visualizao, ao invs de utilizar a notao de lista linear, geralmente as pilhas so representadas na forma de um grfico, crescendo na vertical, de baixo para cima, conforme o esquema a seguir:

an an-1 P: [an, an-1, ..., a2, a1] ... a2 a1

topo

base

Figura 3.21 Notao linear e grfica de pilhas Temos a seguir, representados na forma grfica, os sucessivos estados que uma pilha assume quando novos elementos so nela colocados ou dela retirados.

c b a a b a Push (P, c) b a Pop (P) a Pop (P)

Push (P, a) Push (P, b)

e d a d a d a Pop (P) a Pop (P) Pop (P)

Push (P, d) Push (P, e)

Figura 3.22 Sucessivos estados de uma pilha na notao grfica Note que a representao de pilha na forma de lista linear sugere que a posio de topo seja fixa. Isto torna necessrio empurrar os elementos para baixo sempre que um novo elemento for colocado na pilha. Ao remover o elemento do topo, os demais elementos devem ser puxados de volta para cima. J na representao grfica, fica implcito que, na verdade, a posio de topo que se

40 movimenta toda vez que a pilha cresce ou diminui. 3.2.1 INIT, ISEMPTY E ISFULL Imagine uma pilha de pratos sobre uma mesa, dentro de uma sala. Seria possvel colocar novos pratos indefinidamente sobre ela? Obviamente, no! Em algum momento o prato do topo da pilha tocaria o teto da sala. E o contrrio: seria possvel remover pratos do topo da pilha indefinidamente? Tambm no! Em algum momento a pilha tornar-se-ia vazia. A mesa e o teto so limites fsicos que impedem que a pilha cresa ou diminua indefinidamente. teto altura da parede

pratos mesa cho

Figura 3.23 Limitaes fsicas de uma pilha No exemplo da Figura 3.23, antes de comearmos a empilhar os pratos sobre a mesa, devemos garantir que a mesa estar limpa, isto , que no existem panelas, talheres ou qualquer outro objeto no local onde colocaremos os pratos. Para adicionar um prato pilha, primeiro verificamos se ainda existe espao entre o topo da pilha e o teto da sala. Finalmente, para remover, precisamos nos certificar de que ainda existem pratos sobre a mesa. Assim, precisamos de mais trs operaes essenciais para manipular pilhas: Init: inicializa a pilha no estado vazia; IsEmpty: verifica se a pilha est vazia; IsFull: verifica se a pilha est cheia. Sempre que uma varivel criada, ela permanece com contedo indefinido, at que um determinado valor seja a ela atribudo. Geralmente, a criao de uma varivel se restringe apenas alocao da rea de memria necessria para represent-la; nenhum valor inicial armazenado nesta rea, at que uma instruo especfica para esta finalidade seja executada. No caso de uma varivel do tipo pilha, isto no diferente. A operao Init(P) tem como objetivo definir um estado inicial para a pilha P. Por uma questo de bom senso, uma pilha sempre inicializada no estado vazia. Toda vez que criamos uma varivel pilha, antes de qualquer coisa, devemos inicializ-la para garantir que no haver nenhuma sujeira no local onde ela ser montada! Para verificarmos se uma pilha P est vazia, podemos usar a funo lgica IsEmpty(P), que toma como argumento a pilha em que estamos interessados e retorna verdadeiro somente se ela estiver vazia, sem nenhum elemento armazenado. A funo IsFull(P) usada para verificar se uma pilha est cheia, isto , ela retorna verdadeiro somente quando no h mais espao para armazenar

41 elementos na pilha. 3.2.2 UM PRIMEIRO EXEMPLO DO USO DE PILHAS Este primeiro exemplo objetiva mostrar como um programa completo, que utiliza o tipo pilha, pode ser escrito. O programa simplesmente pede ao usurio que digite um nmero inteiro positivo em decimal e, em seguida, mostra o nmero no sistema binrio. Para entender a lgica do programa, lembre-se de que para converter um nmero inteiro da base decimal para a binria, devemos dividi-lo sucessivamente por 2, at obtermos um quociente igual a 0. Neste momento, os restos obtidos nas divises devem ser tomados em ordem inversa. Veja o exemplo: 13 -12 1 2 6 6 -6 0 2 3 3 -2 1 2 1 1 -0 1 2 0

Assim, o valor 13 decimal fica 1101 em binrio. No programa a seguir, a pilha utilizada para armazenar os restos obtidos, de modo que depois eles possam ser recuperados no ordem inversa em que foram gerados.

program DEC_BIN; uses PILHAS; var P : Pilha; x, n : integer; begin writeln (Digite um inteiro decimal positivo: ); readln(n); Init(P); {torna a pilha vazia} repeat x := n mod 2; Push(P, x); n := n div 2; until (n = 0);

{calcula o {empilha o {calcula o {quociente

resto} resto} quociente} 0, pra}

write (Correspondente ao binrio: ); while (not IsEmpty(P)) do begin {pilha vazia, pra} x := Pop(P); {desempilha o resto} write (x); {imprime o resto} end; end.

42 Exerccios: 7) Escreva os algoritmos para compor uma biblioteca para manipulao de pilhas. Esta biblioteca deve conter os seguintes procedimentos e funes: Init, IsEmpty, IsFull, Top, Push e Pop. 3.2.3 IMPLEMENTAO SEQENCIAL DE PILHAS Como os elementos da pilha so armazenados em seqncia, um sobre o outro, e a incluso ou excluso de elementos no requer movimentao de dados, o esquema de alocao seqencial de memria mostra-se bastante apropriado para implement-las. Neste esquema, a forma mais simples de se representar um a pilha na memria consiste em: um vetor, que serve para armazenar os elementos contidos na pilha; um ndice, utilizado para indicar a posio corrente de topo da pilha. Para agrupar estas duas partes e formar a estrutura coesa que a pilha, usaremos o registro: const MAX = 50; tipo ELEM = caractere; PILHA = registro TOPO : inteiro; MEMO : vetor [1..MAX] de ELEM; fim registro; var P : PILHA; As duas primeiras linhas de cdigo tm como objetivo tornar a implementao o mais independente possvel dos tipos e quantidades de dados manipulados. Por exemplo, se desejssemos uma pilha com capacidade de armazenar at 200 valores reais, bastaria fazer duas pequenas alteraes nas linhas 1 e 2, e nenhum algoritmo precisaria ser alterado: const MAX = 200; tipo ELEM = real;

P.MEMO armazena os elementos da pilha

P: 3

a 1

b 2

c 3 4 5 6

... 7 ... MAX

P.TOPO indica a posio do ltimo elemento inserido

Figura 3.24 Estrutura de armazenamento da Pilha P: [c, b, a]

43 Como uma pilha um registro, podemos acessar seus componentes individuais, usando o operador ponto (.) e o nome do campo em que estamos interessados. 3.2.4 ALGORITMOS PARA MANIPULAO DE PILHAS Como sabemos, implementar um tipo de dados no se resume apenas em especificar a estrutura sob a qual os dados sero mantidos na memria, mas requer tambm o desenvolvimento dos algoritmos que descrevem o funcionamento das operaes bsicas sobre aquela estrutura.

Inicializando a Pilha:

Inicializar a pilha atribuir 0 TOPO, pois 0 uma posio inexistente no vetor e, alm disto, pode ser mudado facilmente para 1 quando o primeiro elemento for inserido na pilha; ou retornar a 0 quando o ltimo elemento for dela removido. procedimento INIT (var P: PILHA); P.TOPO 0; fim procedimento INIT;

Verificando Limites:

Na implementao seqencial, a checagem de pilha vazia ou cheia extremamente simples! Se acabamos de inicializar uma pilha P, usando a operao Init(P), claro que a operao que testa a pilha vazia, IsEmpty(P), dever retornar um valor lgico verdadeiro. funo ISEMPTY(P: PILHA): lgico; se (P.TOPO = 0) ento ISEMPTY Verdadeiro; seno ISEMPTY Falso; fim se; fim funo ISEMPTY; A operao que testa se a pilha est cheia ou no, tambm bastante simples. Se o campo TOPO usado para registrar a posio do elemento que ocupa o topo da pilha, claro que a pilha estar cheia quando o elemento do topo estiver armazenado na ltima posio do vetor MEMO, representada pela constante MAX.

44 funo ISFULL(P: PILHA): lgico; se (P.TOPO = MAX) ento ISFULL Verdadeiro; seno ISFULL Falso; fim se; fim funo ISFULL; Empilhando um Elemento:

Para inserir um novo elemento na pilha, primeiro verificamos se ainda existe espao. Existindo, temos que colocar o novo elemento no topo da pilha. procedimento PUSH (var P: PILHA; X: ELEM); se (no ISFULL(P)) ento P.TOPO P.TOPO + 1; P.MEMO[P.TOPO] X; seno escreva Stack Overflow! fim se; fim procedimento PUSH; Desempilhando um Elemento:

Para retirar um elemento de uma pilha, primeiro temos que nos certificar de que ela no se encontra vazia. Caso a pilha no esteja vazia, ento o elemento que est no topo dever ser retornado como resultado da operao e a varivel P.TOPO dever ser decrementada. funo POP (var P: PILHA): ELEM; se (no ISEMPTY(P)) ento POP P.MEMO[P.TOPO]; P.TOPO P.TOPO - 1; seno escreva Stack Underflow! fim se; fim funo POP; Obtendo o Valor do Elemento do Topo:

Algumas vezes, temos a necessidade de apenas observar o elemento que se encontra no topo da pilha. A operao TOP(P) nos permite fazer tal acesso,

45 sem alterar o estado da pilha. funo TOP (P: PILHA): ELEM; se (no ISEMPTY(P)) ento TOP P.MEMO[P.TOPO]; seno escreva Stack Underflow! fim se; fim funo TOP;

3.3 FILAS Uma fila um tipo especial de lista linear em que as inseres so realizadas num extremo, ficando as remoes restritas ao outro. O extremo onde os elementos so inseridos denominado final da fila, e aquele onde so removidos denominado comeo da fila. As filas so tambm denominadas listas FIFO (FirstIn/First-Out). Um exemplo bastante comum de filas verifica-se num balco de atendimento, onde pessoas formam uma fila para aguardar at serem atendidas. Naturalmente, devemos desconsiderar os casos de pessoas que furam a fila ou que desistem de aguardar! Diferentemente das filas no mundo real, o tipo de dados abstrato no suporta insero nem remoo no meio da lista. Incio final sai entra Figura 4.25 Uma fila de caixa bancrio A palavra queue, da lngua inglesa, significa fila. Por tradio, as duas operaes bsicas que uma fila suporta so denominadas como a seguir: Enqueue: insere um elemento no final da fila; Dequeue: remove um elemento do comeo da fila. Sendo F uma fila e x um elemento qualquer, a operao Enqueue (F, x) aumenta o tamanho da fila F, acrescentando o elemento x no seu final. A operao Dequeue (F) faz a fila diminuir, j que remove e retorna o elemento posicionado no seu comeo. Operao Enqueue (F, a) Enqueue (F, b) Enqueue (F, c) Enqueue (F, d) Dequeue (F) Dequeue (F) Estado da Fila F: [ ] F: [a] F: [a, b] F: [a, b, c] F: [a, b, c, d] F: [b, c, d] F: [c, d] Resultado a

46 Operao Enqueue (F, e) Enqueue (F, f) Enqueue (F, Dequeue (F)) Dequeue (F) Dequeue (F) Dequeue (F) Estado da Fila F: [c, d, e] F: [c, d, e, f] F: [d, e, f] F: [d, e, f, c] F: [e, f, c] F: [f, c] F: [c] Resultado b c d e f

3.3.1 IMPLEMENTAO SEQENCIAL DE FILAS Graficamente, representamos uma fila como uma coleo de objetos que cresce da esquerda para a direita, com dois extremos bem-definidos: comeo e final: comeo F: a b final c d

Figura 3.26 Representao grfica da fila F: [a, b, c, d] Intuitivamente, a partir da representao grfica, percebemos que possvel implementar uma fila tendo trs recursos bsicos: Espao de memria para armazenar os elementos; Uma referncia ao primeiro elemento da coleo; Uma referncia primeira posio livre, aps o ltimo elemento da fila. O espao de memria seqencial pode ser alocado por meio de um vetor. Para referenciar o primeiro elemento e a primeira posio disponvel no final da fila, podemos utilizar duas variveis inteiras que sirvam como ndice aos elementos do vetor. const MAX = 50; tipo ELEM = caractere; FILA = registro COMECO : inteiro; FINAL: inteiro; MEMO : vetor [1..MAX] de ELEM; fim registro; var F : FILA; Observe que somente na oitava linha que uma varivel do tipo Fila foi realmente criada. Veja, na Figura 3.27, como ficaria a estrutura de armazenamento da fila F: [a, b, c]. A primeira operao a ser definida, deve ser aquela que inicializa a fila, indicando que ela se encontra vazia. Considerando a forma escolhida para

47 representar a fila (figura anterior), podemos verificar que, medida que novos elementos vo sendo inseridos, o ndice F.FINAL vai se deslocando para a direita. Analogamente, quando um elemento removido, o ndice F.COMECO persegue F.FINAL. A conseqncia disto que, conforme os elementos vo entrando e saindo da fila, ela vai se movendo gradativamente para a direita. Particularmente, quando a fila no tiver mais nenhum elemento, o ndice F.COMECO ter alcanado o ndice F.FINAL, isto , teremos F.COMECO igual a F.FINAL. F.FINAL F.COMECO F: 1 4 a 1 b 2 F.MEMO c 3 4 MAX

Figura 3.27 Estrutura de armazenamento da fila F: [a, b, c] Como desejamos uma fila inicialmente vazia e F.COMECO igual a F.FINAL indica esta situao, bastaria atribuir qualquer valor inteiro k aos dois ndices, tornando-os iguais. Entretanto, convencionamos que F.FINAL apontaria a posio livre onde deveria ser armazenado um elemento que estivesse entrando na fila. Desta forma, a melhor escolha k = 1. Indicamos que a fila est vazia e, ao mesmo tempo, que a posio disponvel para insero a primeira posio do vetor F.MEMO. procedimento QINIT (var F: FILA); F.COMECO 1; F.FINAL 1; fim procedimento QINIT; Note que a fila estar vazia sempre que F.COMECO for igual a F.FINAL, mesmo que o valor destes ndices seja k 1. Adicionalmente, a fila estar cheia quando tivermos F.FINAL > MAX, isto , quando no existir uma posio livre aps o ltimo elemento da fila. funo QISEMPTY(var F: FILA): lgico; QISEMPTY (F.COMECO = F.FINAL); fim funo QISEMPTY;

funo QISFULL(var F: FILA): lgico; QISFULL (F.FINAL > MAX); fim funo QISFULL;

48 Para adicionar um elemento fila, primeiramente precisamos verificar se existe espao suficiente; isto feito facilmente com a funo QISFULL(). Caso exista, devemos lembrar que o ndice F.FINAL aponta justamente a posio onde dever entrar o novo elemento. Aps o elemento ter sido armazenado no final da fila, o ndice F.FINAL deve ser atualizado para que passe a indicar a prxima posio disponvel. Como estamos utilizando uma rea seqencial para armazenar os elementos, basta incrementar o valor de F.FINAL para que ele aponte a prxima posio livre no vetor. procedimento ENQUEUE (var F: FILA; X: ELEM); se (no QISFULL(F)) ento F.MEMO[F.FINAL] X; F.FINAL F.FINAL + 1; seno escreva Fila cheia; fim se; fim procedimento ENQUEUE; Para remover, lembramos que F.COMECO aponta o elemento que deve ser atendido primeiro, caso exista um. Aps o elemento ter sido removido, o ndice F.COMECO deve ser atualizado para apontar o prximo elemento a ocupar o incio da fila. funo DEQUEUE (var F: FILA): ELEM; se (no QISEMPTY(F)) ento DEQUEUE F.MEMO[F.COMECO]; F.COMECO F.COMECO + 1; seno escreva Fila vazia; fim se; fim funo DEQUEUE; 3.3.2 PROBLEMAS NA IMPLEMENTAO SEQENCIAL DE FILAS Suponha uma fila F: [a, b, c, d, e]. De acordo com a nossa implementao, e admitindo que a fila pode armazenar no mximo 5 elementos (MAX = 5), podemos representar a fila F como a seguir: F: 1 6 a 1 b 2 c 3 d 4 e 5

Figura 3.28 Uma fila cheia

49 Vimos que cada vez que um elemento removido, o ndice que aponta o comeo da fila desloca-se uma posio direita. Se inicialmente ele vale 1, como observamos na figura anterior, aps a remoo de todos os elementos da fila F, teremos a situao esquematizada a seguir:

F:

a 1

b 2

c 3

d 4

e 5

Figura 3.29 Uma fila cheia ou vazia? Se tentarmos inserir um novo elemento na fila, no conseguiremos, pois a funo QISFULL() indica que no existe mais espao disponvel (F.FINAL > MAX). Por outro lado, remover tambm no possvel, pois QISEMPTY() indica que a fila est vazia (F.COMECO = F.FINAL). Resumindo, chegamos a uma situao extremamente indesejvel. Temos uma fila que est cheia e vazia ao mesmo tempo. Afinal, como isto possvel? Chegamos concluso de que esta nossa implementao no muito eficiente, apresentando tanto desperdcio de memria quanto problemas de lgica. 3.3.3 SOLUCIONANDO OS PROBLEMAS DA IMPLEMENTAO SEQENCIAL Eliminar o erro lgico, que sinaliza fila vazia e cheia ao mesmo tempo, bastante simples. Basta acrescentar uma varivel contadora para indicar quantos elementos esto armazenados na fila. Esta varivel deve estar inicialmente zerada. Quando um elemento for inserido, ela ser incrementada; quando for removido, ela ser decrementada. Desta forma, o impasse pode ser resolvido simplesmente consultando tal varivel. F: 3 2 5 a 1 b 2 c 3 d 4

Figura 3.30 A fila F: [b, c, d] com varivel contadora Para eliminar o desperdcio de espao, o ideal seria que cada posio liberada por um elemento removido se tornasse prontamente disponvel para receber um novo elemento inserido. Para isto, teramos de dispor de uma rea seqencial de memria tal que a posio 1 estivesse imediatamente aps a posio MAX. Assim, QISFULL() somente indicaria fila cheia quando realmente todo o espao de armazenamento estivesse esgotado. Se fosse possvel alocar uma rea de memria como ilustra a Figura 3.31; ento um ndice i, com valor MAX + 1, estaria apontando para a primeira posio do vetor. Da mesma forma, um ndice j, com valor MAX + 2, estaria apontando a segunda posio e assim por diante... Entretanto, as clulas de memria seguem uma organizao linear e, para obter esta circularidade, deveremos lanar mo de

50 um artifcio: sempre que um ndice for incrementado e seu valor ultrapassar a constante MAX, restabelecemos seu valor a 1. A rotina ADC(), definida a seguir, serve para simular esta circularidade. MAX a b c e 5 d 4 2 1 comeo

6 final

Figura 3.31 Uma fila na representao circular procedimento ADC (var I: inteiro); I I + 1; se I > MAX ento I 1; fim procedimento ADC; 3.3.4 IMPLEMENTAO CIRCULAR PARA FILAS Temos, a seguir, a implementao circular completa para o tipo de dados Fila. As rotinas apresentadas so basicamente idnticas quelas apresentadas na implementao seqencial, exceto pelos detalhes discutidos na seo anterior. const MAX = 50; tipo ELEM = caractere; FILA = registro TOTAL : inteiro; COMECO : inteiro; FINAL: inteiro; MEMO : vetor [1..MAX] de ELEM; fim registro; var F : FILA; procedimento QINIT (var F: FILA); F.TOTAL 0; F.COMECO 1;

51 F.FINAL 1; fim procedimento QINIT; funo QISEMPTY(var F: FILA): lgico; QISEMPTY (F.TOTAL = 0); fim funo QISEMPTY; funo QISFULL(var F: FILA): lgico; QISFULL (F.TOTAL = MAX); fim funo QISFULL; procedimento ADC (var I: inteiro); I I + 1; se I > MAX ento I 1; fim procedimento ADC; procedimento ENQUEUE (var F: FILA; X: ELEM); se (no QISFULL(F)) ento F.MEMO[F.FINAL] X; ADC(F.FINAL); F.TOTAL F.TOTAL + 1; seno escreva Fila cheia; fim se; fim procedimento ENQUEUE; funo DEQUEUE (var F: FILA): ELEM; se (no QISEMPTY(F)) ento DEQUEUE F.MEMO[F.COMECO]; ADC(F.COMECO); F.TOTAL F.TOTAL - 1; seno escreva Fila vazia; fim se; fim funo DEQUEUE;

52 3.4 RECURSIVIDADE 3.4.1 INTRODUO Um algoritmo que para resolver um problema divide-o em subproblemas mais simples, cujas solues requerem a aplicao dele mesmo, chamado recursivo. Em termos de programao, uma rotina recursiva quando ela chama a si mesma, seja de forma direta ou indireta. Em geral, uma rotina recursiva R pode ser expressa como uma composio formada por um conjunto de comandos C (que no contm chamadas a R) e uma chamada (recursiva) rotina R:

R [C , R ]
Figura 3.32 - Recurso Direta Entretanto, pode-se ter tambm uma forma indireta de recurso, na qual as rotinas so conectadas atravs de uma cadeia de chamadas sucessivas que acaba retornando primeira que foi chamada:

R1 [C1 , R 2 ]

R2 [C 2 , R3 ] R3 [C 3 , R4 ]

M Rn [C n , R1 ]
Figura 3.33 - Recurso Indireta Assim, diz-se que uma rotina R indiretamente recursiva se ela contm uma chamada a outra rotina S, que por sua vez contm uma chamada direta ou indireta a R. Note que a recurso nem sempre explcita e, s vezes, pode ser difcil perceb-la atravs de uma simples leitura da rotina. A recurso uma tcnica particularmente poderosa em definies matemticas. Alguns exemplos familiares so: nmeros naturais e certas funes: Nmeros naturais: (a) 0 um nmero natural; (b) o sucessor de um nmero natural um nmero natural. A funo fatorial n! (para inteiros no negativos): (a) 0! = 1 (b) n > 0: n! = n * (n - 1)!

O poder da recurso deve-se, evidentemente, possibilidade de se definir um conjunto infinito de objetos atravs de uma formulao finita. Do mesmo modo,

53 um nmero infinito de clculos pode ser definido por um programa recursivo finito, ainda que este no contenha repeties explcitas. Algoritmos recursivos, no entanto, so especialmente adequados quando o problema a ser resolvido, a funo a ser calculada ou a estrutura de dados a ser processada j estejam definidos sob uma forma recursiva. 3.4.2 USO DE RECURSO NA SOLUO DE PROBLEMAS Todo algoritmo deve ser executado em tempo finito, isto , deve terminar aps ter executado uma quantidade finita de passos. Para garantir que uma chamada recursiva no criar um looping que ser executado infinitamente, necessrio que ela esteja condicionada a uma expresso lgica que, em algum instante, tornar-se- falsa e permitir que a recurso termine. Assim, uma rotina recursiva melhor representada por R [C , T R ] , onde T R indica que a rotina R somente ser chamada se o teste T for satisfeito. A tcnica bsica para garantir o trmino da execuo de um algoritmo recursivo consiste em: Exprimir T em termos de uma funo f(x), tal que f(x) 0 implica uma condio de parada; mostrar que f(x) decresce a cada passo de repetio, isto , que temos a forma R( x ) [C , ( f ( x) > 0) R(x 1)] , onde x decresce a cada chamada. Na prtica, ao definir uma rotina recursiva, dividimos o problema da seguinte maneira: Soluo Trivial: dada por definio; isto , no necessita da recurso para ser obtida. Esta parte do problema resolvida pelo conjunto de comandos C. Soluo Geral: parte do problema que em essncia igual ao problema original, sendo porm menor. A soluo, neste caso, pode ser obtida por uma chamada recursiva R(x-1) Para decidir se o problema ter soluo trivial ou geral; isto , se sua soluo ser obtida pela execuo do conjunto de instrues C ou pela chamada recursiva R(x-1), usamos um teste. Por exemplo, vamos definir uma funo recursiva para calcular o fatorial de um nmero natural: Soluo Trivial: 0! = 1 {dada por definio} Soluo Geral: n! = n * (n - 1)! {requer reaplicao da rotina para (n - 1) !} Considerando f(n) = n, ento n = 0 implica numa condio de parada do mecanismo recursivo, garantindo o trmino do algoritmo que calcula o fatorial: funo FAT (N: inteiro): inteiro; se (N = 0) ento FAT 1 seno FAT N * FAT (N - 1); fim se; fim funo FAT; Em termos matemticos, a recurso uma tcnica que, atravs de

54 substituies sucessivas, reduz o problema a ser resolvido a um caso de soluo trivial. Veja o clculo de f(4) na figura a seguir: FAT(4) 4*FAT(3) 4*3*FAT(2) 4*3*2*FAT(1) 4*3*2*1*FAT(0) 4*3*2*1*1

Problema a resolver

Substituies sucessivas

Caso de soluo trivial obtido !

Figura 3.34 - Recurso vista como substituies sucessivas 3.4.3 QUANDO APLICAR RECURSO? Embora a recurso seja uma ferramenta bastante interessante para resoluo de problemas, nem sempre ela poder ser empregada. Enquanto alguns problemas tm soluo imediata com o uso de recurso, outros so praticamente impossveis de se resolver de forma recursiva. preciso analisar o problema e verificar se realmente vale a pena tentar encontrar uma soluo recursiva. A recurso, se bem utilizada, pode tornar um algoritmo muito elegante; isto , claro, simples e conciso. Porm, na maioria das vezes, uma soluo iterativa (no recursiva) ser mais eficiente. Cada chamada recursiva implica em um custo tanto de tempo quanto de espao, pois cada vez que a rotina chamada, todas as variveis locais so recriadas. Assim, determinar precisamente se devemos usar recurso ou no, ao resolver um problema, uma questo bastante difcil de ser respondida. Para isto teramos que comparar as possveis solues recursiva e iterativa. 3.4.4 ELIMINANDO A RECURSO DE CAUDA Dizemos que uma rotina apresenta recurso de cauda se a chamada recursiva est no final do seu cdigo, tendo como nica funo criar um looping que ser repetido at que a condio de parada seja satisfeita. Vejamos, por exemplo, a funo para clculo de fatorial:

funo FAT (N: inteiro): inteiro; se (N = 0) ento FAT 1 seno FAT N * FAT (N - 1); fim se; fim funo FAT;

Looping!

Observe que a principal funo da chamada FAT (N - 1) criar um looping que se repete at que a condio de parada seja satisfeita. Infelizmente, este exemplo clssico de recurso (clculo de fatorial) um dos casos onde a recurso menos eficiente que a iterao, justamente porque apresenta recurso de cauda.

55 Geralmente, se uma rotina R(x) tem como ltima instruo a chamada recursiva R(y), ento podemos trocar R(y) pela atribuio x y, seguida de um desvio para o incio do cdigo de R. Isto funciona porque reexecutar R, para o novo valor de x, tem o mesmo efeito de chamar R(y). A recurso de cauda pode ser eliminada se empregarmos no seu lugar, uma estrutura de repetio que esteja condicionada expresso de teste usada na verso recursiva. Veja como isto pode ser feito na funo que calcula o fatorial: funo FAT_ITERATIVO (N: inteiro): inteiro; var F : inteiro; F 1; enquanto (N > 0) faa F F * N; N N - 1; fim enquanto; FAT_ITERATIVO F; fim funo FAT; Embora a funo fatorial seja mais eficientemente implementada de forma iterativa, ela ainda um excelente exemplo para se entender o que recurso. 3.4.5 PILHAS E ROTINAS RECURSIVAS O controle de chamadas e retornos de rotinas feito atravs de uma pilha criada e mantida, automaticamente, pelo sistema. Na verdade, quando uma rotina chamada, no apenas o endereo de retorno empilhado, mas todas as suas variveis locais so tambm recriadas na pilha. Por exemplo, ainda na verso recursiva da funo fatorial, para a chamada FAT(4) teramos: 0 1 2 3 4 FAT(0)

n n n n n 4 3 4 FAT(3) 2 3 4 FAT(2) 1 2 3 4 FAT(1)

FAT(4)

Figura 3.35 - Varivel local n sendo recriada na pilha a cada chamada recursiva Quando a rotina comea a ser executada, a varivel n criada no topo da pilha e inicializada com o valor que foi passado no momento da chamada; quando termina, a ltima cpia criada para a varivel deixa de existir. A qualquer instante durante a execuo, o valor assumido para a varivel aquele existente no topo da

56 pilha. Assim, utilizamos a memria para armazenar todos os argumentos intermedirios e valores de retorno na pilha interna do sistema. Isto pode causar problemas se h uma grande quantidade de dados, levando a estouro da pilha. Basicamente, qualquer tipo de recurso pode ser eliminado se utilizarmos no seu lugar comandos de repetio e, eventualmente, pilhas. Normalmente, as rotinas assim modificadas sero mais rpidas que suas correspondentes em verso recursiva. Entretanto, se o uso de muitos comandos de repetio e vrias pilhas for necessrio para realizar a converso da verso recursiva para a iterativa, talvez seja melhor permanecer com a rotina recursiva. Um algoritmo claro, simples e conciso vale mais que qualquer algoritmo envenenado que rode um pouquinho mais rpido. A recurso geralmente utilizada porque simplifica um problema conceitualmente, no porque inerentemente mais eficiente.

57

UNIDADE 4 - RVORES
Uma rvore uma coleo finita de n 0 nodos. Se n = 0, dizemos que a rvore nula; caso contrrio uma rvore apresenta as seguintes caractersticas:

existe um nodo especial denominado raiz; os demais so particionados em T1, T2, ..., Tk estruturas disjuntas de rvores; as estruturas T1, T2, ..., Tk denominam-se subrvores.

A exigncia de que as estruturas T1, T2, ..., Tk sejam colees disjuntas, garante que um mesmo nodo no aparecer em mais de uma subrvore ao mesmo tempo; ou seja, nunca teremos subrvores interligadas. Observe que cada uma das estruturas Ti organizada na forma de rvore, isto caracteriza uma definio recursiva. a

Figura 4.1 A representao grfica de uma rvore O nmero de subrvores de um nodo denomina-se grau. Por exemplo, na Figura 4.1, o nodo a tem grau 3, o nodo d tem grau 2 e o nodo c tem grau 1. Um nodo que possui grau 0, ou seja, que no possui subrvores, denomina-se terminal ou folha; so folhas os nodos e, k, g, l, m, i e j. O grau de uma rvore (n-aridade), definido como sendo igual ao mximo dos graus de todos os seus nodos. Na ilustrao acima, podemos constatar que o grau da rvore 3, pois nenhum nodo tem mais de 3 subrvores. As razes das subrvores de um nodo denominam-se filhos do nodo, que o pai delas. Os filhos do nodo b so e, f e g; h o pai de l e m; os filhos do mesmo pai denominam-se irmos. Intuitivamente, pode-se ainda definir termos tais como av, neto, primo, descendentes, etc. Por definio, dizemos que a raiz de uma rvore encontra-se no nvel 1. Estando um nodo no nvel n, seus filhos estaro no nvel n + 1. A altura de uma rvore definida como sendo o mximo dos nveis de todos os seus nodos. Uma rvore nula tem altura 0. A rvore da figura acima tem altura igual a 4. A subrvore com raiz d tem altura 2. 4.1 RVORES BINRIAS Uma rvore binria uma rvore que pode ser nula, ou ento tem as

58 seguintes caractersticas:

existe um nodo especial denominado raiz; os demais nodos so particionados em T1, T2 estruturas disjuntas de rvores binrias; T1 denominada subrvore esquerda e T2, subrvore direita da raiz.

rvore binria um caso especial de rvore em que nenhum nodo tem grau superior a 2, isto , nenhum nodo tem mais que dois filhos. Adicionalmente, para rvores binrias existe um "senso de posio", ou seja, distingue-se entre uma subrvore esquerda e uma direita. Por exemplo, as rvores binrias representadas na figura abaixo so consideradas distintas: a primeira tem a subrvore direita nula e a segunda, a esquerda. a b a b

Figura 4.2 Duas rvores binrias distintas A terminologia bsica utilizada para rvores binrias a mesma definida para rvores de grau genrico. Podemos dizer agora filho esquerdo, filho direito, etc. 4.1.1 RVORES DE BUSCA BINRIA Uma rvore binria, cuja raiz armazena o elemento R, denominada rvore de busca binria se:

todo elemento armazenado na subrvore esquerda menor que R; nenhum elemento armazenado na subrvore direita menor que R; as subrvores esquerda e direita tambm so rvores de busca binria. d d

(a) ordenada

(b) no-ordenada

Figura 4.3 rvores binrias Observe que a definio de rvore de busca binria tambm recursiva. A rvore representada na figura (b) acima, tem o elemento d na raiz, todos os elementos na subrvore esquerda (a, b e c) so menores que a raiz; nenhum elemento da subrvore direita (d, e e f) menor que a raiz e, no entanto, ela no uma rvore de busca binria. Isto devido ao fato de a subrvore esquerda no ser

59 uma rvore de busca binria. Note que a subrvore esquerda, cuja raiz a, tem uma subrvore esquerda no ser uma rvore de busca binria. Note que a subrvore esquerda, cuja raiz a, tem uma subrvore esquerda que armazena um elemento maior que a raiz. Logo, a rvore como um todo no est de acordo com a definio e no considerada, portanto, uma rvore de busca binria! 4.1.2 OPERAES BSICAS EM RVORES DE BUSCA BINRIA Ao implementarmos rvores binrias, precisamos definir o formato dos nodos a serem empregados na organizao dos dados. Como cada nodo precisa armazenar um elemento e referenciar duas subrvores, podemos defini-lo como a seguir: tipo ELEM = char; ARVBIN = ^NODO; NODO = registro ESQ : ARVBIN; OBJ : ELEM; DIR : ARVBIN; fim registro; var T : ARVBIN; Como para as estruturas definidas anteriormente, uma rvore de busca binria vazia ser representada por uma varivel ponteiro nula. Veja a seguir, a estrutura de representao interna de uma rvore de busca binria na memria:

Figura 4.4 Representao interna de uma rvore binria A grande vantagem de uma lista encadeada que nenhuma movimentao de dados necessria quando um novo elemento precisa ser nela inserido; pois o ajuste de alguns ponteiros suficiente para coloc-lo na posio adequada. A insero numa rvore de busca binria ainda mais eficiente, pois os novos elementos inseridos entram sempre na condio de folhas, isto , um nodo

60 no pode entrar numa rvore e j assumir filhos. Para inserir o elemento c na rvore da figura anterior, comeamos pelo nodo raiz apontado por T. Como c menor que e, tomamos a subrvore da esquerda. Comparando com a nova raiz, temos c > b e conclumos que o elemento c dever ser armazenado na subrvore direita. O processo se repete at chegarmos a uma subrvore nula. Neste ponto, uma folha alocada para armazenar o novo elemento entrando como raiz da subrvore nula. No nosso exemplo, o elemento c entraria como subrvore esquerda do nodo que armazena o elemento d. Inserir um elemento X numa rvore vazia trivial. Se a rvore no se encontra vazia e o elemento a inserir menor que a raiz, o inserimos na subrvore esquerda; caso contrrio, devemos inseri-lo na subrvore direita: procedimento TINS (var T: ARVBIN; X: ELEM); se (T = nil) ento aloque (T); T^.OBJ X; T^.ESQ nil; T^.DIR nil; seno se (X < T^.OBJ) ento TINS (T^.ESQ, X); seno TINS (T^.DIR, X); fim se; fim se; fim procedimento TINS; Uma vez que j podemos criar rvores de busca binria, vamos codificar uma rotina capaz de realizar pesquisas na rvore. Dada uma rvore de busca binria T e um elemento X a ser procurado entre seus nodos, temos quatro possibilidades a considerar:
T uma rvore nula: no h o que fazer; a raiz de T armazena o elemento X: a soluo trivial; o valor de X menor que aquele armazenado na raiz de T: deve-se prosseguir

com a busca na subrvore esquerda de T; o valor de X maior ou igual quele armazenado na raiz de T: deve-se prosseguir com a busca na subrvore direita de T. Quando os elementos que a rvore armazena esto distribudos de forma equilibrada em todas as subrvores, a busca numa rvore binria aproxima-se muito, em eficincia, da busca binria realizada em tabelas seqenciais. A cada comparao, reduzimos aproximadamente para a metade os nodos que ainda tero que ser examinados, acelerando bastante o processo de pesquisa. A funo codificada a seguir, pesquisa uma rvore com o objetivo de encontrar um dado elemento. Se o elemento no est armazenado na rvore, um ponteiro nulo retornado; caso contrrio, a funo retorna um ponteiro para a subrvore cuja raiz armazena o elemento procurado.

61 funo TFND (T: ARVBIN; X: ELEM): ARVBIN; se (T = nil) ento TFND nil; seno se (X = T^.OBJ) ento TFND T; seno se (X < T^.OBJ) ento TFND TFND(T^.ESQ, seno TFND TFND(T^.DIR, fim se; fim se; fim se; fim funo TFND; O algoritmo para remoo em rvore de busca binria talvez o mais trabalhoso que temos. Ainda assim, aps analisarmos todos os possveis casos, ele parecer bastante simples. Para facilitar o entendimento, vamos admitir inicialmente que o elemento a ser removido encontra-se na raiz da rvore de busca binria T. Nestas condies, temos trs possibilidades:
a raiz no possui filhos: a soluo trivial; podemos remov-la e anular T; a raiz possui um nico filho: podemos remover o nodo raiz, substituindo-o pelo

{elemento no foi encontrado}

{elemento encontrado na raiz}

{procura numa subrvore} X); X);

seu nodo-filho; a raiz possui dois filhos: no possvel que os dois filhos assumam o lugar do pai; escolhemos ento o nodo que armazena o maior elemento na subrvore esquerda de T, este nodo ser removido e o elemento armazenado por ele entrar na raiz da rvore T. O problema de remoo pode ser resolvido trivialmente no primeiro e no segundo caso. A dificuldade maior aparece quando o nodo a ser removido tem dois filhos, pois a sua remoo deixaria dois nodos rfos. Para entender a soluo adotada para o terceiro caso, precisamos lembrar da definio de rvore de busca binria. Segundo ela, no podemos ter elementos maiores nem iguais raiz numa subrvore esquerda, tambm no podemos ter elementos menores que a raiz numa subrvore direita. Ora, se tomamos o maior elemento da subrvore esquerda e o posicionarmos na raiz, ento continua valendo a definio, isto , a rvore continua ordenada aps a alterao. Sabemos que o maior elemento numa rvore binria ordenada encontrase no nodo que ocupa a posio mais direita possvel. Este nodo certamente no ter um filho direito, pois se o tivesse no seria o maior da rvore; pode, entretanto, ter um filho esquerdo (menor). Primeiro desenvolveremos uma funo que recebe um ponteiro para uma rvore no-nula, procura o nodo que armazena o seu maior elemento, desliga-o da rvore e retorna o seu endereo:

62 funo GETMAX (var T: ARVBIN): ARVBIN; se (T^.DIR = nil) ento GETMAX T; T T^.ESQ; seno GETMAX GETMAX(T^.DIR); fim se; fim funo GETMAX; De posse da funo GETMAX(), podemos remover facilmente um nodo que tenha dois filhos: P GETMAX (T^.ESQ); {desliga o nodo com o maior valor} T^.OBJ P^.OBJ; {armazena valor na raiz da rvore} desaloque(P); {libera o nodo desocupado} T

Figura 4.5 Resultado da operao P GETMAX (T^.ESQ) Veja, na figura anterior, o resultado da execuo da operao GETMAX() sobre uma rvore de busca binria no-nula. Note que se o nodo removido d tivesse um filho esquerdo, este seria adotado como filho direito pelo nodo b. A simplificao que foi feita inicialmente, supondo que o elemento a ser removido estivesse na raiz da rvore, precisa agora ser desfeita. Na verdade, o elemento a ser removido pode estar armazenado em qualquer posio da rvore, ou mesmo nem existir. Desta forma, antes de remover um elemento de uma rvore, devemos encontrar o nodo que o armazena. Isto j foi visto quando definimos a operao TFND(). O algoritmo que vamos desenvolver agora essencialmente igual quele utilizado para pesquisa. A diferena que, caso o elemento seja encontrado (na raiz de uma rvore), ele ser removido: procedimento TREM (var T: ARVBIN; X: ELEM); var P : ARVBIN;

63 se (T = nil) ento exit; {elemento no foi encontrado}

se (X = T^.OBJ) ento {elemento encontrado na raiz} P T; se (T^.ESQ = nil) ento T T^.DIR; {a raiz no tem filho esquerdo} seno se (T^.DIR = nil) ento T T^.ESQ; {a raiz no tem filho direito} seno {a raiz tem ambos os filhos} P GETMAX(T^.ESQ); T^.OBJ P^.OBJ; fim se; fim se; desaloque(P); seno se (X < T^.OBJ) ento TREM (T^.ESQ, X); seno TREM (T^.DIR, X); fim se; fim se; fim procedimento TREM; 4.1.3 ATRAVESSAMENTO EM RVORES BINRIAS Atravessar uma rvore binria significa passar de forma sistemtica, por cada um de seus nodos, realizando um certo processamento. Existem quatro tipos bsicos de atravessamento:

{procura na subrvore esquerda} {procura na subrvore direita}

em-ordem; pr-ordem; ps-ordem; em nvel.

Os trs primeiros tipos de atravessamento so facilmente compreendidos se fizermos uma analogia entre eles e as notaes segundo as quais uma expresso aritmtica pode ser escrita: vamos considerar, por exemplo, a expresso infixa A + B representada da seguinte forma: +

Figura 4.6 A expresso A + B como rvore binria

64 Para reconstituir a forma infixa da expresso, deveramos imprimir os nodos da seguinte maneira:

imprimir a folha esquerda imprimir a raiz imprimir a folha direita

(E) (R) (D)

Para obter a forma prefixa, fazemos:


imprimir a raiz imprimir a folha esquerda imprimir a folha direita

(R) (E) (D)

E, finalmente, para a forma posfixa teramos:


imprimir a folha esquerda imprimir a folha direita imprimir a raiz

(E) (D) (R)

A seqncia de acesso ERD, necessria para reconstituir a forma infixa, denominada atravessamento em-ordem, a seqncia RED, para forma pr-fixa, denomina atravessamento em pr-ordem e, finalmente, a seqncia EDR denominada atravessamento ps-ordem. Se o atravessamento em-ordem for aplicado sobre uma rvore de busca binria, ento conseguimos acessar (processar) os nodos em ordem crescente. Isto muito interessante, quando desejamos armazenar seqncias ordenadas atravs do uso de rvores binrias. b

Figura 4.7 Uma rvore de busca binria Se as trs formas de atravessamento so aplicadas rvore da figura anterior, temos:

em-ordem: a, b, c pr-ordem: b, a, c ps-ordem: a, c, b

O exemplo da Figura 4.7 usa uma rvore muito simples; seja ento uma rvore binria um pouco mais complexa (Figura 4.8), onde suas rvores no so apenas folhas. Caso fosse aplicado o atravessamento em-ordem (ERD) para a rvore acima, os nodos seriam impressos na ordem: a, b, c, d, e, f. Para obter esta seqncia, fcil perceber que primeiro a subrvore esquerda (da raiz d) foi impressa em-ordem, produzindo a seqncia a, b, c, depois, foi impressa a raiz d e, finalmente, a subrvore direita tambm foi impressa em-ordem.

65

d b a c e f

Figura 4.8 Uma rvore binria com subrvores Analisando melhor as figuras e a analogia feita a partir delas, conclumos que a seqncia bsica de acesso ERD pode ser generalizada. Na verdade, cada subrvore no precisa se restringir a uma nica folha:

imprimir a subrvore esquerda imprimir a raiz imprimir a subrvore direita

(E) (R) (D)

Observe que a seqncia ERD agora tornou-se recursiva, pois ambas as subrvores devem ser impressas tambm em-ordem e a recurso pra quando chegamos a subrvores nulas. As seqncias pr-ordem e ps-ordem podem ser generalizadas segundo o mesmo raciocnio. Veja os algoritmos codificados a seguir: procedimento EMORDEM (T: ARVBIN); se (T <> nil) ento EMORDEM (T^.ESQ); escreva (T^.OBJ, , ); EMORDEM (T^.DIR); fim se; fim procedimento EMORDEM;

procedimento PREORDEM (T: ARVBIN); se (T <> nil) ento escreva (T^.OBJ, , ); PREORDEM (T^.ESQ); PREORDEM (T^.DIR); fim se; fim procedimento PREORDEM;

procedimento POSORDEM (T: ARVBIN); se (T <> nil) ento POSORDEM (T^.ESQ);

66 POSORDEM (T^.DIR); escreva (T^.OBJ, , ); fim se; fim procedimento POSORDEM; O quarto tipo de atravessamento, denominado em-nvel, apesar de ser o mais facilmente compreendido, aquele que apresenta maior dificuldade de programao. Supondo que ele fosse aplicado rvore da figura anterior, obteramos a seqncia: d, b, e, a, c, f. Observe que agora os nodos so acessados, por nvel, da esquerda para a direita. Para realizar um atravessamento em-nvel, precisamos de uma fila contendo inicialmente apenas o nodo raiz. A partir da, enquanto a fila no se tornar vazia, retiramos dela um nodo cujos filhos devero ser colocados na fila, a ento, o nodo retirado da fila pode ser impresso. procedimento EMNIVEL (T : ARVBIN); var N : ARVBIN; F : FILA; se (T <> nil) ento QINIT (F); ENQUEUE (F, T); enquanto (no QISEMPTY(F)) faa N DEQUEUE (F); se (N^.ESQ <> nil) ento ENQUEUE (F, N^.ESQ); se (N^.DIR <> nil) ento ENQUEUE (F, N^.DIR); escreva (N^.OBJ, , ); fim enquanto; fim se; fim procedimento EMNIVEL; Comeamos a falar de atravessamento, dizendo que atravessar uma rvore passar por todos os seus nodos realizando, para cada um deles, um certo processamento. Nos quatro algoritmos que desenvolvemos, o processamento realizado foi uma simples impresso, entretanto qualquer outra tarefa poder ter sido executada. Por exemplo, o algoritmo TFND() emprega o atravessamento pr-ordem e, para cada nodo acessado, o processamento consiste numa comparao que verifica se o elemento armazenado pelo nodo aquele procurado. Muitos outros algoritmos importantes no tratamento de rvores so baseados nestes tipos de atravessamento. Veja, por exemplo, o algoritmo TKILL(), para destruir rvores: ele usa um atravessamento do tipo ps-ordem, cujo processamento a liberao do nodo acessado:

67 procedimento TKILL (var T: ARVBIN); se (T <> nil) ento TKILL (T^.ESQ); TKILL (T^.DIR); desaloque (T); T nil; fim se; fim procedimento TKILL; 4.2 RVORES BALANCEADAS Para obtermos o mximo desempenho num busca binria, necessrio que a rvore de busca binria esteja perfeitamente balanceada. Uma rvore binria perfeitamente balanceada aquela em que, para todo nodo da rvore, a diferena entre a altura da subrvore da esquerda e a altura da subrvore da direita seja nula. Existem mtodos que balanceiam perfeitamente uma rvore binria; entretanto estes mtodos so caros computacionalmente e, aps algumas inseres e excluses de elementos, a rvore tornar-se-ia novamente desbalanceada. Um critrio menos rigoroso de balanceamento poderia ser adotado, conduzindo a procedimentos mais simples de reorganizao da rvore, acarretando apenas uma pequena deteriorao no desempenho mdio do processo de busca. 4.3 RVORES-AVL Uma definio de balanceamento, menos rigorosa, foi apresentada por Adelson-Velskii e Landis. O critrio de balanceamento o seguinte: Uma rvore dita balanceada se e somente se, para qualquer nodo, a altura de suas duas subrvores diferem de no mximo uma unidade. As rvores que satisfazem esta condio so, em geral, denominadas rvores-AVL (em homenagem a seus inventores). Note que todas as rvores perfeitamente balanceadas so tambm AVL-balanceadas. A definio no apenas simples como tambm conduz a um procedimento de rebalanceamento relativamente simples. Na figura seguinte so apresentados duas rvores: uma delas rvore-AVL, a outra no.

{libera subrvore esquerda} {libera subrvore direita} {libera a raiz da rvore}

rvore-AVL

rvore no-AVL

Figura 4.9 Exemplos de rvore-AVL e de rvore no-AVL

68 Um nodo da rvore que apresenta diferena de altura entre suas subrvores esquerda e direita menor que 1, chamado de nodo regulado, caso contrrio chamado de nodo desregulado. Uma rvore que contenha um nodo desregulado tambm chamada de desregulada. Na figura acima o nodo v um nodo desregulado pois a diferena de altura entre suas subrvores esquerda e direita igual a 2 e, portanto, a rvore no-AVL. 4.3.1 INCLUSO EM RVORES-AVL Seja T uma rvore AVL, na qual sero efetuadas incluses de nodos. Para que T se mantenha como rvore AVL aps as incluses preciso efetuar operaes de restabelecimento da regulagem de seus nodos, quando necessrio. A idia consiste em verificar, aps cada incluso, se algum nodo p se encontra desregulado, isto , se a diferena de altura entre as duas subrvores de p tornou-se maior do que um. Em caso positivo, aplicar transformaes apropriadas para regul-lo. Sero utilizadas quatro transformaes, indicadas na Figura 4.10, denominadas, respectivamente, rotao direita, rotao esquerda, rotao dupla direita e rotao dupla esquerda. As subrvores T1, T2, T3 e T4 que aparecem na figura podem ser vazias ou no. O nodo p chamado raiz da transformao. Por exemplo, a Figura 4.11(c) representa o efeito de uma rotao direita da raiz e, efetuada na rvore da Figura 4.11(b). Observe que essas transformaes preservam a natureza da rvore como sendo binria de busca. Isto , se o valor da chave de um nodo v for inferior ao da correspondente ao nodo w, ento acontece uma dentre as seguintes possibilidades: v pertence subrvore esquerda de w, ou w pertence subrvore direita de v. Uma anlise da operao de incluso efetuada a seguir. No processo, tornar-se- evidente como aplicar as transformaes. Suponha que o nodo q foi includo em T. Se aps a incluso todos os nodos mantiverem-se regulados, ento a rvore manteve-se AVL e no h nada a efetuar. Caso contrrio, seja p o nodo mais prximo s folhas de T que se tornou desregulado. Observe que no h ambigidade na escolha de p, pois qualquer subrvore de T que se tornou desregulada aps a incluso de q deve necessariamente conter p. Ento p se encontra no caminho de q raiz de T, e sua escolha nica. Sejam hE(p) e hD(p) as alturas das subrvores esquerda e direita de p, respectivamente. Naturalmente, |hE(p) - hD(p)| > 1. Ento, conclui-se que |hE(p) - hD(p)| = 2, pois T era uma rvore-AVL e, alm disso, a incluso de um nodo no pode aumentar em mais de uma unidade a altura de qualquer subrvore. Podem-se identificar os seguintes casos de anlise:

Caso 1: hE(p) > hD(p)

Ento, q pertence subrvore esquerda de p. Alm disso, p possui o filho esquerdo u q. Pois, caso contrrio, p no estaria desregulado. Finalmente, por esse mesmo motivo, sabe-se que hE(u) hD(u). As duas seguintes possibilidades podem ocorrer:

Caso 1.1: hE(u) > hD(u) Essa situao corresponde da Figura 4.10(a), sendo q um nodo

69 pertencente a T1. Observe que h(T1) h(T2) = 1 e h(T2) = h(T3). Conseqentemente, a aplicao da rotao direita da raiz p transforma a subrvore considerada na da Figura 4.10(b), o que restabelece a regulagem de p.

Figura 4.10 Rotaes para restabelecer a regulagem dos nodos em rvores-AVL

70

g g e e j c c f k b b d a (a)

g j c f k b d a (b) Figura 4.11 Regulagem de uma rvore-AVL d (c) f e k j

Caso 1.2: hD(u) > hE(u)

Ento u possui o filho direito v, e a situao ilustrada na Figura 4.10(e). Nesse caso, vale |h(T2) h(T3)| 1 e max{h(T2), h(T3)} = h(T1) = h(T4) Aplica-se ento a rotao dupla direita da raiz p. A nova configurao aparece na Figura 4.10(f) e a regulagem de p restabelecida.

Caso 2: hD(p) > hE(p)

Dessa vez, p possui o filho direito z p. Segue-se que hE(z) hD(z) e as demais situaes so as seguintes:

Caso 2.1: hD(z) > hE(z)

Esse caso corresponde Figura 4.10(c), sendo q pertencente a T3. As relaes de altura so agora h(T3) h(T2) = 1 e h(T2) = h(T1) Isso significa que a rotao esquerda torna a rvore novamente AVL (Figura 4.10(d)).

Caso 2.2: hE(z) > hD(z)

Ento z possui o filho esquerdo y (Figura 4.10(g)). Observe que as alturas das subrvores T1, T2, T3 e T4 satisfazem as mesmas ralaes do caso 1.2. Dessa forma, a aplicao da rotao dupla esquerda transforma a subrvore na indicada em 4.10(h). E p torna-se novamente regulado.

71 A argumentao apresentada mostra que a regulagem de p restabelecida pela aplicao apropriada de uma das transformaes estudadas. Alm disso, fcil concluir que o pai de p, aps a transformao, encontra-se tambm regulado, pois a transformao diminui a unidade de altura da subrvore de raiz p. Isto assegura a regulagem de todos os nodos ancestrais de p. Conseqentemente, uma nica transformao suficiente para regular T. Como exemplo, considere a rvore AVL da Figura 4.11(a), onde o valor de cada chave o valor alfabtico indicado. Suponha que o nodo a deva ser includo na rvore. Aps a incluso, esta se transforma na rvore da Figura 4.11(b), que no-AVL. O nodo e tornou-se desregulado, pois a altura de sua subrvore esquerda excede em duas unidades a da direita. Alm disso, esse nodo o mais prximo s folhas da rvore, dentre aqueles desregulados. Finalmente, o novo nodo a pertence subrvore esquerda do filho c de e. Logo, a situao corresponde ao caso 1 da anlise. A aplicao de uma rotao direita da raiz e torna esse nodo novamente regulado. O resultado da rotao est ilustrado na Figura 4.11(c). Observe, na Figura 51.11(b), que o nodo g tambm se encontra desregulado. A rotao utilizada tambm o regula. 4.3.2 IMPLEMENTAO DA INCLUSO Nessa seo, apresentada a implementao da operao de incluso em rvores-AVL. Seja T uma rvore-AVL e x a chave a ser includa em algum novo nodo q. O processo completo de incluso pode ser dividido do seguinte modo. Inicialmente, efetua-se uma busca em T para verificar se x j se encontra em T. Em caso positivo, o processo deve ser encerrado, pois trata-se de uma chave duplicata. Caso contrrio, a busca encontra o local correto do novo nodo. Segue-se, ento, a incluso propriamente dita. Deve-se verificar, em seguida, se essa operao tornou algum nodo desregulado. Em caso negativo, o processo termina, pois T permanece AVL. Caso contrrio, a regulagem de T deve ser efetuada. De acordo com a ltima seo, essa regulagem consiste na execuo de uma das operaes de rotao da Figura 4.10. A ltima etapa, pois, consiste no reconhecimento da operao de rotao indicada ao caso e em sua posterior execuo. Isto devolve a T a condio de rvore-AVL. Em seguida, sero discutidos alguns aspectos do procedimento anterior. O primeiro problema ainda no detalhado o de verificar se algum nodo v de T se tornou desregulado aps a incluso. H uma soluo direta bastante simples para resolver essa questo. Basta determinar as alturas de suas duas subrvores e subtrair uma da outra. Essa operao pode ser realizada sem dificuldades, percorrendo a subrvore de raiz v em T. Mas isso consome tempo. Alm disso, em princpio, qualquer nodo do caminho de q at a raiz da rvore pode ter se tornado desregulado. Ento, a aplicao direta deste procedimento conduz a um algoritmo complexo, o que o torna fora de cogitao. Essa complexidade pode ser facilmente reduzida percorrendo-se T de baixo para cima e armazenando-se o valor da altura de cada subrvore. Naturalmente, h(v) = 1 + max{hE(v), hD(v)}. Entretanto, para alcanar a meta de efetuar a incluso num tempo menor, utiliza-se um processo diferente. Define-se o rtulo balano(v), para cada nodo v de T como: balano(v) = hD(v) hE(v)

72 Observa-se que v est regulado se e somente se 1 balano(v) 1. O problema como atualizar balano(v) de forma eficiente, tendo em vista a incluso de q. A idia consiste em determinar o efeito dessa incluso nos valores de balano(v). Se q pertencer subrvore esquerda de v e essa incluso ocasionar um aumento na altura dessa subrvore, ento subtrai-se uma unidade de balano(v). Se esse valor decrescer para 2, indica que v se tornar desregulado. Analogamente, se q pertencer subrvore direita de v e provocar um aumento de sua altura, adiciona-se uma unidade a balano(v). O nodo v ficara desregulado, nesse caso, se balano(v) aumentar para 2. Para completar o processo, resta ainda determinar em que campos a incluso de q provoca acrscimo na altura h(v) da subrvore Tv. Para resolver esta questo, interessante que se obtenha um mtodo simples e eficiente, isto , que possa ser resolvido em tempo constante. De incio, observa-se que a insero do nodo q acarreta obrigatoriamente uma alterao na altura da subrvore esquerda ou direita (de acordo com a nova chave) de seu nodo pai w. A situao do campo balano permite avaliar se essa alterao pode, ou no, se propagar aos outros nodos v do caminho de w at a raiz da rvore. Suponha que o nodo q inserido na subrvore esquerda de v. A anlise se inicia com v = w e prossegue em seus nodos ancestrais, de forma recursiva. O processo se encerra quando da constatao de que a altura de Tv no foi modificada, ou de que v se tornou regulado. Trs casos distintos podem ocorrer: Caso 1: balano(v) = 1 antes da incluso.

Neste caso, balano(v) se torna 0 e a altura da subrvore de raiz v no foi modificada. Conseqentemente, as alturas dos nodos restantes do caminho at a raiz da rvore no se alteram. Caso 2: balano(v) = 0 antes da incluso.

Neste caso, balano(v) se torna 1 e a altura da subrvore de raiz v foi modificada. Conseqentemente, os nodos restantes do caminho at a raiz tambm podem ter suas alturas modificadas e devem ser analisados. Se v a raiz de T, a anlise encerra, pois nenhum nodo se tornou desregulado. Caso contrrio, repetir o processo com v substitudo por seu pai. Caso 3: balano(v) = -1 antes da incluso.

Neste caso, balano(v) se torna 2 e o nodo est desregulado. A rotao correta deve ser empregada. Qualquer rotao implica que a subrvore resultante tenha a mesma altura da subrvore antes da incluso. As alturas dos ancestrais de v no mais necessitam de avaliao. Para uma insero na subrvore direita de v, casos simtricos devem ser considerados. Aps a regulagem da rvore, completa-se o processo. O algoritmo visto a seguir busca e insere, se possvel, uma nova chave x numa rvore-AVL. Observe que um s algoritmo foi considerado para as duas tarefas, ao contrrio do que j foi visto anteriormente. Tal enfoque se deve necessidade de conhecer todo o caminho da raiz at o nodo inserido ao se regular a rvore. O procedimento recursivo insAVL cumpre essa tarefa.

73 De incio a posio de insero procurada determinando-se, na pilha de recurso, o caminho da raiz at o novo nodo. O procedimento incio-nodo aloca o novo nodo. Aps a insero, o procedimento percorre o caminho inverso na rvore, acertando a campo bal que implementa o rtulo balano, j mencionado. Por meio deste pode-se conferir a regulagem de cada nodo, determinando-se ento o nodo p apontado por pt. A operao de rotao conveniente efetuada imediatamente. O parmetro lgico h retornar se houve ou no alterao na altura da subrvore do nodo em questo, induzindo ento atualizao do campo bal. A varivel ptraiz um ponteiro para a raiz da rvore. A chamada externa ins-AVL (ptraiz, x, h). tipo ELEM = char; ARVAVL = ^NODO; BALANCO = (-1, 0, +1); NODO = registro ESQ : ARVAVL; OBJ : ELEM; DIR : ARVAVL; BAL : BALANCO; fim registro; var T : ARVAVL;

procedimento INCIO-NODO (var T: ARVAVL; X: ELEM); aloque T^.ESQ T^.DIR T^.OBJ T^.BAL (T); nil; nil; X; 0;

fim procedimento INCIO-NODO;

procedimento CASO1 (var T: ARVAVL; var H: lgico); var TU, TV : ARVAVL; TU T^.ESQ; se (TU^.BAL = -1) ento T^.ESQ TU^.DIR; TU^.DIR T; T^.BAL 0; T TU; seno TV TU^.DIR;

74 TU^.DIR TV^.ESQ; TV^.ESQ TU; T^.ESQ TV^.DIR; TV^.DIR T; se (TV^.BAL = -1) ento T^.BAL 1 seno T^.BAL 0; fim se; se (TV^.BAL = 1) ento TU^.BAL -1 seno TU^.BAL 0; fim se; T TV; fim se; T^.BAL 0; H false; fim procedimento CASO1;

procedimento CASO2 (var T: ARVAVL; var H: lgico); var TU, TV : ARVAVL; TU T^.DIR; se (TU^.BAL = 1) ento T^.DIR TU^.ESQ; TU^.ESQ T; T^.BAL 0; T TU; seno TV TU^.ESQ; TU^.ESQ TV^.DIR; TV^.DIR TU; T^.DIR TV^.ESQ; TV^.ESQ T; se (TV^.BAL = 1) ento T^.BAL -1 seno T^.BAL 0; fim se; se (TV^.BAL = -1) ento TU^.BAL 1 seno TU^.BAL 0;

75 fim se; T TV; fim se; T^.BAL 0; H false; fim procedimento CASO2;

procedimento INS-AVL (var T: ARVAVL; X: ELEM; var H: lgico); se (T = nil) ento INCIO-NODO (T, X); H true seno se (X = T^.OBJ) ento exit; {sai do procedimento} se (X < T^.OBJ) ento INS-AVL (T^.ESQ, X, H); se (H) ento caso T^.BAL 1 : T^.BAL 0; H false; 0 : T^.BAL -1 -1 : CASO1 (T, H); {rebalanceamento} fim caso; fim se seno INS-AVL (T^.DIR, X, H); se (H) ento caso T^.BAL -1 : T^.BAL 0; H false; 0 : T^.BAL 1 1 : CASO2 (T, H); {rebalanceamento} fim caso; fim se; fim se; fim se; fim procedimento INS-AVL;

76 4.3.3 REMOES EM RVORES-AVL A remoo em rvores-AVL muito mais complexa que a insero. Isto realmente verdade, embora a operao de rebalanceamento seja similar realizada na insero. Em particular, o rebalanceamento efetuado, novamente, atravs de rotaes simples ou duplas dos nodos. Os casos mais simples so os nodos terminais e nodos com apenas um nico descendente. Se o nodo a ser removido tiver duas subrvores, deve-se novamente substitu-lo pelo nodo mais direita de sua subrvore esquerda. De maneira similar ao que ocorre no caso da insero, passado um parmetro h, passado por referncia e de tipo lgico, indicando que a altura da rvore foi reduzida. O rebalanceamento deve ser efetuado somente quando o valor de h for verdadeiro. A varivel h se torna verdadeira sempre que um nodo localizado e removido, ou ento se o prprio rebalanceamento reduzir a altura de alguma subrvore. No algoritmo, implementado a seguir, foram introduzidas as duas operaes (simtricas) de balanceamento na forma de procedimentos, j que tais operaes so necessrias em mais de um ponto do algoritmo de remoo. Note-se que os procedimentos balanceL e balanceR so aplicados quando os ramos esquerdo e direito, respectivamente, tiverem sido reduzidas em sua altura. A operao do procedimento est ilustrada na Figura 4.12. Dada a rvoreAVL (a), as remoes sucessivas dos nodos com chaves 4, 8, 6, 5, 2, 1 e 7 resultam nas rvores (b)..(h). A remoo da chave 4 , por si mesma, bastante simples, pois representa um nodo terminal. Entretanto, ela resulta em desbalanceamento do nodo 3. A correspondente operao de rebalanceamento envolve uma rotao simples direita. O rebalanceamento se torna novamente necessrio aps a remoo do nodo 6. Desta vez a subrvore direita da raiz (7) rebalanceada por meio de uma rotao simples esquerda. A remoo do nodo 2, embora trivial em si por possuir apenas um nico descendente, exige uma complexa operao de rotao dupla esquerda do nodo 3. O ltimo caso, uma rotao simples esquerda, finalmente executada aps a remoo do nodo 7, que foi de incio substitudo pelo elemento mais direita de sua subrvore esquerda, isto , pelo nodo de chave 3.

Figura 4.12 Remoes em rvores-AVL

77 procedimento BALANCEL (var T: ARVAVL; var H: lgico); var T1, T2 : ARVAVL; B1, B2 : BALANCO; caso T^.BAL -1 : T^.BAL 0; 0 : T^.BAL 1; H false; 1 : T1 T^.DIR; B1 T1^.BAL; se (B1 >= 0) ento T^.DIR T1^.ESQ; T1^.ESQ T; se (B1 = 0) ento T^.BAL 1; T1^.BAL -1; H false; seno T^.BAL 0; T1^.BAL 0; fim se; T T1; seno T2 T1^.ESQ; B2 T2^.BAL; T1^.ESQ T2^.DIR; T2^.DIR T1; T^.DIR T2^.ESQ; T2^.ESQ T; se (B2 = 1) ento T^.BAL -1; seno T^.BAL 0; fim se; se (B2 = -1) ento T1^.BAL 1; seno T1^.BAL 0; fim se; T T2; T2^.BAL 0; fim se;

78 fim caso; fim procedimento BALANCEL;

procedimento BALANCER (var T: ARVAVL; var H: lgico); var T1, T2 : ARVAVL; B1, B2 : BALANCO; caso T^.BAL 1 : T^.BAL 0; 0 : T^.BAL -1; H false; -1 : T1 T^.ESQ; B1 T1^.BAL; se (B1 <= 0) ento T^.ESQ T1^.DIR; T1^.DIR T; se (B1 = 0) ento T^.BAL -1; T1^.BAL 1; H false; seno T^.BAL 0; T1^.BAL 0; fim se; T T1; seno T2 T1^.DIR; B2 T2^.BAL; T1^.DIR T2^.ESQ; T2^.ESQ T1; T^.ESQ T2^.DIR; T2^.DIR T; se (B2 = -1) ento T^.BAL 1; seno T^.BAL 0; fim se; se (B2 = 1) ento T1^.BAL -1; seno T1^.BAL 0; fim se;

79 T T2; T2^.BAL 0; fim se; fim caso; fim procedimento BALANCER;

procedimento DELETE (var T: ARVAVL; X: ELEM; var H: lgico); var Q : ARVAVL; procedimento DEL (var R: ARVAVL; var H: lgico); se (R^.DIR <> nil) ento DEL (R^.DIR, H); se (H) ento BALANCER(R, H); seno Q^.OBJ R^.OBJ; Q R; R R^.ESQ; H true; fim se; fim procedimento DEL; se (T = nil) ento exit; {chave no encontrada. Sair!} seno se (T^.OBJ > X) ento DELETE (T^.ESQ, X, H); se (H) ento BALANCEL (T, H); seno se (T^.OBJ < X) ento DELETE(T^.DIR, X, H); se (H) ento BALANCER (T, H); seno Q T; se (Q^.DIR = nil) ento T Q^.ESQ; H true; seno se (Q^.ESQ = nil) ento T Q^.DIR; H true; seno DEL (Q^.ESQ, H);

80 se (H) ento BALANCEL (T, H); fim se; fim se; desaloque (Q); fim se; fim se; fim se; fim procedimento DELETE; 4.4 RVORES HEAP E HEAPSORT O heap uma estrutura de dados eficiente que suporta as operaes constri, insere, retira, substitui e altera. A estrutura foi proposta por Williams (1964). Um heap definido como uma seqncia de itens com chaves c[1], c[2], c[3], ..., c[n] tal que c[i] c[2i] c[i] c[2i + 1] para todo i = 1, 2, 3, ..., n/2 Observe a rvore seguinte: 1 S 2 R 4 E 5 N 3 O 6 A 7 D

Esta rvore binria representa um heap. As linhas que saem de uma chave sempre levam a duas chaves menores no nvel inferior. Conseqentemente, a chave do nodo raiz a maior chave do conjunto. Uma rvore binria completa pode ser representada por um array, conforme ilustra a figura a seguir: 1 S 2 R 3 O 4 E 5 N 6 A 7 D

Esta representao extremamente compacta e permite caminhar facilmente pelos nodos da rvore: os filhos de um nodo i esto nas posies 2i e 2i + 1 (caso existam), e o pai de um nodo i est na posio i div 2. Um heap uma rvore binria na qual cada nodo satisfaz a condio do heap apresentada acima. No caso da representao do heap por vetor, a maior

81 chave est sempre na posio 1 do vetor. Os algoritmos para implementar as operaes sobre o heap operam ao longo de um dos caminhos da rvore, a partir da raiz at o nvel inferior da rvore. 4.2.1 HEAPSORT O primeiro passo construir um heap. Um mtodo elegante foi apresentado por Floyd (1964). Dado um vetor A[1], A[2], ..., A[n], os itens A[n/2 + 1], A[n/2 + 2], ..., A[n] formam um heap pois no h dois ndices i, j tais que j = 2i ou j = 2i + 1. Considere o seguinte vetor: 1 O 2 R 3 D 4 E 5 N 6 A 7 S

A:

Os itens A[4] a A[7] formam a parte inferior da rvore binria associada, onde nenhuma relao de ordem necessria para formarem um heap. A seguir o heap estendido para a esquerda (Esq = 3), englobando o item A[3], pais dos itens A[6] e A[7]. Neste momento a condio do heap violada, e os itens D e S so trocados. 1 2 3 4 5 6 7 A: O R D E N A S Esq = 3 O R S E N A D A seguir o heap novamente estendido para a esquerda (Esq = 2) incluindo o R, passo que no viola a condio do heap. Finalmente, o heap estendido para a esquerda (Esq = 1), incluindo o item O, e os itens O e S so trocados, encerrando o processo. 1 A: O Esq = 1 S 2 R R 3 S O 4 E E 5 N N 6 A A 7 D D

O procedimento a seguir mostra a implementao do algoritmo para construir o heap. O procedimento REFAZ reconstri o heap conforme descrito anteriormente. procedimento CONSTROI (var A: vetor; N: inteiro); var Esq : inteiro; procedimento REFAZ (ESQ, DIR: inteiro; var A: vetor); var I, J : inteiro; X : ELEM; I ESQ; J 2 * I; X A[I];

82

enquanto (J <= DIR) faa se (J < DIR) ento se (A[J] < A[J + 1]) ento J J + 1; fim se; fim se; se (X >= A[J]) ento break; fim se; A[I] A[J]; I J; J 2 * i; fim enquanto; A[I] X; fim procedimento REFAZ; ESQ (N div 2) + 1; enquanto (ESQ > 1) faa ESQ ESQ 1; REFAZ (ESQ, N, A); fim enquanto; fim procedimento CONSTROI; A partir do heap obtido pelo procedimento CONSTROI, pega-se o item na posio 1 do vetor (raiz do heap) e troca-se com o item que est na posio n do vetor. A seguir, basta usar o procedimento REFAZ para reconstituir o heap para os itens A[1], A[2], ..., A[n 1]. Repita estas duas operaes com os n 1 itens restantes, depois com os n 2 itens, at que reste apenas 1 item. Na figura abaixo apresentado um exemplo do heapsort. Os valores assinalados representam os valores afetados pelo procedimento REFAZ. 1 S R O N E D A 2 R N N E D A D 3 O O A A A E 4 E E E D N 5 N D D O 6 A A R 7 D S

O programa a seguir mostra a implementao da rotina HEAPSORT.

83 procedimento HEAPSORT (var A: vetor; N: inteiro); var ESQ, DIR : inteiro; X : ELEM; {Aqui deve ser inserido o procedimento REFAZ} {Constri o heap} ESQ (N div 2) + 1; DIR N; enquanto (ESQ > 1) faa ESQ ESQ 1; REFAZ (ESQ, DIR, A); fim enquanto; {ordena o vetor} enquanto (DIR > 1) faa X A[1]; A[1] A[DIR]; A[DIR] X; DIR DIR 1; REFAZ (ESQ, DIR, A); fim enquanto; fim procedimento HEAPSORT; 4.5 RVORES B At agora, restringiu-se a discusso aos casos de rvores em que cada n possui no mximo dois descendentes, ou seja, estudaram-se apenas rvores binrias. Isto, evidentemente, satisfatrio se desejamos representar relaes de parentesco de pessoas com o enfoque de rvore genealgica, ou seja, na forma de um relacionamento em que cada pessoa est associada a seus pais. Afinal de contas, ningum possui mais do que um pai e uma nica me. Como seria, entretanto, se fosse preferido o enfoque do ponto de vista dos descendentes? Ser necessrio considerar o fato de que algumas pessoas possuem mais do que dois filhos, e que suas rvores contero ns com muitos ramos. Por falta de um termo melhor, tais rvores sero denominadas rvores multidirecionais. Naturalmente, no h nada de especial em tais estruturas, e j estudamos todos os recursos de programao e de definio de dados que permitem tratar tais situaes. Se, por exemplo, for imposto um limite superior absoluto para um nmero de filhos, ento possvel representar os filhos atravs de um vetor que seja um dos componentes do registro que representa uma pessoa. Entretanto, se o nmero de filhos variar muito de uma pessoa para outra, isto poder resultar em uma subutilizao das reas de memria. Neste caso, ser mais apropriado representar a descendncia na forma de uma lista linear, com um apontador para o descendente mais novo (ou mais velho) associado ao genitor. Uma possvel definio de tipo para

84 este caso apresentada a seguir, e uma possvel estrutura de dados est representada na Figura 4.13. tipo PTR = ^PESSOA; tipo PESSOA = registro NOME : caractere; IRMOS, DESCENDENTES : ^PESSOA; fim registro; Entretanto, existe uma rea muito prtica, de interesse geral, para aplicao de rvores multidirecioais. a construo e a manuteno de rvores de busca de grandes dimenses, nas quais as inseres e remoes so necessrias, mas para as quais a rea da memria principal do computador no suficientemente grande. Admita-se, ento, que os ns de uma rvore estejam armazenados em dispositivos de memria secundria, tais como um disco. As estruturas dinmicas introduzidas neste captulo so particularmente adequadas para incorporaes de dispositivos de armazenamento secundrio. A principal inovao simplesmente que os apontadores agora referemse a reas da memria de massa, em vez de representarem endereos de reas da memria principal. Joo Pedro

Alberto

Maria

Roberto

Carla

Cris

Joana

Paulo

Jorge

Lcia

Figura 4.13 Uma rvore multidirecional representada como rvore binria Utilizar uma rvore binria para conjuntos de dados compostos de milhes de elementos requer, em mdia, aproximadamente, log2 106 (isto , cerca de 20) passos de busca. Como agora, cada passo envolve um acesso de disco (com os inerentes atrasos de acesso), torna-se bastante desejvel uma organizao de memria capaz de permitir a reduo do nmero de acessos. A rvore multidirecional uma soluo perfeita para este problema. Se for efetuado um acesso a um elemento localizado na memria secundria, possvel efetuar um acesso a todo um grupo completo de elementos sem muito custo adicional. Isto sugere que a rvore seja subdividida em subrvores, e que as subrvores sejam representadas como unidades, do ponto de vista do acesso. Essas subrvores sero denominadas pginas. A Figura 4.14 mostra uma rvores binria subdividida em pginas, cada qual formada por 7 ns.

85

Figura 4.14 Uma rvore binria subdividida em pginas A reduo do nmero de acessos a disco cada acesso a uma pgina envolve agora um acesso ao disco pode ser considervel. Suponha-se que se deseje alojar 100 ns em cada pgina, ento a rvore de busca de milhes de elementos exigir em mdia apenas log100 106 (isto , cerca de 3) acessos pgina, em vez de 20. Porm, naturalmente, se o crescimento da rvore for aleatrio, ento, no pior caso, pode-se ainda ter um nmero de acessos da ordem de 104. Portanto, um esquema de crescimento controlado quase obrigatrio no caso de rvores multidirecionais. 4.4.1 RVORES B MULTIDIRECIONAIS Caso se deseje definir um critrio de crescimento controlado, ento a exigncia de um balanceamento perfeito rapidamente eliminada, j que o custo computacional de balanceamento muito alto. Para viabiliz-lo, necessrio relaxar as exigncias de alguma forma. Um critrio bastante conveniente foi proposto por R. Bayer e E. M. McCreight em 1970; todas as pginas, exceto uma, contm um nmero de ns entre n e 2n, para uma dada constante n. Assim, em uma rvore com N elementos, e com pginas de, no mximo, 2n ns, o pior caso exigir logn N acessos pgina. evidente que boa parte do custo computacional da pesquisa devido exatamente a tais acessos. Alm disso, o fator de utilizao de memria, importante parmetro de desempenho, de pelo menos 50%, uma vez que as pginas esto sempre, no mnimo, preenchidas pela metade como decorrncia da hiptese inicial. Com todas estas vantagens, o esquema envolve algoritmos comparativamente simples de busca, insero e remoo. Estes algoritmos sero estudados a seguir em maiores detalhes. As estruturas de dados sobre as quais os algoritmos devero operar so denominadas rvores B, e apresentam as seguintes caractersticas (n definido como sendo a ordem da rvore B):

Cada pgina contm no mximo 2n elementos (chaves); Cada pgina, exceto a que contm a raiz, apresenta no mnimo n elementos; Cada pgina ou uma pgina terminal (folha), isto , no possui descendentes, ou ento possui m + 1 descendentes, onde m o nmero de chaves contido nesta pgina; Todas as pginas terminais aparecem no mesmo nvel.

A Figura 4.15 mostra uma rvore B de ordem 2 com 3 nveis. Todas as pginas contm 2, 3 ou 4 elementos; a exceo a raiz, qual permitido

86 apresentar somente um nico elemento. Todas as pginas terminais aparecem no nvel 3. As chaves ocorrem em ordem crescente da esquerda para a direita se a rvore B for condensada em um nico nvel atravs da insero de descendentes entre as chaves da pgina de algum ancestral.

25

10 20

30 40

2 5 7 8

13 14 15 18

22 24

26 27 28

32 35 38

41 42 45 46

Figura 4.15 rvore B de ordem 2 Este arranjo representa uma extenso natural das rvores de busca binria, e determina o mtodo de busca de um elemento, dada a respectiva chave. Considere-se uma pgina de forma igual mostrada na Figura 4.16 e um dado argumento de busca x.

p0 k1 p1 k2 p2 .... pm-1 km pm

Figura 4.16 Uma pgina de rvore B com m chaves Admitindo-se que a pgina tenha sido movida para a memria principal, poder-se- utilizar mtodos convencionais de busca entre as chaves k1, ..., km. Se m for suficientemente grande, pode-se aplicar a busca binria. Para m pequeno, no entanto, uma busca seqencial ordinria pode ser utilizada. Note-se que o tempo exigido para a execuo de uma operao completa de busca na memria principal provavelmente desprezvel quando comparado ao tempo necessrio para se mover uma pgina da memria secundria para a principal. Se a busca no tiver sucesso, haver trs casos a considerar:

ki < x < ki+1, para 1 i < m. A busca deve continuar na pgina pi^; km < x. A busca deve continuar na pgina pm^; x < k1 . A busca deve continuar na pgina p0^;

Se, em algum caso, o apontador envolvido for nil, ou seja, no houver nenhuma pgina de descendentes, ento em toda a rvore no haver nenhum elemento com chave x, e a busca se encerra sem sucesso. Surpreendentemente, a operao de insero em uma rvore B , tambm, comparativamente simples. Se um elemento deve ser inserido em uma pgina com m < 2n elementos, o processo de insero dever continuar operando sobre esta pgina. Somente as inseres em pginas completamente lotadas que

87 causam alteraes na estrutura da rvore, podendo provocar a alocao de novas pginas. Para se compreender o que ocorre neste caso, ser considerada a Figura 4.17, que ilustra a insero da chave 22 em uma rvore B de ordem 2. Esta operao realizada nos seguintes passos:

Localiza-se, inicialmente, a pgina onde a chave 22 dever ser inserida. Sua insero na pgina C impossvel, uma vez que C j est lotada; A pgina C decomposta em duas pginas. Para isto, uma nova pgina, D, alocada; As 2n+1 chaves so distribudas igualmente entre as pginas C e D, e a chave do elemento central deslocada para um nvel superior, na pgina dos ancestrais A.

A 20

A 20 30

7 10 15 18 B

26 30 35 40 C

7 10 15 18 B

22 26 C

35 40 D

Figura 4.17 - Insero da chave 22 na rvore B Este esquema, bastante elegante, preserva todas as propriedades caractersticas das rvores B. Em particular, as pginas particionadas contm exatamente n elementos cada uma. Naturalmente, a insero de um elemento na pgina ancestral pode causar nova superlotao na pgina, provocando a propagao do particionamento. Em um caso extremo, o fenmeno poder propagar-se at atingir a raiz. De fato, esta a nica maneira de uma rvore B crescer em profundidade. A rvore B possui, portanto, um estranho modo de crescimento: ela cresce a partir das folhas, em direo raiz. Agora ser desenvolvido detalhadamente um algoritmo a partir destas especificaes preliminares. J fcil intuir que uma definio recursiva ser a mais conveniente, devido propriedade, apresentada pelo processo de particionamento, de se propagar para trs ao longo da trajetria de busca. Assim sendo, a estrutura geral do algoritmo dever ser similar da insero em rvores balanceadas, embora os detalhes sejam diferentes. Antes de mais nada, deve-se formular uma definio da estrutura das pginas. Os elementos sero representados na forma de vetores. tipo PPTR = ^PAGINA; INDICE = [0..2*N]; ITEM = registro KEY : inteiro; P : PPTR; COUNT : inteiro; fim registro; PAGINA = registro M : INDICE; P0 : PPTR; E : vetor [1..2*N] de ITEM; fim registro;

88 Novamente, o componente COUNT dos elementos da rvore incluir todos os tipo de informaes adicionais que possam estar associadas a cada elemento, porm sem participar de forma alguma do processo de busca propriamente dito. Note-se que, em cada pgina, h espao para alojar 2n elementos. O campo m indica o nmero efetivo de elementos existentes na pgina em cada instante. Como, exceto para a pgina que contm a raiz, vale a relao m n, ento pode-se garantir que, permanentemente, no mnimo 50% da rea de memria estejam de fato utilizados. O algoritmo de busca e insero em rvore B foi escrito na forma de um procedimento denominado search (busca). Sua estrutura principal trivial, e muito semelhante utilizada no programa de busca em rvore binria balanceada, com a exceo de que as decises tomadas durante o processamento no so meras escolhas binrias. Em vez disso, as buscas que so efetuadas internamente a uma dada pgina so implementadas na forma de uma busca binria no vetor "E" de elementos. O algoritmo de insero construdo na forma de um procedimento separado, apenas por questes de clareza. Este algoritmo ativado aps o algoritmo de busca ter detectado a necessidade de algum elemento ser mudado de nvel na rvore, em direo sua raiz. Esta condio indicada por meio de um parmetro booleano h, que assume um papel semelhante ao do algoritmo de insero em rvores balanceadas. Neste algoritmo, h indica que a subrvore cresceu. Se h for verdadeiro, um segundo parmetro u representar o elemento cujo nvel deve "subir" na rvore. O novo elemento imediatamente passado adiante, atravs do parmetro u, para a pgina terminal, com a finalidade de promover a insero propriamente dita. O esquema est esboado a seguir. procedimento SEARCH (X: inteiro; A: PPTR; var H: lgico; var U: ITEM); se (A = nil) ento {X no se encontra na rvore, inserir} Atribui o valor de X ao elemento U; Atribui TRUE a H, indicando que um elemento U "subir" na rvore; seno com (A^) faa busca binria de X no vetor E; se (ACHOU) ento processar dados; seno SEARCH (X, descendente, H, U); se (H) ento {um elemento "subiu" na rvore} se (nmero de elementos na pgina A^<2N) ento inserir U na pgina A^ e atribuir para H o valor TRUE; seno desmembra a pgina e faz o elemento central "subir" na rvore;

89 fim se; fim se; fim se; fim com; fim se; fim procedimento SEARCH; Se o parmetro h for verdadeiro aps a chamada da rotina SEARCH no programa principal, exigido um desmembramento da pgina que contm a raiz. Dado que tal pgina executa uma tarefa totalmente distinta da executada pelas demais, ento o seu tratamento dever ser programado em separado. Consiste apenas na alocao de uma nova pgina para a raiz e na insero do elemento nico, dado pelo parmetro u, nesta pgina. Como conseqncia, a nova pgina para raiz conter apenas um nico elemento. A Figura 4.18 mostra o resultado do algoritmo anterior para construir uma rvore B a partir da seguinte seqncia de insero de chaves: 20; 40 10 30 15; 35 7 26 18 22; 5; 42 13 46 27 8 32; 38 24 45 25; Os "ponto e vrgulas" indicam as posies instantneas, tomadas quando da alocao de cada pgina. A insero da ltima chave provoca dois desmembramentos, bem como a alocao de trs pginas adicionais.

(a)

20

20

(b)
10 15 30 40

20 30

(c)
7 10 15 18 22 26 35 40

10 20 30

(d)
5 7 15 18 22 26 35 40

90

10 20 30 40

(e)
5 7 8 13 15 18 22 26 27 32 35 42 46

25

(f)
10 20 30 40

5 7 8

13 15 18

22 24

26 27

32 35 38

42 45 46

Figura 4.18 Crescimento de uma rvore B de ordem 2 O comando com neste programa possui um significado especial. Em primeiro lugar, tal comando indica que os identificadores dos componentes da pgina se referem implicitamente pgina A^ em todos os comandos que compem o corpo do comando com associados. Se, de fato, as pginas residirem na memria secundria como certamente seria necessrio em grandes sistemas de banco de dados , ento o comando with poder, adicionalmente, incorporar em sua interpretao a necessidade de uma transferncia de pgina desejada para a memria principal. Dado que cada ativao da rotina SEARCH impe a transferncia de uma pgina para a memria principal, so necessrias, no mximo, k = logn N chamadas recursivas para uma rvore que contenha N elementos. Portanto, o sistema deve ser capaz de acomodar simultaneamente k pginas na memria principal. Isto um fator limitante para a extenso 2n das pginas. Na realidade, necessita-se acomodar mais de k pginas, porque a operao de insero pode causar novos desmembramentos de pgina. Uma conseqncia deste fato que se torna conveniente alocar permanentemente a pgina que contm a raiz na memria principal, porque cada operao de busca certamente percorre, sem exceo, a pgina que contm a raiz da rvore. Um outro mrito da organizao da rvore B sua adequao e economia nos casos de pura atualizao seqencial de todo o banco de dados. Cada pgina e, neste caso, copiadas para a memria principal exatamente uma nica vez. A remoo de elementos da rvore B bastante simples em essncia. Seus detalhes, porm, so complexos. possvel distinguir-se duas diferentes circunstncias: O elemento a ser removido uma folha da rvore, ou seja, est armazenado em sua pgina terminal. Neste caso, o correspondente procedimento de remoo bastante simples; O elemento no se encontra em uma pgina terminal, devendo ser substitudo por um dos dois elementos a ele adjacentes na rvore, e que se encontram em

91 uma pgina terminal, permitindo deste modo uma fcil remoo dos mesmos. No segundo caso, a localizao da chave adjacente anloga operao anteriormente utilizada para a remoo em rvores binrias. Desce-se a rvore ao longo do seu apontador mais direita, at encontrar a pgina terminal P, e substituindo-se o elemento a ser removido pelo elemento mais direita de P, e reduzindo-se ento de uma unidade o valor armazenado em P. De qualquer maneira, a reduo da dimenso deve ser imediatamente seguida de um teste do nmero de elementos m da pgina recm-reduzida, isto porque, se m < n, ento a principal caracterstica das rvores B seria violada. Alguma operao adicional ter de ser executada. Esta condio de underflow indicada pelo parmetro lgico h, passado por referncia. A nica sada consiste em emprestar ou anexar um elemento de uma das pginas vizinhas, por exemplo, de Q. Dado que isto envolve uma operao de cpia da pgina Q na memria principal, fica-se tentado a explorar ao mximo esta situao inconveniente e onerosa, executando-se a incorporao de mais de um elemento por vez. A estratgia usual distribuir eqitativamente entre as pginas P e Q os elementos nelas encontrados. Esta operao toma o nome de balanceamento de pgina. Naturalmente, poder ocorrer que no haja nenhum elemento a ser incorporado at que Q tenha atingido sua dimenso mnima n. Neste caso, o nmero total de elementos presentes nas pginas P e Q 2n 1; pode-se efetuar a fuso das duas pginas em uma nica, incorporando-se antes o elemento central da pgina que contm os ancestrais de P e Q, e, depois, descartar completamente a pgina Q. O funcionamento do mtodo pode ser visualizado considerando-se, por exemplo, a remoo da chave 22 na Figura 4.17. Novamente, a remoo da chave central da pgina que contm os ancestrais provoca a reduo das dimenses da pgina para um nvel abaixo do limite permitido n, exigindo que seja tomada outra atitude especial (quer balanceamento, quer fuso) no nvel seguinte. No caso extremo, a fuso de pgina pode at mesmo ser propagada por toda a extenso da trajetria, subindo em direo raiz da rvore. Se a dimenso da raiz for reduzida a zero, ela prpria ser removida, causando uma reduo na altura da rvore B. Esta , de fato, a nica situao em que uma rvore B logra reduzir-se em altura. A Figura 4.19 mostra a degenerao gradual da rvore B da Figura 4.18 como conseqncia da gradual remoo das chaves: 25 45 24; 38 32; 8 27 46 13 42; 5 22 18 26; 7 35 15; Novamente os ponto e vrgulas marcam as posies em que foram tomados os instantneos, e que corresponde s situaes em que ocorrem eliminaes de pginas. O algoritmo de remoo est includo no algoritmo seguinte na forma de um procedimento. A semelhana de suas estruturas em relao s utilizadas no algoritmo de remoo de rvores balanceadas particularmente notvel.

92

25

(a)
10 20 30 40

5 7 8

13 15 18

22 24

26 27

32 35 38

42 45 46

10 22 30 40

(b)
5 7 8 13 15 18 20 26 27 32 35 38 42 46

10 22 30

(c)
5 7 8 13 15 18 20 26 27 35 40 42 46

10 22

(d)
5 7 15 18 20 26 30 35 40

15

(e)
7 10 20 30 35 40

(f)

10 20 30 40

Figura 4.19 Degenerao de uma rvore B de ordem 2

const N = 2; tipo PPTR = ^PAGINA; INDICE = [0..2*N]; ITEM = registro KEY : inteiro; P : PPTR; COUNT : inteiro; fim registro; PAGINA = registro M : INDICE;

93 P0 : PPTR; E : vetor [1..2*N] de ITEM; fim registro;

procedimento SEARCH (X: integer; A: PPTR; var H: lgico; var V: ITEM); {busca a chave x na rvore B com raiz A; se for encontrada, incrementa o contador. Caso contrrio, inserir novo elemento com chave X. Se um elemento sobe na rvore, guardar em V o seu valor. H = a rvore ficou mais alta} var I, L, R : inteiro; B : PPTR; U : ITEM; se (A = nil) ento H TRUE; com (V) faa KEY X; COUNT 1; P nil; fim com; seno com (A^) faa L 1; R M + 1; {busca binria} enquanto (L < R) faa I (L + R) div 2; se (E[I].KEY <= X) ento L I + 1; seno R I; fim se; fim enquanto; R R 1; se ((R > 0) e (E[R].KEY = X)) ento INC(E[R].COUNT); seno {o elemento no se encontra nesta pgina} se (R = 0) ento SEARCH (X, P0, H, U); seno SEARCH (X, E[R].P, H, U);

94 fim se; se (H) ento {insere U no lado direito de E[R]} se (M < 2*N) ento H FALSE; M M + 1; para I M at (R+2) faa {decrescente} E[I] E[I-1]; fim para; E[R+1] U; seno aloque (B); {divide A em A, B e atribui ao} se (R <= N) ento {elemento central V} se (R = N) ento V U; seno V E[N]; para I N at (R+2) faa {decresc.} E[I] E[I-1]; fim para; E[R+1] U; fim se; para I 1 at N faa B^.E[I] A^.E[I+N]; fim para; seno {insere na pgina direita} R R N; V E[N+1]; para I 1 at (R-1) faa B^.E[I] A^.E[I+N+1]; fim para; B^.E[R] U; para I (R+1) at N faa B^.E[I] A^.E[I+N]; fim para; fim se; M N;

95 B^.M N; B^.P0 V.P; V.P B; fim se; fim se; fim se; fim com; fim se; fim procedimento SEARCH;

procedimento UNDERFLOW (C, A: PPTR; S : inteiro; var H: lgico); {A = pgina em que ocorreu overflow; C = ancestrais, S = ndice do elemento eliminado em C} var B: PPTR; I, K, MB, MC : inteiro;

pgina

dos

se (S < MC) ento S S + 1; B C^.E[S].P; MB B^.M; K (MB - N + 1) div 2; (num. elementos na pgina B} A^.E[N] C^.E[S]; A^.E[N].P B^.P0; se (K < 0) ento {transfere K elementos de B para A} para I 1 at (K-1) faa A^.E[I+N] B^.E[I]; fim para; C^.E[S] B^.E[K]; C^.E[S].P B; B^.P0 B^.E[K].P; MB MB - K; para I 1 at MB faa B^.E[I] B^.E[I+K]; fim para; B^.M MB; A^.M N-1+K; H FALSE;

96 seno {fuso das pginas A e B} para I 1 at N faa A^.E[I+N] B^.E[I]; fim para; para I S at (MC-1) faa C^.E[I] C^.E[I+1]; fim para; A^.M 2*N; C^.M MC-1; H MC<=N; desaloque (B); fim se; seno {B = se (S = B seno B fim se; pgina esquerda de A} 1) ento C^.P0; C^.E[S-1].P;

MB B^.M + 1; K (MB - N) div 2; se (K > 0) ento {transfere K elementos da pg. B p/ A} para I N-1 at 1 faa {contador decrescente} A^.E[I+K] A^.E[I]; fim para; A^.E[K] C^.E[S]; A^.E[K].P A^.P0; MB MB K; para I K-1 at 1 faa {contador decrescente} A^.E[I] B^.E[I+MB]; fim para; A^.P0 B^.E[MB].P; C^.E[S] B^.E[MB]; C^.E[S].P A; B^.M MB 1; A^.M N 1 + K; H FALSE; seno {fuso das pginas A e B}

97 B^.E[MB] C^.E[S]; B^.E[MB].P A^.P0; para I 1 at N-1 faa B^.E[I+MB] A^.E[I]; fim para; B^.M 2*N; C^.M MC 1; H MC<=N; desaloque (A); fim se; fim se; fim procedimento UNDERFLOW;

procedimento DELETE (X: inteiro; A: PPTR; var H: lgico); {busca e remove a chave X da rvore A; se ocorrer Underflow da pgina, faz-se o balanceamento ou fuso com a pgina adjacente.} var I, L, R : inteiro; Q : PPTR; procedimento DEL (P: PPTR; var H: lgico); var Q: PPTR; com (P^) faa Q E[M].P; se (Q <> nil) ento DEL(Q, H); se (H) ento UNDERFLOW(P, Q, M, H); fim se; seno P^.E[M].P A^.E[R].P; A^.E[R] P^.E[M]; M M 1; H M<N; fim se; fim com; fim procedimento DEL;

98

se (A = nil) ento {X no se encontra na rvore} H FALSE; seno com (A^) faa L 1; R M + 1; enquanto (L < R) faa I (L + R) div 2; se (E[I].KEY < X) ento L I + 1; seno R I; fim se; fim enquanto; se (R = 1) ento Q P0; seno Q E[R-1].P; fim se; se ((R <= M) e (E[R].KEY = X)) ento {o elemento foi encontrado e agora retirado} se (Q = nil) ento {A uma pgina terminal} M M 1; H M<N; para I R at M faa E[I] E[I+1]; fim para; seno DEL (Q, H); se (H) ento UNDERFLOW (A, Q, R-1, H); fim se; fim se; seno DELETE (X, Q, H); se (H) ento UNDERFLOW (A, Q, R-1, H); fim se; fim se;

99 fim com; fim se; fim procedimento DELETE; 4.6 OUTROS TIPOS DE RVORES E SUAS REPRESENTAES

100

UNIDADE 5 PESQUISA DE DADOS


5.1 MTODOS DE BUSCA As buscas esto entre as tarefas mais freqentes encontradas em programao de computadores. H diversas variaes bsicas sobre o assunto e uma grande variedade de algoritmos foram desenvolvidos com esta finalidade. A hiptese bsica no texto que segue que a coleo de dados, entre os quais um determinado elemento deve ser procurado, fixa. Admitiremos que este conjunto de N elementos seja representado atravs de um vetor, por exemplo como: var A: vetor [1..N] de ITEM; Tipicamente, o tipo item apresenta uma estrutura de registro, contendo um campo que atua como chave para a pesquisa. A tarefa consiste, ento, em se encontrar um elemento de A cujo campo de chave seja igual a um argumento de busca X fornecido. O ndice I resultante, que satisfaz condio A[i].CHAVE = X permite ento o acesso aos outros campos do elemento encontrado. Como o interesse bsico deste estudo exclusivamente o da busca, no sendo importantes os dados associados chave, admitiremos que o tipo ITEM seja composto apenas do campo de chave, ou seja, o dado a prpria chave. 5.1.1 BUSCA LINEAR Nos casos em que no se dispe de informaes adicionais sobre os dados a serem pesquisados, o procedimento mais bvio uma busca seqencial por todo o vetor, aumentando-se desta maneira passo a passo o tamanho da regio do vetor em que no figura o elemento desejado. Este procedimento chamado busca linear. Uma busca deste tipo termina quando for satisfeita uma das duas condies seguintes: O elemento encontrado, isto , AI = X; Todo o vetor foi analisado, mas o elemento no foi encontrado. Isto resulta no algoritmo abaixo: I 1; enquanto ((I <= N) e (A[I] <> X)) faa I I + 1; fim enquanto; Quando I for maior que N, isto significa que no foi encontrado o elemento. Evidentemente, o trmino das repeties garantido pois, em cada passo, I incrementado e, portanto, certamente alcanar o limite N aps um nmero finito de passos, caso no exista o elemento desejado no vetor.

101 Obviamente, cada passo requer o incremento do ndice e a avaliao de uma expresso booleana. O problema seguinte consiste em verificar se esta tarefa pode ser simplificada, e portanto, a busca ser acelerada? A nica possibilidade de se obter tal efeito consiste em se encontrar uma simplificao da expresso booleana, que depende certamente de dois fatores. Assim, preciso estabelecer uma condio baseada em um nico fator que implique nos dois fatores dos quais depende a expresso. Isto possvel desde que se possa garantir que tal elemento ser encontrado. Para tanto introduz-se um elemento adicional, com valor X, no final do vetor. A este elemento auxiliar d-se o nome de sentinela, porque ele evita que a busca avance o limite demarcado pelo ndice. O vetor A ser ento declarado da seguinte forma: var A: vetor [1..N+1] de ITEM; e o algoritmo de busca linear com sentinela torna-se A[N+1] X; I 1; enquanto (A[I] <> X) faa I I + 1; fim enquanto;

Evidentemente, I = N+1 implica que no foi encontrado o elemento desejado (exceto o correspondente sentinela). 5.1.2 BUSCA BINRIA obvio que no existem meios de acelerar uma busca sem que se disponha de maiores informaes acerca do elemento a ser localizado. Sabe-se tambm que uma busca pode ser mais eficiente se os dados estiverem ordenados. Suponha-se, por exemplo, uma lista telefnica em que os nomes no estejam listados em ordem alfabtica. Uma coisa dessas seria completamente intil. A seguir ser apresentado, com base neste raciocnio, um algoritmo que explora o ato do vetor estar ordenado. A idia principal a de testar um elemento sorteado aleatoriamente, por exemplo AM, e compar-lo com um argumento de busca X. Se tal elemento for igual a X, a busca termina, se for menor do que X, conclui-se que todos os elementos com ndices menores ou iguais a M podem ser eliminados dos prximos testes, e se for maior que X, todos aqueles que possuem ndices maior ou igual a M podem ser tambm eliminados. Isto resulta no algoritmo abaixo, denominado busca binria; ele utiliza duas variveis-ndices L e R, que marcam, respectivamente, os limites Esquerdo e Direito da regio de A em que um elemento ainda pode ser encontrado. L 1; R N; FOUND Falso;

102

enquanto ((L <= R) e no(FOUND)) faa M (L + R) div 2; se (A[M] = X) ento FOUND Verdadeiro; seno se (A[M] < X) ento L M + 1; seno R M 1; fim se; fim se; fim enquanto; Embora a escolha de M possa ser arbitrria no sentido de que o algoritmo funciona independentemente dele, o valor desta varivel influencia na eficincia do algoritmo. Torna-se absolutamente claro que a meta eliminar, em cada passo, o maior nmero possvel de elementos em futuras buscas, sem levar em considerao o resultado da comparao. A soluo tima escolher a mediana dos elementos, porque esta escolha elimina, em qualquer caso, metade dos elementos do vetor. Como conseqncia, o nmero mximo de passos ser log2 N, arredondado para cima at o nmero inteiro mais prximo. Com isso, este algoritmo permite uma drstica melhora do desempenho em relao ao mtodo de busca linear, onde o nmero esperado de comparaes N/2. 5.2 PROCESSAMENTO EM CADEIAS 5.2.1 INTRODUO Entende-se por cadeia uma seqncia qualquer de elementos, denominados caracteres. Os caracteres, por sua vez, so elementos escolhidos de um conjunto denominado alfabeto. Por exemplo, 0110010 uma cadeia com alfabeto {0, 1}. As cadeias aparecem, em computao, no processamento de textos, palavras, mensagens, cdigos, etc. com aplicaes to diversas quanto o tratamento computacional de dicionrios, mensagens de correio eletrnico, seqenciamento de DNAs em biologia computacional, criptografia de textos confidenciais e muitos outros. De fato, a importncia do processamento de cadeias na computao vem crescendo consideravelmente, at mesmo para computadores de uso pessoal, nos quais a edio de textos tem sido a principal aplicao. Nesta seo ser abordado o problema do casamento de cadeias. Dadas duas cadeias de caracteres, o problema consiste em verificar se a primeira delas contm a segunda, isto , se os caracteres que compem a segunda cadeia aparecem seqencialmente tambm na primeira. 5.2.2 O PROBLEMA DO CASAMENTO DE CADEIAS

103 J foi mencionado que uma cadeia uma seqncia de caracteres escolhidos de um conjunto chamado alfabeto. O nmero de caracteres da cadeia o seu comprimento. Sejam X, Y duas cadeias de caracteres com comprimentos n, m, respectivamente, n m. Supe-se que X e Y sejam representadas por vetores com elementos xi e yj, 1 i n e 1 j m. Y uma subcadeia de X quando Y for uma subseqncia de elementos consecutivos de X, isto , quando existir algum inteiro L n m, tal que xL+j = yj, 1 j m. O problema do casamento de cadeias consiste em verificar se Y subcadeia de X. Em caso positivo, localizar Y em X. Diz-se, ento, que houve um casamento de Y com X na posio L + 1. Por exemplo, se X a cadeia baaabea e Y aabe, ento Y subcadeia de X e a soluo do problema do casamento de cadeias deve indicar que Y aparece em X a partir do terceiro caractere, como indica a Figura 5.8. Por outro lado, se Y formada pela seqncia aeb, ento no subcadeia de X.

b L

FIGURA 5.8 O problema do casamento de cadeias 5.2.3 O ALGORITMO DA FORA BRUTA Um mtodo bastante simples para se verificar se Y subcadeia de X consiste em examinar todas as possveis situaes. Se Y for subcadeia de X, ento Y se encontra em X, deslocado de L posies esquerda. A idia consiste em comparar Y com a subcadeia de tamanho m de X, que se inicia no caractere xL+1, para todos os valores possveis de L. Ou seja, L = 0, 1, 2, ..., n m. A Figura 5.9 ilustra a aplicao do algoritmo da fora bruta para as mesma cadeias X e Y da Figura 5.8. O algoritmo, de incio, compararia a subcadeia X1 = baaa, iniciada em x1, com Y; em seguida, de incio, a subcadeia X2 = aaab, iniciada em x2, com Y. Nessas duas situaes, o teste seria negativo. Na iterao seguinte, em que a subcadeia X3 = aabe considerada, verifica-se o casamento.

padro b

no a

no a

sim

104

Figura 5.9 O algoritmo da fora bruta O algoritmo a seguir descreve o processo. A varivel L indica o nmero de caracteres, na cadeia X, que antecedem cada subcadeia considerada. A varivel lgica TESTE utilizada para informar a ocorrncia ou no de casamento. Para tornar o mtodo mais eficiente, utilizou-se o princpio de abandonar a verificao de uma subcadeia no meio do processo, se for detectado que um casamento no possvel. Por exemplo, se o i-simo caractere da subcadeia considerada no coincide com o i-simo caractere de Y, no h necessidade de comparar os caracteres de ordem i + 1, i + 2, ..., m. funo CASAMENTO (X, Y : caractere): inteiro; {retorna a posio onde ocorre o casamento e 0 caso no ocorra casamento de cadeias de caracteres} var L, N, M, I : inteiro; TESTE : lgico; N COMPRIMENTO(X); {COMPRIMENTO funo que retorna o} M COMPRIMENTO(Y); (comprimento da varivel parmetro} para L 0 at (N M) faa I 1; TESTE Verdadeiro; enquanto ((I <= M) e (TESTE)) faa se (X[L + I] = Y[I]) ento I I + 1; seno TESTE Falso; fim se; fim enquanto; se (TESTE) ento CASAMENTO L + 1; exit; fim se; fim para; CASAMENTO 0; fim funo CASAMENTO; 5.2.4 O ALGORITMO DE KNUTH, MORRIS E PRATT

105 Por volta de 1970, D. E. Knuth, J. H. Morris e V. R. Pratt inventaram um algoritmo que exige, essencialmente, cerca de N comparaes de caracteres, mesmo nos piores casos. O novo algoritmo baseado no fato de que, no incio de cada comparao de um novo padro, informaes valiosas podem estar sendo descartadas e, talvez, algumas destas informaes podero ser utilizadas para se agilizar a busca no texto da cadeia. O exemplo a seguir auxiliar na compreenso do algoritmo. Dada uma cadeia X = BABABAEABAEABAEAABAEABAEAAEBA, desejamos saber se a subcadeia Y = ABAEABAEAAE est contida na cadeia X. A primeira etapa do algoritmo de Knuth, Morris e Pratt consiste em descobrir padres de comportamento na subcadeia Y. Estes padres de comportamento podero ser armazenados num vetor e depois utilizados para agilizar a busca. A Figura 5.10 mostra o processo de obteno deste vetor. O algoritmo que analisa a subcadeia Y armazenando os padres de comportamento num vetor apresentado a seguir. const TAM = 11; {constante do tamanho da subcadeia Y} tipo VET_PADROES = vetor [1..TAM] de inteiro; procedimento FIND_PADROES (Y : caractere; var PADROES: VET_PADROES); var I, J, K, M : inteiro; para I 1 at TAM faa PADROES[I] 0; fim para; J 0; K 1; M COMPRIMENTO(Y); {ou M TAM} enquanto (K < M) faa se (Y[K + 1] = Y[J + 1]) ento K K + 1; J J + 1; PADROES[K] J; seno se (J = 0) ento K K + 1; PADROES[K] 0; seno J PADROES[J]; fim se; fim se; fim enquanto; fim procedimento FIND_PADROES;

106

A -

B -

E PADROES[1] = 0 PADROES[2] = 0 PADROES[3] = 1 PADROES[4] = 0 PADROES[5] = 1 PADROES[6] = 2 PADROES[7] = 3 PADROES[8] = 4 PADROES[9] = 5 PADROES[10] = 1 PADROES[11] = 0

A -

A A A A A -

B B B B -

A A A -

E E -

A -

A -

Figura 5.10 Os valores de PADROES[K] A segunda etapa do algoritmo consiste em utilizar-se do vetor PADROES para efetuar a busca da subcadeia propriamente dita. Neste processo nem todas as subcadeias de X com comprimento igual subcadeia Y necessitam ser comparadas com Y. A Figura 5.11 mostra as comparaes efetuadas pelo algoritmo de Knuth, Morris e Pratt na busca da subacadeia Y em X. O algoritmo utilizado para verificar o casamento entre as cadeias X e Y apresentado a seguir. funo CASAMENTO_KMP (X, Y : caractere): inteiro; {retorna a posio onde ocorre o casamento e 0 caso no ocorra casamento de cadeias de caracteres} var PADROES N, M, I, J TESTE : VET_PADROES; : inteiro; : lgico;

N COMPRIMENTO(X); M COMPRIMENTO(Y); I 0; J 0; FIND_PADROES(Y, PADROES); enquanto ((I - J) <= (N - M)) faa TESTE Verdadeiro; enquanto ((J < M) e (TESTE)) faa se (X[I + 1] = Y[J + 1]) ento I I + 1;

107 J J + 1; seno TESTE Falso; fim se; fim enquanto; se (TESTE) ento CASAMENTO_KMP I - M + 1; exit; fim se; se (J = 0) ento I I + 1; seno J PADROES[J]; fim se; fim enquanto; CASAMENTO_KMP 0; fim funo CASAMENTO_KMP;

A B A E A B A E A A E

X B A B A B A E A B A E A B A E A A B A E A B A E A A E B A L=0 A L=1 A B A E L=3 B A E A B A E A A L=7 B A E A A E L = 16 B A E A B A E A A E

Figura 5.11 Comparaes efetuadas pelo algoritmo de Knuth, Morris e Pratt 5.3 ESPALHAMENTOS Esta seo apresenta uma eficiente tcnica empregada para agilizar o processo de pesquisa de informaes, denominada espalhamento ou "hashing". Veremos que uma das principais caractersticas desta tcnica de armazenamento e recuperao de informaes que ela no requer ordenao, o que torna a insero to rpida quanto o acesso!

108 5.3.1 FUNDAMENTOS Seja P o conjunto que contm todos os elementos de um determinado universo, possivelmente infinito. Chamamos espalhamento ou "hashing" ao particionamento de P em um nmero finito de classes P1, P2, P3, ..., Pn, com n >1, tal que: 1.

P
i =1

=P

Isto , a unio de todas as n classes deve resultar no prprio conjunto P; significando que para todo elemento k P deve existir uma classe Pi, 1 i n, tal que k Pi. 2.

P
i =1

Isto , a interseco de todas as n classes deve resultar em um conjunto vazio, significando que no existem k P e 1 i < j n, tal que k Pi e k Pj. Em outras palavras, um mesmo elemento no pode pertencer a mais de uma classe ao mesmo tempo. A correspondncia unvoca entre os elementos do conjunto P e as n classes sugere a existncia de uma funo h, atravs da qual feito o particionamento (Figura 5.12) A funo h: P [1 .. n], que leva cada elemento de P sua respectiva classe, chamada funo de espalhamento ou funo hashing. Sendo k um elemento qualquer do conjunto P, o seu valor de hashing, h (k), determina a que classe ele pertence. Pode-se garantir que:

k Ph (k )
h
P = { e1, e2, e3, e4, e5, e6, e7, e8, ... } Figura 5.12 - O espalhamento realizado pela funo h A cardinalidade de um conjunto C, representada por |C|, equivale ao Pn = {e5, e91} P3 = {e2, e9, e26} P2 = {e7} P1 = {e1, e3, e4}

109 nmero de elementos existentes em C. Se, num certo espalhamento, a diferena absoluta entre as cardinalidades de quaisquer duas classes for no mximo 1, dizemos que a funo de espalhamento utilizada tima. Funo tima ABS(|Pi| - |Pj|) 1; para 1 i < j n Por exemplo, sejam o conjunto P = {a, b, c, d, e, f, g, h} e a funo h: P [1 .. 3], que " espalha" os elementos de P entre trs classes distintas e que fornece os seguintes valores de hashing:

h (a) = 2; h (b) = 1; h (c) = 3; h (d) = 3; h (e) = 1; h (f) = 2; h (g) = 3; h (h) = 1;

De acordo com os valores de hashing obtidos para os elementos, temos as classes P1 = {b, e, h}, P2 = {a, f} e P3 = {c, d, g}, cujas cardinalidades so, respectivamente, |P1| = 3, |P2| = 2 e |P3| = 3. Pode-se dizer que a funo h empregada neste espalhamento, tima pois todas as classes tm aproximadamente a mesma quantidade de elementos, ou seja, o espalhamento foi uniforme. Quando a funo utilizada no espalhamento de um conjunto P tima, a menor classe criada tem no mnimo (|P| div n) elementos e a maior, no mximo (|P| div n) + 1. Considerando o nosso exemplo, onde |P| = 8 e n = 3, podemos constatar que a menor cardinalidade obtida foi |P2| = (8 div 3) = 2, enquanto a maior foi |P1| = |P3| = (8 div 3) + 1 = 3. Suponha agora um espalhamento no qual uma funo P tima utilizada para particionar um conjunto P em |P| classes distintas. Naturalmente, existindo |P| classes, teremos tantas classes quantos forem os elementos de P. Aliando isto ao fato de que o espalhamento foi realizado por uma funo tima (o espalhamento uniforme), evidente que cada classe ter um nico elemento de P. Dizemos que o espalhamento assim obtido perfeito. Se um espalhamento no perfeito, existe pelo menos uma classe com mais de um elemento, isto , existem x P e y P, tal que x y mas h(x) = h(y); neste caso, dizemos que x e y so sinnimos. 5.3.2 APLICABILIDADE DO ESPALHAMENTO Em termos prticos, a grande vantagem do espalhamento est no fato de que, dado um elemento k de um conjunto P, o valor de hashing h(k) pode ser calculado em tempo constante, fornecendo imediatamente a classe da partio de P em que o elemento se encontra. Se considerarmos P uma coleo de elementos a

110 ser pesquisada, fcil perceber que o processo ser muito mais eficiente se a pesquisa for restrita a uma pequena parte do conjunto P (uma nica classe). Suponha o caso de procurar o nome "Maria" em um conjunto de nomes prprios:

Eduardo Maria Jos k = "Maria" Denis Sandra Fbio Paula Figura 5.13 - Pesquisa sem espalhamento Regina Beatriz

Ana Itivaldo

P1 Denis Sandra k = "Maria" h(k) = 2 Ana Eduardo Maria Paula Beatriz Jos P2 Itivaldo P3 Fbio Regina

Figura 5.14 - Pesquisa com espalhamento O espalhamento pode ser usado como uma tcnica de reduo do espao de busca; o processo de pesquisa ser to mais eficiente quanto menores forem as parties. Se o espalhamento for perfeito, ento o valor de hashing calculado dar imediatamente a localizao do elemento desejado; neste caso, temos um acesso direto ou randmico, como tambm chamado, sendo este o tipo de busca mais eficiente de que dispomos.

Beatriz Denis Jos Sandra Ana Eduardo k = "Maria" h(k) = 7 Maria Itivaldo

P1 P2 P3 P4 P5 P6 P7 P8

111

Figura 5.15 - Acesso direto ou randmico atravs de hashing 5.3.3 TABELAS DE ESPALHAMENTO Tabela de espalhamento a estrutura de dados que implementa o espalhamento para aplicaes em computador. Ela pode ser representada por um vetor onde cada posio, denominada encaixe, mantm uma classe de partio. O nmero de encaixes na tabela deve coincidir com o nmero de classes criadas pela funo de espalhamento. Considerando que um conjunto P seja espalhado em P1, P2, P3, ..., Pn classes distintas, um vetor T[1 .. n] pode representar o espalhamento de maneira satisfatria, bastando associar a cada elemento T[i] uma classe Pi, 1 i n correspondente. Admitindo a existncia de elementos sinnimos em P, de se esperar que durante o espalhamento ocorra pelo menos uma coliso, ou seja, possvel que tenhamos que armazenar um elemento numa posio da tabela que j se encontre ocupada por um outro valor. Existem diversas formas de se resolver este problema, entretanto, a mais comumente utilizada chamada tratamento de coliso por espalhamento externo. O espalhamento externo parte do princpio que existiro muitas colises durante o carregamento das chaves numa tabela e que, na verdade, cada encaixe T[i] armazenar no um nico elemento, mas uma coleo de elementos sinnimos. Como a quantidade de elementos sinnimos em cada classe pode variar bastante, a lista encadeada ser bastante til. No espalhamento aberto, a tabela de hashing ser um vetor cujos elementos so ponteiros para listas encadeadas que representam as classes de espalhamento. const N = tipo ELEM tipo CLAS NODO 5; = caractere; = ^NODO; = registro CHAVE : ELEM; PROX : CLAS; fim registro;

tipo TABHSH = vetor [1..N] de CLAS; var T : TABHSH;

112

1 2 3 4 5 ... N

Figura 5.16 - Uma tabela de espalhamento ou hashing O espalhamento denominado externo porque as chaves so armazenadas "fora" da tabela de hashing, numa rea de memria alocada dinamicamene. 5.3.4 FUNES DE ESPALHAMENTO Espalhamento pode ser visto como um mtodo de busca que permite acessar dados diretamente, atravs de uma funo que transforma uma chave k em um endereo fsico, relativo ou absoluto, h(k). Uma funo de espalhamento ideal seria aquela capaz de mapear n chaves em exatamente n endereos, sem a ocorrncia de colises. Existem n! formas de se obter este mapeamento ideal, o que poderia nos levar a crer que tais funes ideais so facilmente encontradas. Considerando-se, entretanto, que existem nn formas possveis de atribuir n chaves a n endereos, a probabilidade de se obter um espalhamento perfeito mnima (n! / nn). Na prtica, devemos nos contentar com funes capazes de fazer um mapeamento razovel, distribuindo-se as n chaves de forma mais ou menos uniforme entre m endereos, tal que m < n (sempre teremos colises). Muito esforo tem sido dedicado em busca de funes eficientes, que transformem rapidamente uma chave num endereo e que possibilitem uma distribuio uniforme das chaves dentro de um determinado espao de armazenamento (encaixes da tabela de hashing), sendo que nenhum dos mtodos propostos garantem a eficincia mxima desejada. Felizmente, porm, um dos mais simples mtodos existentes, o mtodo da diviso, tem mostrado na prtica um desempenho bastante satisfatrio, sendo recomendado como o melhor mtodo para uso geral. 5.3.5 O MTODO DA DIVISO O mtodo da diviso consiste basicamente em realizar uma diviso inteira e tomar o seu resto. Para entendermos melhor como ele funciona, vamos utiliz-lo para espalhar as chaves 54, 21, 15, 46, 7, 33, 78, 9, 14, 62, 95 e 87 numa tabela

113 contendo N = 5 encaixes. A funo definida a seguir, que transforma as chaves em endereos relativos, est baseada no mtodo da diviso: funo DH (CHV: inteiro) : inteiro; DH (CHV mod N) + 1; fim funo DH; evidente que numa diviso inteira por N no se pode obter resto maior nem igual a N; logo, o operador mod resultar sempre em valores no intervalo de 0 a N - 1. Como a tabela oferece N encaixes numerados de 1 a N, devemos adicionar uma unidade ao resto calculado, de modo a obtermos valores na faixa de 1 a N. Veja a seguir, os valores de hashing gerados pela funo:

DH(54) = (54 mod 5) + 1 = 5; DH(15) = (15 mod 5) + 1 = 1; DH(7) = (7 mod 5) + 1 = 3; DH(78) = (78 mod 5) + 1 = 4; DH(14) = (14 mod 5) + 1 = 5; DH(95) = (95 mod 5) + 1 = 1; 1 2 3 4 5 15 21 7 33 9

DH(21) = (21 mod 5) + 1 = 2; DH(46) = (46 mod 5) + 1 = 2; DH(33) = (33 mod 5) + 1 = 4; DH(9) = (9 mod 5) + 1 = 5; DH(62) = (62 mod 5) + 1 = 3; DH(87) = (87 mod 5) + 1 = 3; 95 46 62 78 14 54 87

Figura 5.17 - Espalhamento de chaves numricas 5.3.6 TRANSFORMAO DE CHAVES ALFANUMRICAS O mtodo da diviso no pode ser usado diretamente quando as chaves so alfanumricas; neste caso, temos antes que transform-las em valores numricos que podero ser divididos por N. Uma forma simples de se transformar uma chave alfanumrica num valor numrico consiste em considerar cada caractere da chave como um valor inteiro (correspondente ao seu cdigo ASCII) e realizar uma soma com todos eles: funo ADH (CHV: caractere): inteiro; var I, SOMA : inteiro; SOMA 0; para I 1 at COMPRIMENTO(CHV) faa SOMA SOMA + ORD(CHV[I]); {ORD retorna o cdigo ASCII} fim para; {do parmetro}

114

ADH (SOMA mod N) + 1; fim funo ADH; Vamos supor agora que pretendemos espalhar em uma tabela com N = 7 encaixes as chaves Thais, Edu, Bia, Neusa, Lucy, Rose, Yara, Decio e Sueli. Calculando a funo ADH( ) para cada uma das chaves, obtemos os valores relacionados a seguir e a tabela ilustrada na Figura 5.18. Observe que se no fossem as chaves Decio e Sueli, o espalhamento seria perfeito.

ADH(Thais) = 2; ADH(Bia) = 3; ADH(Lucy) = 1; ADH(Yara) = 6; ADH(Sueli) = 4. 1 2 3 4 5 6 7

ADH(Edu) = 7; ADH(Neusa) = 5; ADH(Rose) = 4; ADH(Decio) = 2;

Lucy Thais Bia Rose Neusa Yara Edu Sueli Decio

Figura 5.18 Espalhamento de chaves alfanumricas Considere agora um conjunto de chaves alfanumricas onde cada uma delas apenas uma permutao dos mesmos caracteres bsicos; por exemplo: ABC, ACB, BAC, BCA, CAB e CBA. Neste caso, se estas chaves forem espalhadas pela funo ADH( ), numa tabela com N = 7 encaixes, teremos:

ADH(ABC) = 3; ADH(BAC) = 3; ADH(CAB) = 3;

ADH(ACB) = 3; ADH(BCA) = 3; ADH(CBA) = 3.

Claramente, se as chaves so todas compostas pelos mesmos caracteres, e a transformao da chave baseada na soma dos seus caracteres, ento todas as chaves sero mapeadas para a mesma classe (no h espalhamento). No poderamos ter uma situao mais indesejvel! Isto acontece porque a transformao por simples somatrio no leva em considerao a posio em que os caracteres aparecem na chave. Para resolver este problema, vamos usar um algoritmo bastante eficiente para a transformao de chaves alfanumricas denominado somatrio com deslocamentos. Este algoritmo associa a cada caractere da chave uma quantidade de bits que dever ser deslocada esquerda no seu cdigo ASCII, antes de ele ser

115 adicionado soma total. As quantidades a serem deslocadas esquerda variam de 0 a 7, de forma cclica, conforme esquematizado na Figura 5.19 Posio Chave Deslocamento 0 1 2 3 4 5 6 7 0 1 2 ...
1 2 3 4 5 6 7 8 9 10 11 ...

Figura 5.19 Quantidade de bits a ser deslocada para cada posio da chave No somatrio simples, qualquer que seja a posio ocupada por um caractere X dentro da chave, ele sempre transformado no nmero ORD(X). No somatrio com deslocamento, entretanto, o valor numrico associado a X depender no somente do seu cdigo, mas tambm da posio que ele ocupa dentro da chave. Seu valor ser: ORD(X) deslocado esquerda de [(posio 1) mod 8] bits Para entender melhor como este algoritmo funciona, vamos tomar como exemplo, o cdigo ASCII da letra A (65) na sua representao binria: ORD(A) = (0 1 0 0 0 0 0 1)B Se a letra A aparecer na primeira posio da chave, ento o seu valor numrico ser o seu prprio cdigo ASCII, pois, para a primeira posio, o deslocamento 0 e nada ser alterado. Porm, se ela aparecer na segunda posio, seu cdigo dever ser deslocado de 1 bit esquerda; se aparecer na terceira, 2 bits; na quarta, 3 e assim sucessivamente conforme especificado na Figura 5.19.

1a posio: (0 2a posio: (0 3a posio: (0 4a posio: (0 5a posio: (0 6a posio: (0 7a posio: (0 8a posio: (0 9a posio: (0

1 1 1 1 1 1 1 1 1

0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0

1)B 1)B 1)B 1)B 1)B 1)B 1)B 1)B 1)B

shl shl shl shl shl shl shl shl shl

0 = (0 1 = (1 2 = (0 3 = (0 4 = (0 5 = (0 6 = (0 7 = (1 0 = (0

1 0 0 0 0 0 1 0 1

0 0 0 0 0 1 0 0 0

0 0 0 0 1 0 0 0 0

0 0 0 1 0 0 0 0 0

0 0 1 0 0 0 0 0 0

0 1 0 0 0 0 0 0 0

1)B = 65; 0)B = -126; 0)B = 4; 0)B = 8; 0)B = 16; 0)B = 32; 0)B = 64; 0)B = -128; 1)B = 65;

Em Pascal, utilizam-se os operadores shl (shift to left) e shr (shift to right) para se deslocar bits de um valor inteiro, respectivamente, esquerda e direita. Observe que o 8o bit (da direita para a esquerda) usado como bit de sinal, sendo que se este bit est ligado, o byte representa um valor negativo em complemento de 2. A funo de hashing para chaves alfanumricas com permutao usa o somatrio com deslocamentos e o mtodo da diviso, em conjunto: funo SDH (CHV: caractere): inteiro; var

116 P, SOMA : inteiro; SOMA 0; para P 1 at COMPRIMENTO(CHV) faa SOMA SOMA + ORD(CHV[P]) shl ((P 1) mod 8); fim para; SDH (ABS(SOMA) mod N) + 1; fim funo SDH; Agora sim as chaves com permutao podem ser espalhadas numa tabela com 7 encaixes:

SDH(ABC) = 6; SDH(BAC) = 7; SDH(CAB) = 7;

SDH(ACB) = 2; SDH(BCA) = 6; SDH(CBA) = 5.

Embora o somatrio com deslocamentos aparea como uma forma de resolver o problema de chaves com permutao, podemos empreg-lo sempre que tivermos que manipular chaves alfanumricas; ainda que elas no sejam permutaes do mesmo conjunto de caracteres. Compare os resultados obtidos no espalhamento (N = 7) a seguir: Chave Thais Denise Carlos Fabio Paula Silvio Roberto Luis Denis Yara
4 3 2 1 0 1 2 3 4 5 6 7 ADH( ) SDH( )

ADH(chave) 2 6 4 6 3 1 6 1 3 6

SDH(chave) 1 1 3 4 2 7 5 6 3 4

117 Figura 5.20 Comparao entre os algoritmos ADH( ) e SDH( ) 5.3.7 OPERAES BSICAS SOBRE TABELA DE ESPALHAMENTO Dada uma tabela de espalhamento T, a operao HINIT( ) inicia a tabela no estado vazio. Como o espalhamento externo, isto , a tabela armazena apenas ponteiros para listas encadeadas que contm chaves sinnimas, a operao de inicializao simplesmente anula todos os N ponteiros (encaixes) existentes na tabela: procedimento HINIT (var T: TABHSH); var I : inteiro; para I 1 at N faa T[I] nil; fim para; fim procedimento HINIT; A operao HINS(T, K) insere a chave K no h(K)-simo encaixe da tabela T. Admitindo-se que a operao INS( ), que realiza a insero em lista ordenada, se encontra disponvel, a operao de insero numa tabela de espalhamento pode ser codificada como a seguir: procedimento HINS (var T: TABHSH; K: ELEM); INS(T[H(K)], K); fim procedimento HINS; Note que, como os elementos do vetor T so ponteiros, a expresso T[H(K)] resulta num ponteiro para a lista ordenada onde a chave K deve ser inserida. A insero propriamente dita realizada pela rotina INS( ). Analogamente, as operaes HREM(T, K) e HFND(T, K), respectivamente empregadas para remoo e pesquisa, podem ser codificadas com base nas operaes de remoo e busca em listas ordenadas. Lembre-se de que, sendo L uma lista ordenada, REM(L, K) exclui a chave K da lista L e FIND(L, K) retorna um ponteiro para o nodo que contm a chave K, ou nil se a chave no for encontrada em L. procedimento HREM (var T: TABHSH; K: ELEM); REM(T[h(K)], K); fim procedimento HREM;

funo HFND (var T: TABHSH; K: ELEM): lgico;

118

HFND (FND(T[h(K)], K) <> nil); fim procedimento HFND; Vale lembrar que qualquer estrutura de dados capaz de armazenar colees de dados pode ser usada para resolver as colises que ocorrem durante o processo de carga da tabela de hashing, assim, rvores binrias poderiam ter sido escolhidas ao invs das listas ordenadas.