Prévia do material em texto
ESTRUTURAS
DE
DADOS
ROGÉRIO FERREIRA SGOTI
FACULDADE DE TECNOLOGIA DE BOTUCATU
SUMÁRIO
Apresentação da disciplina .................................................................................................. ..... 3
1 Introdução ao estudo das estruturas de dados ..................................................................... 4
2 Revisão de tópicos ........................................................................................................ ........ 4
3 Registros ............................................................................................................................... 6
4 Algoritmos de Ordenação ................................................................................................... ... 9
5 Algoritmos de Pesquisa ......................................................................................................... 13
6 Comparativo entre os algoritmos de pesquisa ...................................................................... 16
7 Listas ............................................................................................................................. ........ 18
8 Alocação Estática de Memória ...................................................................................... ........ 18
8.1 Lista Estática Sequencial ............................................................................................... . 18
8.2 Lista Estática Encadeada ............................................................................. ................... 22
9 Alocação Dinâmica de Memória ............................................................................................ 28
9.1 Ponteiros ......................................................................................................................... 28
9.2 Listas Dinâmicas ........................................................................................................ ..... 29
10 Pilhas ................................................................................................................................... 38
10.1 Operações sobre Pilhas ................................................................................................ 38
10.2 Aplicações de Pilhas ..................................................................................................... 39
10.3 Implementação de Pilhas .............................................................................................. 39
10.4 Alocação Sequencial de Pilhas ..................................................................................... 39
10.5 Alocação Dinâmica de Pilhas ........................................................................................ 42
11 Filas ..................................................................................................................................... 44
11.1 Operações sobre Filas .................................................................................................. 44
11.2 Aplicações de Filas ........................................................................................................ 44
11.3 Implementação de Filas ................................................................................................ 44
11.4 Alocação Sequencial de Filas ....................................................................................... 45
11.5 Fila Circular .......................................................................................................... ......... 48
11.6 Alocação Dinâmica de Filas .......................................................................................... 50
12 Espalhamento (Hashing) .............................................................................................. ....... 52
12.1 Fundamentos ............................................................................................................ ..... 52
12.2 Funções de Espalhamento ............................................................................................ 53
12.3 Tabelas de Espalhamento ............................................................................................. 55
13 Recursividade ...................................................................................... ................................ 57
13.1 Implementação de Recursão ......................................................................................... 57
13.2 Recursão Degenerada .................................................................................................. 60
13.3 Eliminação de Recursão ................................................................................................ 62
14 Árvores ................................................................................................................................ 64
14.1 Fundamentos ............................................................................................................ ..... 64
14.2 Árvores Binárias ............................................................................................................ 65
14.3 Percursos em Árvores Binárias ..................................................................................... 66
14.4 Algoritmos dos Percursos .............................................................................................. 68
14.5 Árvores de Busca Binária .............................................................................................. 70
14.6 Implementação de Árvores de Busca Binária ............................................................... 71
Bibliografia ................................................................................................................ ................ 74
ESTRUTURAS DE DADOS
Instituição: Faculdade de Tecnologia de Botucatu
Curso: Análise e Desenvolvimento de Sistemas
Carga Horária: 04 h/a semanais – Total: 80 horas
Professor Responsável: Rogério Ferreira Sgoti
CONTEÚDO PROGRAMÁTICO
Semana Tópicos
1ª
Apresentação da Disciplina: Objetivos, Ementa, Conteúdo
Programático, Critério de Avaliação, Bibliografia. Revisão de Algoritmos:
procedimentos, funções e passagens de parâmetros.
2ª Registros. Aplicações, Sintaxes e Exemplos. Exercícios.
3ª Manipulação de registros. Exercícios práticos.
4ª
Ordenação ou Classificação de dados. Conceitos. Algoritmos clássicos
de ordenação. Simulação desses algoritmos.
5ª
Pesquisa ou Busca de dados. Conceitos. Algoritmos de Pesquisa
Sequencial e Pesquisa Binária.
6ª
Cálculos de desempenho para algoritmos de pesquisa. Exemplos.
Lista de Exercícios. Correção dos Exercícios.
7ª
Introdução às estruturas do tipo Lista. Conceitos e Aplicações. Tipos de
Listas. Lista Estática Sequencial. Operações e Algoritmos. Simulação.
8ª Lista Estática Encadeada. Operações e Algoritmos. Simulação.
9ª Listas de Exercícios: listas estáticas – sequencial e encadeada.
10ª Atendimento às dúvidas. 1ª Avaliação de Estruturas de Dados.
11ª
Alocação Estática X Alocação Dinâmica de Memória. Listas Dinâmicas.
Conceitos e Aplicações. Exemplos.
12ª
Ponteiros. Conceitos. Notações. Exemplos. Operações e Algoritmos.
Simulação.
13ª
Pilhas. Conceitos e Aplicações. Operações sobre Pilhas. Exemplos.
Algoritmos. Simulação.
14ª
Filas. Conceitos e Aplicações. Operações sobre Filas. Exemplos.
Algoritmos. Simulação.
15ª
Espalhamento (ou hashing). Conceitos e Aplicações. Exemplos.
Tabelas de Espalhamento.
16ª
Recursividade. Conceitos e Aplicações. Vantagens e desvantagens do
uso de recursividade. Exemplos.
17ª
Árvores. Conceitos e Aplicações. Operações sobre Árvores. Algoritmos.
Exercícios. Simulação dos exercícios.
18ª Atendimento às dúvidas. 2ª Avaliação de Estruturas de Dados.
19ª Entrega de notase atendimento às dúvidas dos alunos.
20ª Avaliação P3 de Estruturas de Dados e encerramento da disciplina.
1 – INTRODUÇÃO AO ESTUDO DAS ESTRUTURAS DE DADOS
Um programa de computador consiste em basicamente duas coisas: instruções e locais
para se armazenarem dados e informações. Desse modo, as linguagens de programação são
“equipadas” com mecanismos para poder controlar as instruções e os dados. Esses mecanismos
são denominados “estruturas” e todas as linguagens de programação contemporâneas contam
com dois tipos de estruturas: as estruturas de controle de fluxo de execução (para instruções) e
as estruturas de dados (para os dados e informações).
Estruturas de Controle de Fluxo de Execução
Estrutura Sequencial;
Estruturas Condicionais;
Estruturas Iterativas;
Estruturas de Chamada/Retorno a/de Rotinas.
Estruturas de Dados
Estruturas Primitivas (Tipos de dados: inteiro, real, lógico e caracter);
Estruturas Compostas
Homogêneas (Vetores e Matrizes);
Heterogêneas (Registros e Arquivos).
Estruturas Complexas
Estáticas: Listas Estáticas (Sequenciais e Encadeadas), Pilhas, Filas, Árvores;
Dinâmicas: Listas Dinâmicas (Ponteiros), Pilhas, Filas, Árvores.
2 – REVISÃO DE TÓPICOS
- Modularização;
- Variáveis Locais e Globais;
- Passagens de Parâmetros.
Exemplos:
Algoritmos para cálculo do fatorial de um número n.
Exercícios
1) Escreva três algoritmos que recebam um número inteiro e exibam se esse número é primo ou
não. Cada algoritmo com uma técnica diferente: procedimento com passagem de parâmetros por
valor, procedimento com passagem de parâmetros por referência e por meio de função.
2) Escreva um algoritmo que receba 100 números quaisquer e armazene-os em um vetor.
Calcular e exibir o maior número e o menor número.
3 – REGISTROS
As estruturas de dados compostas, utilizadas até o momento (vetores e matrizes),
permitem o armazenamento de vários valores na mesma estrutura, porém, cada estrutura só pode
ser definida para um único tipo de dados.
Quando foi preciso utilizar dados de diferentes tipos para a solução de problemas, foi
necessário o uso de diferentes estruturas.
Agora, é apresentada uma estrutura de dados em que é possível armazenar, na mesma
estrutura, diversos valores de diferentes tipos de dados. Essa estrutura é chamada de Registro e,
por sua característica, classificada como uma estrutura de dados heterogênea.
Um exemplo de registro:
Sintaxe:
TIPO
identificador_registro = REGISTRO
campo1: tipo de dado;
campo2: tipo de dado;
.
.
.
campoN: tipo de dado;
FIM;
VAR
identificador_variavel: identificador_registro;
Exemplo:
BOLETIM ESCOLAR
Aluno.......: Fulano de Tal
1ª Nota....: 8,5
2ª Nota....: 9,0
3ª Nota....: 7,5
Média......: 7,5
TIPO
cad_aluno = REGISTRO
nome: caracter;
nota1: real;
nota2: real;
nota3: real;
media: real;
FIM;
VAR
aluno: cad_aluno;
Referência:
identificador_variavel.campo
aluno.nome “João”;
leia (aluno.nota1);
exiba (aluno.media);
Exercício
Escreva um algoritmo que receba o nome e as 4 notas de aluno, calcule e exiba a média das
notas e a situação (aprovado ou reprovado) baseado na média maior ou igual a 6,0.
Outro exemplo de registro:
Exemplo:
TIPO
bimestre = VETOR [1..4] de real;
cad_aluno = REGISTRO
nome: caracter;
nota: bimestre;
media: real;
FIM;
VAR
aluno: cad_aluno;
i: inteiro;
BOLETIM ESCOLAR
Aluno.......: Fulano de Tal
Notas
1 2 3 4
Média......: 7,5
Referência:
identificador_variavel.campo[ i ]
leia (aluno.nota[ i ]);
exiba (aluno.nota[ i ]);
Exercícios
1) Escreva um algoritmo para manipular a ocorrência de um único aluno e suas 4 notas
bimestrais. Receba os dados do usuário e exiba o boletim conforme o último layout apresentado.
2) Idem ao anterior, porém manipular os dados para uma sala com 40 alunos. (E agora, como fica
a estrutura de dados?).
TIPO
bimestre = VETOR [1..4] de real;
cad_aluno = REGISTRO
nome: caracter;
nota: bimestre;
media: real;
FIM;
aluno_vet = VETOR [1..40] de cad_aluno;
VAR
aluno: aluno_vet;
i, j: inteiro;
Referências:
leia (aluno[ i ].nome);
leia (aluno[ i ].nota [ j ]);
exiba (aluno[ i ].media);
Exercícios
1) Considerando o cadastro de uma agenda pessoal de nomes, telefones e cidades, defina a estrutura de
registros apropriada e construa um algoritmo para armazenar 50 contatos nessa agenda. Após o
armazenamento, exibir um relatório com os dados de todos os contatos que residem em Botucatu.
2) Considerando o cadastro de uma agenda de prestadores de serviços contendo nome, código de área,
telefone e cidade, defina a estrutura de registros apropriada e construa um algoritmo para armazenar 80
contatos nessa agenda. Após o armazenamento, exibir um relatório com os dados de todos os prestadores
de serviço de uma determinada área de telefone. Essa informação deve ser solicitada para o usuário. Obs:
o código de área deve ser um campo separado do número do telefone (para possibilitar a consulta prevista
no algoritmo).
identificador_variavel[ i ].campo[ j ] identificador_variavel[ i ].campo
4 – ALGORITMOS DE ORDENAÇÃO OU CLASSIFICAÇÃO
4.1 Algoritmo Bubble Sort (ou método da bolha)
Por ser simples e de fácil implementação o algoritmo Bubble Sort ou método da bolha está
entre os mais conhecidos e difundidos métodos de ordenação de arranjos de dados. Mas não se
trata do algoritmo mais eficiente. É estudado para desenvolvimento do raciocínio lógico e como
introdução ao assunto de ordenação ou classificação de dados.
O princípio da dinâmica de funcionamento desse algoritmo é o teste e a troca de valores
entre as posições consecutivas do arranjo, fazendo com que os valores mais altos (ou mais
baixos) “borbulhem” para o final do arranjo (ou começo). Este algoritmo realiza (n-1) iterações.
Exemplo:
Situação inicial:
1 2 3 4 5
7 9 8 5 3
1ª Iteração
1 2 3 4 5
7 8 5 3 9
2ª Iteração
1 2 3 4 5
7 5 3 8 9
3ª Iteração
1 2 3 4 5
5 3 7 8 9
4ª Iteração
1 2 3 4 5
3 5 7 8 9
Exercício
Escreva um algoritmo que armazene 100 valores num vetor e exiba-os em ordem crescente,
implementando o método bubblesort.
4.2 Algoritmo Selection Sort
O algoritmo do método de ordenação Selection Sort seleciona o maior elemento do arranjo
e troca-o com o elemento da última posição. A cada iteração desconsidera a “última” posição do
vetor fazendo com que o arranjo fique “menor”. Esse algoritmo também realiza (n-1) iterações
para garantir que os elementos estejam ordenados.
Dinâmica de funcionamento:
- Na primeira iteração o algoritmo encontra o maior elemento do vetor e troca este
elemento com o elemento que está na n-ésima posição;
- Na segunda iteração despreza-se o n-ésimo elemento e encontra-se novamente o maior
elemento (exceto o maior elemento já identificado na primeira iteração, este segundo maior é o
maior entre os demais) e troca-se com o elemento que está na (n-1)-ésima posição;
- Este processo se repete até que se tenha ordenado todos os elementos do arranjo.
Exemplo:
1 2 3 4 5
7 9 8 5 3
- Percorre-se o vetor na busca do maior elemento. Neste caso, o maior elemento é o 9, que
está na posição 2. Troca-se então esse elemento com o elemento que está na última posição.
Situação inicial:
1 2 3 4 5
7 9 8 5 3
1ª Iteração
1 2 3 4 5
7 3 8 5 9
- Finalizada a primeira iteração inicia-se o processo novamente, selecionando o maior
elementoe trocando-o com a penúltima posição. E assim sucessivamente...
Exercício
Escreva um algoritmo que armazene 100 valores num vetor e exiba-os em ordem crescente,
implementando o método selection sort.
4.3 Algoritmo Insertion Sort
O algoritmo do método de ordenação Insertion Sort utiliza a técnica de indução algorítmica
para resolver o problema de ordenação. Assim, sabendo-se resolver um problema menor,
resolve-se um problema maior.
Dinâmica de funcionamento:
Considere o vetor V assim definido: V = { V1, V2, V3, . . . ., Vn-1, Vn }.
Considere o vetor V´ assim definido e já ordenado: V´ = { V1}.
i-maior
A dinâmica consiste em comparar do segundo elemento em diante do vetor V com o
elemento (sempre já ordenado) do vetor V´ e inseri-los na posição correta do vetor V´.
Exemplo:
1 2 3 4 5
9 8 7 5 3
Tem-se: V´ = 9
V = 8 7 5 3
Inserir o elemento 8 no vetor já ordenado V´.
1 2 3 4 5
9 8 7 5 3
Para inserir o elemento 8 na posição correta do vetor V´, desloca-se o elemento 9
para uma posição a direita.
1 2 3 4 5
8 9 7 5 3
Assim, o processo se repete até que o vetor esteja ordenado.
1 2 3 4 5
8 9 7 5 3
1 2 3 4 5
7 8 9 5 3
1 2 3 4 5
5 7 8 9 3
1 2 3 4 5
3 5 7 8 9
Exercício
Escreva um algoritmo que armazene 100 valores num vetor e exiba-os em ordem crescente,
implementando o método insertion sort.
V´ V
V´ V
V´ V
V´ V
5 – ALGORITMOS DE PESQUISA (OU BUSCA)
São dois os métodos de pesquisa ou busca por uma ocorrência de algum elemento dentro
de um arranjo de dados que são mais comumente usados. Geralmente é o usuário da aplicação
quem fornece o valor do elemento que está sendo procurando e que tem interesse na pesquisa.
Esses dois métodos são: a pesquisa sequencial e a pesquisa binária.
Sobre qual desses tipos de pesquisa apresenta o melhor resultado, depende-se
exclusivamente da quantidade de operações realizadas para encontrar determinado elemento no
arranjo. E isso é determinado por dois fatores: a quantidade de elementos do arranjo e a
quantidade de consultas que se faça no mesmo.
Após conhecer como funcionam os dois tipos de pesquisa, no próximo capítulo serão
apresentados alguns cálculos sobre como calcular a quantidade de operações necessárias para
encontrar (ou não) um elemento dentro do arranjo de dados. Assim, pode-se decidir de modo
mais exato, qual dos dois métodos é o melhor aplicar em cada situação.
5.1 Pesquisa Sequencial
O método de pesquisa sequencial consiste em efetuar a busca da informação desejada a
partir do primeiro elemento, percorrendo o vetor sequencialmente até o último elemento.
Localizando a informação procurada em algum dos elementos, esta é apresentada ao usuário e
encerra-se a busca. Este método de pesquisa é, na média, um tanto lento. Porém, eficiente nos
casos em que o vetor contenha seus elementos de forma desordenada.
Exercício: Escreva um algoritmo que implemente o método de pesquisa sequencial.
5.2 Pesquisa Binária
O método da pesquisa binária apresenta um mecanismo que, na média, se mostra mais
rápido em comparação ao método da pesquisa sequencial. Uma diferença é que, para aplicar a
pesquisa binária, o arranjo de dados deve, obrigatoriamente, estar previamente ordenado.
Depois de ordenado o vetor, este é dividido em duas partes e examinado se a informação a
ser pesquisada se encontra na linha de divisão (meio) do vetor ou se está acima ou abaixo desta
linha de divisão. Se estiver acima, toda a metade inferior é desprezada para verificação. Em
seguida, se a informação não foi encontrada no meio (lembre-se de que o vetor já está
ordenado!), dividimos novamente esta metade do vetor em duas partes e examinamos se a
informação está no meio, acima ou abaixo da linha divisória. Assim, o processo de divisão e
descarte continua até que a informação seja encontrada ou até se acabar os elementos do
arranjo.
Exercício: Escreva um algoritmo que implemente o método de pesquisa binária.
Exercício
Escreva um algoritmo que exiba um menu para o usuário com as opções: Tamanho do Vetor,
Armazenar Valores, Ordenar Valores, Tipo de Pesquisa, Pesquisar e Sair; e implemente essas
funcionalidades.
6 – COMPARATIVO ENTRE OS ALGORITMOS DE PESQUISA
Cálculo do nº de Operações para Pesquisas Sequencial e Binária
Considerações:
Para a Ordenação do arranjo:
o tem-se (n – 1) iterações;
o o vetor verificará (n – 1) elementos;
o logo, (n – 1).(n – 1) = n2 – 2n + 1.
o a qtde de operações
é proporcional a n2
Para a Pesquisa no arranjo:
o n = nº de iterações por pesquisa ou nº de elementos do arranjo;
o m = qtde de pesquisas;
o op = nº de operações realizadas.
Pesquisa Sequencial
Op = n . m
Pesquisa Binária
- Na ordenação: op´ = n2
- Na pesquisa:
1ª iteração n/2 = n/21
log2 (n) = x
2ª iteração n/4 = n/22
3ª iteração n/8 = n/23
. .
. .
xª iteração n/2x =
- Se forem realizadas m pesquisas, tem-se: op´´ = m . log2 (n)
- Logo, o nº total de operações para a pesquisa binária é dado por op = op´ + op´´
-
Op = n2 + m . log2 (n)
desprezível
2x = n
Exemplo: Arranjo com 8 elementos
n = 8
log2 (8) =
2x = 8
x = 3
São necessárias 3 iterações (operações)
Exercícios
1) Dado um vetor com 8 elementos, decidir e apontar qual o melhor tipo de pesquisa a ser
aplicada no caso de necessidade de 1000 consultas nesse vetor.
2) Considere um arranjo com 8192 elementos. Qual o tipo de pesquisa é mais vantajoso
para 1000 consultas?
3) Dado um vetor com 30 elementos, indicar qual o melhor método de pesquisa para uma
necessidade de 250 consultas.
4) Determinado arranjo contém 3875 elementos e há necessidade de realizar 327
consultas. Nesse caso, qual método de pesquisa aplicar?
5) Dado um vetor com 1480 elementos e necessidade de 15 consultas qual tipo de
pesquisa você implementaria?
7 – LISTAS
Uma Lista é uma estrutura de dados que armazena em memória um conjunto de
elementos, sendo estes dispostos um após o outro, como em uma lista telefônica, um catálogo de
peças, entre outros. Existem vários tipos de listas, e as diferenças que os determinam são: o
modo e a política como são realizadas as operações sobre cada tipo de lista. As principais
operações são, entre outras, inclusão de elementos na lista, acesso a um determinado elemento
da lista, exclusão de um elemento, verificação de lista vazia, verificação de lista cheia e
quantidade de elementos na lista.
A partir de agora, praticamente todas as estruturas de dados que serão estudadas são
derivadas de algum tipo de lista. As listas podem ser classificadas de várias formas, que
dependem de alguns fatores, como: o modo como é implementada a alocação de memória; a
natureza de manipulação dos elementos da lista e, ainda, como ficam dispostos os elementos em
relação à linearidade ou à hierarquia da estrutura.
Classificação e Tipos de Listas
Quanto à Linearidade ou Hierarquia da Estrutura
Listas Lineares (Listas Seqüenciais, Encadeadas, Pilhas e Filas);
Listas Não-Lineares (Árvores).
Quanto à Alocação de Memória
Estática;
Dinâmica.
Quanto à Natureza de Manipulação dos Elementos
Sequencial;
Encadeada.
8 – ALOCAÇÃO ESTÁTICA DE MEMÓRIA
Com alocação estática de memória, as listas podem ser implementadas de forma
sequencial ou encadeada.
8.1 – Lista Estática Sequencial
A lista estática sequencial é uma lista linear implementada como um arranjo de registros
onde estão estabelecidas regras de precedência entre os seus elementos e se configura como
uma coleção ordenada de componentes do mesmo tipo.Exs: Lista de telefones e Lista de alunos.
Características
- Os elementos na lista estão ordenados e armazenados fisicamente em posições
consecutivas;
- A inserção de um elemento na posição i causa o deslocamento a direita dos
elementos de i até o último;
- A eliminação do elemento i requer o deslocamento a esquerda dos elementos (i+1)
até o último;
- Uma lista estática seqüencial L, ou é vazia ou pode ser descrita como:
L =
índice (i) 1 2 3 ... ... n
elemento a1 a2 a3 ... ... an
onde:
a1 é o primeiro elemento da lista;
ai precede ai+1;
an é o último elemento da lista.
Vantagens
- Acesso direto indexado a qualquer elemento da lista;
- Tempo constante para acessar o elemento da posição i.
Desvantagens
- Movimentação de elementos nas operações de inserção e eliminação;
- O tamanho máximo da lista deve ser pré-estimado.
Quando usar
- Listas pequenas;
- O tamanho máximo da lista for bem definido;
- Manipulação de dados com características de inserção e eliminação no final da
lista.
Implementação
TAD (Tipo Abstrato de Dados)
CONST
max = ----;
TIPO
Lista = REGISTRO
elem: VETOR [1..max] de T;
num: 0..max;
FIM;
VAR
L: Lista;
Exemplos:
a) Rotina para criar uma lista estática sequencial.
Procedimento CRIA_LISTA (var L: Lista);
Inicio
L.num 0;
Fim;
Tipo de Dado
inteiro, real,
lógico ou caracter
b) Rotina para verificar se a lista está ou não vazia.
Funcao VAZIA (L: Lista): logico;
Inicio
se (L.num = 0)
entao VAZIA verdadeiro
senao VAZIA falso;
fim se;
Fim;
Operações sobre Listas Estáticas Sequenciais
Exercícios – Escreva, com base no TAD apresentado, as rotinas para as respectivas operações:
1) Inserir um elemento na lista, em sequência.
2) Inserir um elemento na lista, em determinada posição.
3) Retornar o primeiro elemento da lista.
4) Retornar o último elemento da lista.
5) Retornar a quantidade de elementos da lista.
6) Eliminar um elemento da lista (não sendo o último).
7) Eliminar o último elemento da lista.
8) Verificar se os elementos da lista estão ordenados.
8.2 – Lista Estática Encadeada
Os elementos de uma lista estática encadeada são registros com um dos componentes
(campos) destinados a guardar o endereço do próximo registro, possibilitando assim o
encadeamento dos elementos da lista. Cada registro tem o seguinte formato:
Valor lig Ex: Alessandra 2
Uma lista hipotética L possui os seguintes elementos: Bruna, Daniel, Kátia, Mário e Rose.
Então, pode-se representar a lista L da seguinte forma:
L = Bruna Daniel Kátia Mário Rose
1 2 3 4 5
1º
elemento
último
elemento fim da
lista
Para gerenciar esse tipo de lista deve-se trabalhar com dois conjuntos de informação: os
elementos da lista (armazenados) e as posições da lista que ainda estão disponíveis. Obs:
Considera-se como “fim da lista” o último elemento do vetor que tenha valor armazenado, que
pode não ser necessariamente o tamanho máximo do vetor. Convencionou-se a utilizar o valor 0
(zero) para referenciar o fim da lista. Assim tem-se:
L = Bruna Daniel Kátia Mário Rose
Como exemplo o elemento com o valor “Kátia” será eliminado da lista. Nesse caso, o
registro desta posição ficará disponível e pronto para ser usado novamente. Assim, a referência
desse registro deve passar a integrar a lista denominada por “Dispo” que contém os registros
ainda livres (disponíveis) da lista.
L = Bruna Daniel Kátia Mário Rose
Se numa próxima inserção houver a necessidade de inserir o valor “Silvia” na lista, esta
deve ficar configurada da seguinte forma:
L = Bruna Daniel Silvia Mário Rose
Deve ficar claro que o que importa é o encadeamento dos elementos e não a ordem
seqüencial física do armazenamento.
Vantagens
- Não requer a movimentação dos elementos nas operações de inserção e eliminação;
- Apenas os campos de ligação são manipulados e alterados.
Desvantagens
- Necessário prever espaço máximo da lista; - Aumento no tempo de execução;
- Necessidade de gerenciar o campo Dispo; - Reserva de espaço para Dispo.
1 2 3 4 5
1º
elemento
(Prim)
último
elemento
Dispo
6 ... max
1 2 3 4 5
1º
elemento
(Prim)
último
elemento
Dispo
6 ... max
1 2 3 4 5
1º
elemento
(Prim)
último
elemento
Dispo
6 ... max
- O acesso não é indexado;
Quando usar
- Quando for possível fazer uma boa previsão do espaço utilizado;
- Quando o ganho dos movimentos dos elementos sobre a perda de acesso direto a cada
elemento for compensatório.
Implementação
TAD (Tipo Abstrato de Dados)
CONST
max = ----;
TIPO
endereço = 0..max;
Reg = REGISTRO
info: T;
lig: endereco;
FIM;
Lista = REGISTRO
A: VETOR [1..max] de Reg;
prim, dispo: endereco;
FIM;
VAR
L: Lista;
Operações sobre listas seqüenciais encadeadas
- Inicialização da lista; - Acesso ao último elemento da lista;
- Inserção com a lista vazia e não vazia; - Verificação de lista vazia ou cheia;
- Inserção antes ou após o registro x; - Eliminação de elementos da lista;
- Acesso ao primeiro elemento da lista; - Qtde de elementos da lista.
Exercícios – Escreva, com base no TAD apresentado, as rotinas para as respectivas operações:
1) Inicializar uma lista estática encadeada.
2) Retornar se a lista está vazia.
3) Retornar se a lista está cheia.
4) Inserir um elemento em lista vazia.
5) Obter um nó (posição) da lista.
6) Inserir um elemento em lista não vazia.
7) Retornar o primeiro elemento da lista.
8) Retornar o último elemento da lista.
9) Retornar a quantidade de elementos da lista.
10) Inserir um elemento após o elemento x.
11) Eliminar um elemento da lista.
12) Devolver um nó (posição) disponível para a lista.
Esboço do T.A.D
L
A
Info lig
Info lig
Info lig
Info lig
Info lig
prim
dispo
1 2 3 ... max N ...
9 – ALOCAÇÃO DINÂMICA DE MEMÓRIA
9.1 – Ponteiros
Na alocação estática de memória, conforme já abordamos, há a necessidade de o
programador fazer uma estimativa prévia da quantidade de memória a ser ocupada pelos dados
de sua aplicação. Isso se deve porque as estruturas de dados estáticas necessitam ser
declaradas antes do início do código, sendo assim, chamadas de estruturas pré-referenciadas. A
partir daí o programador não tem muita flexibilidade para mudar essa situação.
Em contrapartida, na alocação dinâmica de memória o programador dispõe de estruturas
de dados pós-referenciadas, criando novos registros sob demanda e necessidade da aplicação,
ficando limitação apenas em relação à quantidade de memória física disponível no hardware.
As linguagens de programação modernas tornaram possível explicitar não apenas os
dados, mas também os endereços desses dados. Isso é possível com a utilização de ponteiros,
que são variáveis de memória que “apontam” para endereços de memória. Esses endereços de
memória apontados pelos ponteiros são conhecidos como variáveis dinâmicas.
Exemplo:
A notação introduzida por Wirth para a linguagem de programação Pascal é:
Type
tp = ^T;
Essa declaração expressa que valores do tipo tp são ponteiros para dados do tipo T.
Portanto, lê-se o símbolo “^” como “ponteiro para”.
Valores para ponteiros são gerados quando dados correspondentes a seus tipos são
alocados/desalocados dinamicamente. Em pascal os procedimentos new() e dispose() existem
com esse propósito.
Em pseudolinguagem serão adotados os comandos:
ALOCA: cria um ponteiro que automaticamente aponta para um endereço de memória
existente. Ex:
ALOCA (p); =DESALOCA: destrói um ponteiro juntamente com a variável dinâmica associada. Ex:
DESALOCA (p); =
2BF57 conteúdo
p 2BF57
ponteiro
endereço
físico
variável
dinâmica
p variável dinâmica
p variável dinâmica
Exemplo:
ALGORITMO EXEMPLO;
TIPO
p = ^ caracter;
VAR
nome: p;
INICIO
ALOCA (nome);
leia (nome^);
exiba (nome^);
DESALOCA (nome);
FIM.
Exercício: Usando os conceitos de ponteiro e variáveis dinâmicas, escreva um algoritmo que
armazene o nome de um aluno e suas duas notas de prova. Calcule a média das notas e exiba o
nome e a média.
9.2 – Listas Dinâmicas
A implementação de listas dinâmicas é feita com o uso de ponteiros e dessa forma o
trabalho de alocação de memória fica facilitado. É possível então criar registros por meio de
ponteiros e, conforme a necessidade e a demanda, acrescentando elementos ou eliminando
posições no arranjo de dados ou lista dinâmica.
Operações sobre Listas Dinâmicas
- Criar lista vazia;
- Inserir o primeiro elemento na lista;
- Inserir elemento no início da lista;
- Acessar o primeiro elemento;
- Acessar o último elemento;
- Quantidade de elementos da lista;
- Inserir valor (após determinado elemento ou na posição x);
- Eliminar elemento (após determinado elemento ou da posição x).
Registros com ponteiros
Uma variável do tipo p_rec (por exemplo), aponta para ou é um ponteiro para um registro
do tipo de dados registro, conforme trecho de declaração da estrutura de dados abaixo:
TAD (Tipo Abstrato de Dados)
TIPO
p_rec = ^rec;
Lista = p_rec;
rec = REGISTRO
info: T;
lig: Lista;
Fim;
VAR
L: Lista;
Obs: O tipo de dados ponteiro é o único tipo que pré-referencia outro tipo em Pascal.
Um ponteiro do tipo “Lista” (definido na estrutura acima) pode assumir o conjunto de
valores que corresponde a endereços reais de memória para os campos “info” e “lig”.
Exemplo:
Onde o conteúdo de P corresponde ao endereço do objeto (conjunto de campos info e lig).
Esses endereços são as ligações das listas encadeadas dinâmicas.
Referências – Sintaxe
- O acesso ao registro depende do tipo de alocação adotada. Se utilizar alocação em vetor,
referencia-se como: L.A[p]; e se a alocação for dinâmica, referencia-se como: p^.
- Para referenciar ponteiro, objeto e campo, as sintaxes são:
- ponteiro: p
- objeto: p^
- campos: p^.info
p^.lig
- Para referenciar e controlar a informação sobre fim da lista ou lista vazia, também se deve
utilizar o valor 0 (zero), também conhecido como endereço nulo ou terra para esse fim.
Obs: A linguagem de programação Pascal possui uma constante pré-definida para denotar
esse endereço nulo chamada “nil”.
P
objeto endereço
Info lig
( )
Ponteiro X Objeto apontado
Ex:
a) p q; (ponteiros)
b) p^ q^; (conteúdo dos registros apontados pelos ponteiros)
Ex:
Situação Inicial
a) p q;
b) p^.lig q^.lig;
Kátia Mário Rose
Bruna Daniel Silvia
Bruna Daniel Silvia
Kátia Mário Rose
Bruna Daniel Silvia
p
q
p q
p
q
Manipulação de Registros
1) Criação de um registro
ALOCA (p);
Substitui o procedimento Obter_No (j), utilizado em listas estáticas encadeadas, dada uma
variável do tipo ponteiro para tipo ^rec.
- Efetivamente aloca uma variável do tipo rec;
- Gera um ponteiro ^rec apontado para aquela variável;
- Atribui o ponteiro à variável p.
A partir daí:
- O ponteiro pode ser referenciado como p;
- A variável referenciada por p é denotada por p^.
2) Liberação de um registro
DESALOCA (p);
Substitui o procedimento Devolver_No (j).
- Esta operação libera o espaço apontado por p;
- p passa a ter valor indefinido.
Exercícios – Escreva, com base no TAD apresentado, as rotinas para as respectivas operações:
1) Criar uma lista.
2) Retornar se a lista está ou não vazia.
3) Inserir o primeiro elemento na lista.
4) Inserir um elemento no início da lista.
5) Inserir um elemento no final da lista.
6) Retornar o primeiro elemento da lista.
7) Retornar o último elemento da lista.
8) Retornar a quantidade de elementos da lista.
9) Inserir um elemento após outro na lista.
10) Inserir um elemento na posição “x” da lista.
11) Eliminar o primeiro elemento da lista.
12) Eliminar o último elemento da lista.
13) Eliminar o elemento da posição “x” da lista.
p
Info lig
p
Info lig
Exercícios
1) Diferencie Alocação Estática de Memória de Alocação Dinâmica de Memória.
2) Explique o que são ponteiros.
3) O que são listas? Explique e dê exemplos.
4) Defina, sintaticamente:
a) ponteiro
b) objeto
c) campo
5) Explique os seguintes comandos de atribuição:
a) p^ q^;
b) p p^.lig;
c) p q;
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
10 – PILHAS
Pilhas são listas onde as inserções de novos elementos ou as eliminações de elementos já
armazenados se dão em uma única extremidade da lista: no topo.
Pilhas também são conhecidas como Listas LIFO (Last In First Out – Último que Entra,
Primeiro que Sai), ou seja, o último elemento inserido é o primeiro a ser acessado, lido, excluído,
processado.
Pilha Vazia
Insere (A) A
Insere (B) A B
Remove (B) A B
Insere (C) A C
10.1 Operações sobre Pilhas
- Criar (P): cria uma pilha vazia;
- Inserir (x, P): insere x no topo da pilha P. Também conhecida como empilha (ou push);
- Vazia (P): testa se a pilha está vazia;
- Topo (P): acessa o elemento do topo da pilha;
- Remove (P): elimina um elemento da pilha P. Conhecida como desempilha (ou pop).
topo
topo
topo
topo
topo
10.2 Aplicações de Pilhas
Exemplo: Chamadas de Rotinas
Quando a rotina R1 é executada ela efetua uma chamada a rotina R2, que deve carregar
consigo o endereço de retorno e1. Ao término de R2 o processamento deve retornar a
rotina R1 no devido endereço. Situação idêntica ocorre em R2 e R3.
Assim, quando um procedimento termina, é o seu endereço de retorno que deve ser
consultado. Portanto, há uma lista implícita de endereços (e0, e1, e2, e3) que deve ser
manipulada como uma pilha pelo sistema operacional para controle na execução de
programas. Obs: e0 é o endereço de retorno de R1.
10.3 Implementação de Pilhas
Como implementar uma pilha? Como lista sequencial ou encadeada? No caso geral de
listas ordenadas, a maior vantagem da alocação encadeada sobre a sequencial (se a
memória não for problema) é a eliminação dos deslocamentos necessários quando da
inserção ou eliminação dos elementos.
No caso de pilhas, essas operações de deslocamentos não ocorrem. Portanto, pode-se
afirmar que a alocação sequencial é mais vantajosa na maioria das vezes.
10.4 Alocação Sequencial de Pilhas
TAD (Tipo Abstrato de Dados)
CONST
max_pilha = ----;
TIPO
indice = 0..max_pilha;
Pilha = VETOR [1..max_pilha] de T;
VAR
P: Pilha;
topo: indice;
rotina R1;
R2;
e1
fim;
rotina R2;
R3;
e2
fim;
rotina R3;
R4;
e3
fim;
rotina R4;
fim;
Exercícios – Escreva, com base no TAD apresentado, as rotinas para as respectivas operações:
1) Criar uma pilha.
2) Retornar se a pilha está vazia.
3) Retornar se a pilha está cheia.
4) Inserir um elemento na pilha (empilhar).
5) Acessar elemento da pilha.
6) Eliminarelemento da pilha.
7) Acessar elemento da pilha e eliminá-lo em seguida (desempilhar).
10.5 Alocação Dinâmica de Pilhas
TAD (Tipo Abstrato de Dados)
TIPO
pont = ^reg_pilha;
Pilha = pont;
reg_pilha = REGISTRO
info: T;
lig: pont;
FIM;
VAR
P: Pilha;
Exercícios – Escreva, com base no TAD apresentado, as rotinas para as respectivas operações:
1) Criar uma pilha.
2) Retornar se a pilha está vazia.
3) Empilhar um elemento.
4) Acessar a pilha.
5) Eliminar elemento da pilha.
6) Desempilhar elemento.
11 – FILAS
Filas são listas em que as inserções de novos elementos são feitas numa extremidade da
lista e as eliminações na outra.Filas também são conhecidas como Listas FIFO (First In First Out
– Primeiro que Entra, Primeiro que Sai), ou seja, o primeiro elemento inserido é o primeiro a ser
acessado, lido, excluído, processado.
( a1, a2, a3, ..., an -1, an )
11.1 Operações sobre Filas
- Criar (F) – cria uma fila vazia;
- Inserir (x, F) – insere x no final da fila;
- Vazia (F) – testa se a fila esta vazia;
- Primeiro (F) – acessa o elemento do inicio da fila;
- Ultimo (F) – acessa o elemento do final da fila;
- Elimina (F) – elimina o elemento do inicio da fila.
11.2 Aplicações de Filas
Exemplo: Escalonamento de jobs (fila de processos aguardando recursos do S.O.).
11.3 Implementação de Filas
Como implementar uma fila? Como lista sequencial ou encadeada? Estática ou Dinâmica?
Só tem sentido falar em fila sequencial estática ou fila encadeada dinâmica, uma vez que
não existe movimentação de elementos. As filas encadeadas são usadas quando não há
previsão do tamanho máximo da fila.
Fila
Eliminações
no Início
Inserções
no Final
Começo Final
11.4 Alocação Estática Sequencial de Filas
TAD (Tipo Abstrato de Dados)
CONST
max_fila = ----;
TIPO
indice = 0..max_fila;
Fila = VETOR [1..max_fila] de T;
VAR
F: Fila;
Comeco, {posição anterior ao primeiro elemento}
Final: indice; {posição do último elemento}
Exercícios – Escreva, com base no TAD apresentado, as rotinas para as respectivas operações:
1) Criar uma fila.
2) Retornar se a fila está vazia.
3) Retornar se a fila está cheia.
4) Inserir um elemento na fila.
5) Acessar o primeiro elemento da fila.
6) Acessar o último elemento da fila.
7) Eliminar elemento da fila.
Problemas na Alocação Estática Sequencial de Filas
O que aconteceria com a fila considerando a seguinte sequência de operações? I, E, I, E, I,
E, I, E. Legenda: I – Inclusão; E – Exclusão.
Resp. A fila terá sempre um elemento (ou nenhum), e no entanto, num certo
momento:
Fila
Começo
Final
Ou seja, apenas um elemento (ou nenhum) na fila, ocupando a última posição do
vetor. Na próxima inserção ocorrerá uma condição de overflow e na verdade a fila está
vazia!
Exercício: Implemente uma solução para resolver o problema.
Solução: Na rotina de eliminação, após a atualização da variável Comeco, verificar se a fila
ficou vazia, isto é, se Comeco = Final. Neste caso, reinicializar: Comeco e Final 0.
Procedimento ELIMINA (var Comeco, Final: indice);
Inicio
se (VAZIA = falso)
entao Comeco Comeco + 1;
se (Comeco = Final)
entao Comeco 0;
Final 0;
fim se;
fim se;
Fim;
E o que aconteceria com a fila se a sequência de operações fosse: I, I, E, I, E, I, E, I, E, I?
Resp.: A fila estaria com no máximo dois elementos e ainda assim ocorreria
overflow com a fila quase vazia.
Alternativa: Forçar Final a usar o espaço liberado por Comeco, implementando o que se
conhece por Fila Circular!
11.5 Fila Circular
Fila Circular implementada como um Anel.
Começo
Final
Condições:
- Índices do vetor: 0..max_fila - 1;
- Variáveis de controle: comeco e final;
- Inicialmente: comeco = final = 0;
- Quando Final = max_fila - 1, o próximo elemento será inserido na posição 0 (se essa
posição estiver vazia!);
- Condição de fila vazia: Comeco = Final.
T.A.D.
TIPO
indice = 0..max_fila - 1;
Fila = VETOR [0..max_fila -1] de T;
VAR
F: Fila;
Comeco, Final: indice;
Procedimento INSERE (var F: Fila; var Final: indice; Comeco: indice; valor: T);
Inicio
se ( (Final + 1) MOD max_fila = Comeco) )
entao exiba (“Fila Cheia!”)
senao
Final (Final + 1) MOD max_fila;
F [Final] valor;
fim se;
Fim;
Procedimento ELIMINA (var Comeco: indice);
Inicio
Comeco (Comeco + 1) MOD max_fila;
Fim;
11.6 Alocação Dinâmica de Filas
TAD (Tipo Abstrato de Dados)
TIPO
pont = ^reg_fila;
Fila = pont;
reg_fila = REGISTRO
info: T;
lig: pont;
FIM;
VAR
Comeco, Final: Fila;
Exercícios – Escreva, com base no TAD apresentado, as rotinas para as respectivas operações:
1) Criar uma fila.
2) Retornar se a fila está vazia.
3) Inserir o primeiro elemento na fila.
4) Inserir um elemento na fila.
5) Retornar o primeiro elemento da fila.
6) Retornar o último elemento da fila.
7) Eliminar elemento da fila.
12 – ESPALHAMENTO (HASHING)
• Técnica eficiente para armazenamento e recuperação de dados;
• Vantagem: não requer ordenação dos dados.
12.1 Fundamentos
• Chama-se espalhamento (ou hashing) de um conjunto C a sua divisão em um número finito
de partes: C1, C2,..., Cn, para n>1, tal que:
• C1 ᴗ C2 ᴗ ... ᴗ Cn = C, ou seja, todo elemento de C ocorre em uma dessas partes;
• Ci ∩ Cj = Ø, para 1 ≤ i < j ≤ n, isto é, nenhum elemento de C ocorre em mais de uma
dessas partes;
• Tais propriedades garantem que em um espalhamento de um conjunto C em n partes,
nenhum elemento seja perdido ou duplicado;
• A distribuição unívoca dos elementos de C em n partes, sugere a existência de uma função
h: [1..n], denominada função de espalhamento, que mapeia cada elemento de C à sua
respectiva parte Ci, isto é: x Є C x Є C h(x);
• Caso um espalhamento efetuado por uma função h e o resultado da diferença absoluta
entre os tamanhos de duas partes quaisquer seja no máximo 1, diz-se que essa função h é
ótima;
• Exemplo:
Sejam o conjunto C = {a,b,c,d,e} e h uma função de espalhamento h: C [1..3] que
resulte nos seguintes valores para os elementos de C:
h(a) = 2
h(b) = 1
h(c) = 3
h(d) = 3
h(e) = 1
Então, por h, os elementos de C serão distribuídos da seguinte forma:
C1 = {b,e}
C2 = {a}
C3 = {c,d}
• Como a diferença absoluta entre os tamanhos das partes de C geradas pela função h é de
no máximo 1, pode-se dizer que essa função é ótima para esse espalhamento;
• Como todas as partes de C possuem aproximadamente o mesmo tamanho, diz-se que o
espalhamento é uniforme, ou seja, a menor parte possui (|C| div n) elementos e a maior
parte possui (|C| div n) + 1 elementos;
• Considerando o espalhamento de um conjunto C, por uma função ótima (h: C [1..|C|]),
isto é, o número de partes é igual ao número de elementos do conjunto C, e que o
espalhamento é uniforme (h é ótima!), cada parte terá exatamente um elemento do
conjunto C, e aí se diz que o espalhamento é perfeito;
• Na prática é muito difícil de se obter espalhamentos perfeitos. Geralmente, ao menos uma
das partes é vazia ou possui mais de um elemento (colisão);
• Aplicações de Espalhamento
• Consultas;
• Transformação de elementos alfanuméricos;
• Tratamento de permutações.
• Considere x Є C e o valor h (x) que possibilita acesso direto na parte de C que contém x.
Assim, caso C seja um arranjo a ser consultado, um espalhamento permitirá um processo
de busca muito eficiente, pois essa busca estará restritaa uma pequena parte do conjunto
C;
• Espalhamento técnica de redução do espaço de busca. Quando o tamanho das partes
diminui, a eficiência da busca aumenta. Na situação extrema do espalhamento perfeito, o
valor h (x) permite acesso direto à posição de x, tornando-se o mais eficiente método de
busca existente.
12.2 Funções de Espalhamento
• Mapeia cada elemento de um conjunto em um endereço;
• Situação ideal: n elementos distintos n endereços distintos;
• Existem n! modos de se fazer esse mapeamento ideal;
• Existem nn maneiras de mapear n elementos a n endereços;
• Probabilidade espalhamento perfeito: mínima! (n!/nn);
• Contente-se com funções de mapeamentos razoáveis (distribuições de elementos
medianamente uniformes em endereços);
• Já foi dispendido muito esforço para se criar funções de espalhamentos eficientes.
Paradoxalmente, um dos métodos mais simples (divisão) é o que vem apresentando os
melhores resultados na prática.
x = “João”
Antônio
Pietra
João
Carla
Martin
C
Busca sem espalhamento
C
3
x = “João”
h(x) = 2
Antônio
Pietra
João
Carla
Martin
C
2
C
1
Busca com espalhamento
Antônio
Pietra
João
Carla
Martin
x = “João”
h(x) = 3
C
1
C
2
C
3
C
4
C
5
Espalhamento perfeito
• Método da Divisão
• Indicado para C como subconjunto de N;
• Efetua-se uma divisão por n, considere o resto e soma-se 1;
• Exemplo:
Para n = 3 e C = {9, 12, 22, 35, 47, 51}, tem-se os seguintes valores para h:
h (9) = ( 9 mod 3) + 1 = 1
h (12) = (12 mod 3) + 1 = 1
h (22) = (22 mod 3) + 1 = 2
h (35) = (35 mod 3) + 1 = 3
h (47) = (47 mod 3) + 1 = 3
h (51) = (51 mod 3) + 1 = 1
• Portanto:
C1 = {9, 12, 51}
C2 = {22}
C3 = {35,47}
• Transformação de Elementos Alfanuméricos
• Com elementos alfanuméricos o método da divisão não pode ser usado direto;
• Devem, antes, serem convertidos em valores numéricos;
• Dica: somar o código ASCII de cada caracter do elemento;
• Exemplo em Pascal:
function h (x: string): integer;
var i, s: integer;
begin
s := 0;
for i := 1 to length (x) do
s := s + ord (x[i]);
h := (s mod n) + 1;
end;
• Considere:
n = 7 e C = {Bia, Décio, Edu, Lucy, Neusa, Rose, Sueli, Thais, Yara}
• Tem-se:
h (“Bia”) = 3 Portanto:
h (“Décio”) = 2
h (“Edu”) = 7 C1 = {Lucy}
h (“Lucy”) = 1 C2 = {Décio, Thais}
h (“Neusa”) = 5 C3 = {Bia}
h (“Rose”) = 4 C4 = {Rose, Sueli}
h (“Sueli”) = 4 C5 = {Neusa}
h (“Thais”) = 2 C6 = {Yara}
h (“Yara”) = 6 C7 = {Edu}
12.3 Tabelas de Espalhamento
• Estruturas de dados que permitem a implementação do espalhamento de um conjunto C
em aplicações computacionais;
• Representada por um vetor onde cada posição (encaixe), mantém uma parte de C;
• Evidente: número de encaixes coincide com o número de partes criadas pela função de
espalhamento;
• Se admitir a existência de elementos sinônimos em C, espera-se a ocorrência de pelo
menos uma colisão (necessidade de armazenar um elemento em uma posição já ocupada);
• Solução: tratamento de colisão por espalhamento externo;
• Princípio de que existirão muitas colisões na inserção dos elementos numa tabela;
• Ao invés de armazenar só um elemento, cada encaixe T[i] guardará uma coleção de
elementos sinônimos. Como a quantidade destes pode variar bastante, recomenda-se o
uso de lista encadeada;
• Assim, uma tabela de espalhamento externo será um vetor de listas encadeadas, cada
uma delas uma parte de C;
• O termo “externo” se dá em função dos elementos serem armazenados fora da tabela de
espalhamento (área de memória alocada de modo dinâmico).
1
2
3
4
5
23 62
8 14
11 51 37
34 83 59
8 14
• Tipo Abstrato de Dados (TAD)
Const
n = 5;
Tipo
Lista = ^rec;
rec = REGISTRO
info: T;
lig: Lista;
FIM;
Tab_Esp = VETOR [1..n] de Lista;
Var
TE: Tab_Esp;
• Operações Básicas com Tabelas de Espalhamento
Inicialização da Tabela
Procedimento INICIALIZA_TABELA (var TE: Tab_Esp);
var
i: integer;
Inicio
para i 1 to n faca
CRIAR_LISTA (TE[i]);
fim para;
Fim;
Inserção na Tabela
Procedimento INSERE_TABELA (var TE: Tab_Esp; valor: T);
Inicio
se (L = 0)
entao INSERE_PRIMEIRO_ELEMENTO (TE [ h(valor) ], valor: T )
senao INSERE_INICIO_LISTA (TE [ h(valor) ], valor: T );
fim se;
Fim;
Eliminação de elemento da Tabela
Procedimento ELIMINA_ELEMENTO (var L: Lista; valor: T); {a lista deve estar ordenada}
var n, p: Lista;
Inicio
se (valor = L^.info)
entao n L;
L L^.lig;
DESALOCA (n);
senao p L;
enquanto (p^.lig <> 0) E (valor > p^.lig^.info) faca
p p^.lig;
fim enquanto;
n p^.lig;
p^.lig n^.lig;
DESALOCA (n);
fim se;
Fim;
Procedimento ELIMINA_ELEMENTO_TABELA (var TE: Tab_Esp; valor: T);
Inicio
ELIMINA_ELEMENTO (TE [ h (valor) ], valor: T );
Fim;
13 – RECURSIVIDADE
• Método que possibilita a solução de um problema P a partir das soluções de subproblemas
de P e que são semelhantes ao próprio P;
• Decompõe-se P até a obtenção de subproblemas simples que possam ser resolvidos
diretamente sem ser preciso novas decomposições (triviais);
• Os resultados obtidos compõem a solução do problema original;
• Considere P(i) um algoritmo que resolve o problema P com entrada i. Diz-se que P(i) é uma
instância de P. Sendo P(i) e P(i’) duas instâncias de P, denota-se P(i’) < P(i) indicando
que P(i’) é mais simples que P(i);
• Podendo uma instância ser decomposta em outra mais simples, diz-se que ela é não
trivial e pode ser reduzida. Quando uma instância não pode ser reduzida ela é
denominada trivial;
• Um algoritmo recursivo é composto de duas partes de destaque:
• base de recursão (resolve instâncias triviais diretamente);
• passo de recursão (resolve instâncias não triviais recursivamente);
13.1 Implementação de Recursão
• Exemplo: Cálculo de fatorial
• Def.:
1 para n = 0
n...3.2.1 para n > 0
• Tomando por exemplo: a instância 3! é mais simples que a instância 4!, pois 3! necessita
de uma multiplicação a menos que 4!;
• Como 0 é o menor número natural, a instância 0! não pode mais ser reduzida à outra
instância mais simples (:. é trivial);
n! =
• Classificadas as instâncias do problema fatorial em triviais e não triviais, pode-se resolvê-
las aplicando as seguintes regras:
• Base de recursão: para n=0, a instância n! é trivial e sua solução é 1;
• Passo de recursão: para n>0, a instância n! é não trivial e sua solução é n.(n-1)!.
Pseudocódigo
Funcao fat (n: inteiro): inteiro;
Inicio
se (n = 0)
entao fat 1
senao fat n * fat (n – 1);
Fim;
Simulação da execução de fat(4) usando substituições sucessivas
Outro modo de simular a dinâmica de uma função recursiva
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
Substituições sucessivas efetuadas para alcançar uma instância trivial Instância
não trivial
Solução
obtida
24 =
• Para cada chamada recursiva, reduz-se uma instância não trivial em outra mais simples e a
operação de multiplicação fica pendente, esperando a propagação da resolução da
instância mais simples;
• Considera-se que a função que chama fica congelada, esperando a finalização da
chamada recursiva para a continuidade da execução;
• Isso é possível porque há o empilhamento do endereço de retorno da função que faz a
chamada,da qual a execução deve seguir na instrução que faz o cálculo da multiplicação;
• Ao se obter a instância trivial, as chamadas “congeladas” são reativadas em uma ordem
inversa daquela em que ocorreram;
• Por essa razão a base de recursão de uma função recursiva é tão importante!;
• Sem ela, o circuito cíclico das chamadas recursivas nunca será finalizado, gerando o que é
conhecido como stack overflow, ou seja, um erro que causa o estouro da pilha que controla
o fluxo de execução da função.
Outros exemplos usando recursão
Cálculo de Potência
As regras utilizadas para determinar as instâncias do problema da potenciação são:
Base de recursão: se n = 0, a instância xn é trivial e sua solução é 1;
Passo de recursão: se n > 0, a instância xn é não trivial e sua solução é x.xn-1.
2
3
8
4
*2
2
2
Caso geral
*n
x
n - 1
x
n - 1
x.x
n - 1
x
n
Caso particular
Pseudocódigo
Funcao potencia (x: real; n: inteiro): real;
Inicio
se (n = 0)
entao potencia 1
senao potencia x * potencia (x, n-1);
Fim;
13.2 Recursão Degenerada
• Os aspectos de simplicidade e legibilidade se apresentam como principais benefícios da
técnica de recursão;
• Entretanto, existem dois casos em que tais benefícios não são alcançados:
Recursão de Cauda;
Recursão Redundante.
Recursão de Cauda
• Ocorre quando existe uma única chamada recursiva no final de uma rotina. Assim,
nenhuma instrução dessa rotina fica pendente de ser executada depois de terminar a
execução dessa chamada recursiva;
• Portanto, o único objetivo dessa recursão (cauda) é a criação de um looping, o qual
pode ser mais eficientemente implementado por um comando iterativo.
Funcao SOMA (n1, n2: inteiro): inteiro;
Inicio
se (n1 = 0)
entao SOMA n2
senao SOMA SOMA (pred (n1), succ (n2) );
Fim;
Execução da chamada recursiva SOMA (3,4)
SOMA(3,4) 7
SOMA(2,5)
SOMA(1,6)
SOMA(0,7)
7
7
7
• A utilização de recursão deixa essa execução mais custosa, pois cada chamada
recursiva necessita da alocação de variáveis locais para armazenar os parâmetros n1 e
n2, e também empilhar o endereço de retorno para a função que realiza a chamada;
• Conclui-se que é mais eficiente substituir essa recursão por um laço iterativo.
Funcao SOMA (n1, n2: inteiro): inteiro;
Inicio
Enquanto (n1 > 0) faca
n1 pred (n1);
n2 succ (n2);
Fim Enquanto;
SOMA n2;
Fim;
Recursão Redundante
• Outro exemplo no qual uma solução iterativa é mais vantajosa que uma recursiva são
os casos em que ao decompor o problema resultam inúmeros subproblemas iguais (por
isso, recursão redundante!);
• Exemplo: sequência de Fibonacci (1, 1, 2, 3, 5, 8, 13, ...).
Funcao FIBO (n: inteiro): inteiro;
Inicio
se (n < 3)
entao FIBO 1
senao FIBO FIBO (n - 2) + FIBO (n - 1);
Fim;
Solução iterativa em substituição à função recursiva para cálculo de Fibonacci
Funcao FIBO (n: inteiro): inteiro;
var
i, a, b, c: inteiro;
Inicio
a 1;
b 0;
para i 1 ate n faca
c a + b;
a b;
b c;
fim para;
FIBO c;
Fim;
13.3 Eliminação de Recursão
• O controle do fluxo entre chamadas/retornos de rotinas é realizado por uma pilha
administrada pelo SO que vai empilhando endereços de retorno e as variáveis locais;
• Desse modo, usando instruções iterativas e pilhas, podemos converter qualquer rotina
recursiva em uma rotina iterativa;
• Em geral, rotinas iterativas são mais rápidas que rotinas recursivas;
• No entanto, caso haja uma quantidade muito grande de instruções iterativas e muitas pilhas
para converter recursão em iteração, talvez seja melhor ficar com a versão recursiva
mesmo;
• Um programa com código simples e claro é mais vantajoso que outro que execute apenas
ligeiramente mais rápido;
• Quando uma chamada recursiva é executada, o endereço de retorno e todas as variáveis
locais precisam ser recriadas na pilha;
• Exemplo: chamada recursiva de fat(4).
• Ao ser executada, a variável n é criada no topo da pilha com o valor passado no momento
da chamada. Ao terminar, a última cópia de n criada é destruída. Desse modo, em
qualquer instante da execução o valor de n será o que estiver no topo da pilha.
Relação entre Recursão e a estrutura de Pilha
• Para melhor compreensão da relação entre recursividade e manipulação de pilhas, observe
o procedimento recursivo abaixo, o qual exibe os itens de uma lista ordenada em ordem
inversa.
Fat (4)
4
n
Fat (3)
3
4
n
Fat (2)
3
2
4
n
Fat (1)
3
2
1
4
Fat (0)
3
2
1
0
4 n
n
Procedimento Exibe_Itens (L: lst_ord);
Inicio
se (nao VAZIA (L) )
entao
Exibe_Itens (restante (L) );
exiba (primeiro (L) );
fim se;
Fim;
• Para a exibição dos itens de uma lista em ordem inversa o algoritmo deve exibir o resto
dessa lista em ordem inversa e, por último, exibir o primeiro item;
• Para cada chamada recursiva ( Exibe_Itens (L) ) uma cópia da variável L é criada para
manter o endereço do próximo bloco dessa lista;
• Durante as etapas de execução os endereços dos blocos vão sendo empilhados até a lista
ficar vazia, momento em que se retorna o controle para a instrução exiba ( primeiro (L) ), a
qual vai exibir os itens da lista, do último para o primeiro (ordem inversa);
• Nota-se, nesse caso, que o único objetivo desse procedimento recursivo é o de simular
uma pilha onde os endereços dos blocos da lista precisam esperar o momento para os
itens serem exibidos;
• Assim, com o uso de uma pilha, pode-se substituir o procedimento recursivo por uma
versão iterativa equivalente, conforme o código a seguir.
Procedimento Exibe_Itens (L: lst_ord);
var
P: Pilha;
Inicio
CRIA (P);
Enquanto (nao VAZIA (L) ) faca
EMPILHA (primeiro (L), P );
L resto (L);
Fim Enquanto;
Enquanto (nao VAZIA (P) ) faca
exiba (DESEMPILHA (P) );
Fim Enquanto;
Fim;
Funcao restante (L: Lista): Lista;
Inicio
restante L^.lig;
Fim;
Funcao primeiro (L: Lista): T;
Inicio
primeiro L^.info;
Fim;
Etapa de reduções
Etapa de propagação
14 – ÁRVORES
• Árvores são estruturas de dados que permitem organizar dados de modo hierárquico.
14.1 Fundamentos
• Uma árvore A é uma estrutura composta por n nós, para n ≥ 0;
• Para n = 0, diz-se que a árvore A é vazia; Senão:
há um nó especial em A chamado de raiz;
os outros nós de A são organizados em A1, A2, ..., Ak estruturas de árvores
disjuntas, denominadas de subárvores de A.
• Por definição, árvores são estruturas recursivas, isto é, caso a raiz de uma árvore
seja removida, tem-se uma coleção de árvores;
• Cada nó de uma árvore é raiz de alguma subárvore;
• Representação gráfica de uma árvore
• Nó pai e nó filho;
• A raiz (principal) de uma árvore é o único nó que não tem pai;
• A quantidade de filhos de um nó é denominada grau, assim como um nó que tenha
grau 0 é denominado folha;
• O grau de uma árvore é o máximo entre os graus de seus nós;
• A altura de uma árvore é o máximo dos níveis de seus nós;
• Por definição, uma árvore vazia possui altura 0;
• Árvores são usadas para representar dados e informações com necessidade de
organização de modo hierárquico. Exemplos: árvores genealógicas, árvores
gramaticais, árvores taxonômicas, entre outros;
A
B C
D F E
G
• Na área de sistemas operacionais são úteis na representação de arquivos e
estruturas de diretórios e subdiretórios além de outras aplicações (adiante).
14.2 Árvores Binárias
Uma árvore binária é um tipo particular de árvore que possui grau 2 e onde se distingueuma subárvore esquerda de uma subárvore direita;
Definição do tipo árvore binária
Tipo
arvb = ^bloco_no;
bloco_no = registro
esq: arvb;
info: T;
dir: arvb;
fim;
Operações básicas com árvores binárias
Criação de uma árvore
Funcao NO (e: arvb; valor: T; d: arvb): arvb;
var
A: arvb;
Inicio
Aloca (A);
A^.esq e;
A^.info valor;
A^.dir d;
NO A;
Fim;
Verificação de árvore vazia
Funcao VAZIA (A: arvb): logico;
Inicio
VAZIA (A = 0);
Fim;
Retorno do item da raiz da árvore
Funcao RAIZ (A: arvb): T;
Inicio
RAIZ A^.info;
Fim;
Retorno da subárvore esquerda
Funcao ESQUERDA (A: arvb): arvb;
Inicio
ESQUERDA A^.esq;
Fim;
Retorno da subárvore direita
Funcao DIREITA (A: arvb): arvb;
Inicio
DIREITA A^.dir;
Fim;
14.3 Percursos em Árvores Binárias
Percurso é um modo sistemático de percorrer cada nó de uma árvore exatamente um
por vez;
Tipos de percursos:
Percursos em profundidade;
Percursos em largura.
• Percursos em Profundidade
Em-ordem;
Pré-ordem;
Pós-ordem.
• Percurso em Largura
Em-nível.
Percursos em Profundidade
Em-ordem: percorre recursivamente a subárvore esquerda, volta a raiz da árvore e,
por último percorre recursivamente a subárvore direita;
Pré-ordem: percorre a raiz da árvore e depois percorre recursivamente as
subárvores esquerda e direita, respectivamente;
Pós-ordem: percorre recursivamente as subárvoes esquerda e direita,
respectivamente, e por último, volta a raiz da árvore.
Podemos compreender melhor esses percursos quando comparamos o funcionamento
destes com as formas infixa, prefixa e posfixa de expressões aritméticas.
Forma infixa da expressão Aplica-se o percurso em-ordem
Exibir a subárvore esquerda; (a)
Exibir a raiz; (+)
Exibir a subárvore direita; (b)
Forma prefixa da expressão Aplica-se o percurso pre-ordem
Exibir a raiz; (+)
Exibir a subárvore esquerda; (a)
Exibir a subárvore direita; (b)
Forma posfixa da expressão Aplica-se o percurso pos-ordem
Exibir a subárvore esquerda; (a)
Exibir a subárvore direita; (b)
Exibir a raiz; (+)
Com o uso de recursão, podemos generalizar esse raciocínio e desenvolver a forma
posfixa da expressão abaixo, percorrendo, em pós-ordem, a subárvore esquerda (ab*), depois a
subárvore direita (cd/) e, finalmente, a raiz (+).
Expressão aritmética a*b + c/d
representada por uma árvore binária
+
*
a b
/
c d
+
a b
Expressão aritmética a + b
representada por uma árvore
binária
Ao final, teremos a expressão:
a b * c d / +
14.4 Algoritmos dos 3 tipos de percurso em profundidade
Retorno da subárvore esquerda
Funcao ESQUERDA (A: arvb): arvb;
Inicio
ESQUERDA A^.esq;
Fim;
Retorno da subárvore direita
Funcao DIREITA (A: arvb): arvb;
Inicio
DIREITA A^.dir;
Fim;
Procedimento EM_ORDEM (A: arvb);
Inicio
EM_ORDEM (ESQUERDA (A) );
exiba (RAIZ (A) );
EM_ORDEM (DIREITA (A) );
Fim;
Procedimento PRE_ORDEM (A: arvb);
Inicio
exiba (RAIZ (A) );
PRE_ORDEM (ESQUERDA (A) );
PRE_ORDEM (DIREITA (A) );
Fim;
Procedimento POS_ORDEM (A: arvb);
Inicio
POS_ORDEM (ESQUERDA (A) );
POS_ORDEM (DIREITA (A) );
exiba (RAIZ (A) );
Fim;
Percurso em Largura
Conhecido por percurso em-nível;
Percorre-se os nós de uma árvore por nível:
• de cima para baixo;
• da esquerda para a direita.
Exemplo: na expressão a*b + c/d, teremos:
Na implementação do percurso em largura (em-nível), inicia-se uma fila possuindo só o nó
da raiz da árvore. Em seguida, enquanto essa fila não ficar vazia, elimina-se dessa um nó, exibe-
se o seu conteúdo e armazena-se seus filhos de volta na mesma.
+ * / a b c d
+
*
a b
/
c d
Procedimento EM_NIVEL (A: arvb);
var
F: Fila;
Inicio
CRIA_FILA (F);
ENFILEIRA (A, F);
Enquanto (nao VAZIA (F) ) faca
A ELIMINA (F);
exiba (RAIZ (A) );
se nao VAZIA (ESQUERDA (A) )
entao ENFILEIRA (ESQUERDA (A), F );
se nao VAZIA (DIREITA (A) )
entao ENFILEIRA (DIREITA (A), F);
Fim Enquanto;
Fim;
14.5 Árvores de Busca Binária
Considere A como uma árvore binária na qual sua raiz contenha o item r.
Essa árvore A será uma Árvore de Busca Binária (ordenada), se e somente se:
Todo elemento armazenado na subárvore esquerda de A seja menor ou igual a r;
Todo elemento armazenado na subárvore direita de A seja maior que r;
Toda subárvore de A seja também uma árvore de busca binária.
Assim, os elementos de uma árvore de busca binária A estarão ordenados de acordo com
a propriedade de uma busca binária, ou seja, para todo elemento x Є A, quando um elemento y
está armazenado à esquerda de x, então y < x. Já quando y está armazenado à direita de x,
então y > x.
Uma observação importante acerca da propriedade de busca binária faz com que
cheguemos a conclusão de que ao projetar uma árvore de busca binária obteremos uma
sequência ordenada de todos os seus elementos.
Ordenada
c
b
a
f
e g
d
Não Ordenada
d
a
b c
f
e g
4 6 9
8
5
0 1 2 3 4 5 6 7 8 9
1
0 2
3 7
14.6 Implementação de Árvores de Busca Binária
O TAD para implementar árvores de busca binária é idêntico ao de árvores binárias;
Tipo
arvbb = ^bloco_no;
bloco_no = registro
esq: arvbb;
info: T;
dir: arvbb;
fim;
Esse fato denota que as propriedades da busca binária não são exclusivas dessa estrutura.
Portanto, suas características devem ser desenvolvidas por meio das operações que farão
uso dela.
Criação e teste de árvore vazia
Procedimento CRIA (var A: arvbb);
Inicio
A 0;
Fim;
Funcao VAZIA (A: arvbb): logico;
Inicio
VAZIA (A = 0);
Fim;
Acesso em árvore de busca binária
Funcao RAIZ (A: arvbb): T;
Inicio
se (A = 0)
entao exiba (“Árvore binária vazia!”);
fim se;
RAIZ A^.info;
Fim;
Funcao ESQUERDA (A: arvbb): arvbb;
Inicio
se (A = 0)
entao exiba (“Árvore binária vazia!”);
fim se;
ESQUERDA A^.esq;
Fim;
Funcao DIREITA (A: arvbb): arvbb;
Inicio
se (A = 0)
entao exiba (“Árvore binária vazia!”);
fim se;
ESQUERDA A^.dir;
Fim;
Inserção em árvore de busca binária
Funcao FOLHA (valor: T);
var
A: arvbb;
Inicio
ALOCA (A);
A^.esq 0;
A^.info valor;
A^.dir 0;
FOLHA A;
Fim;
Procedimento INSERE (valor: T; var A: arvbb);
Inicio
se ( VAZIA (A) )
entao A FOLHA (valor)
senao se ( valor <= RAIZ (A) )
entao INSERE (valor, A^.esq)
senao INSERE (valor, A^.dir);
fim se;
fim se;
Fim;
Remoção em árvore de busca binária
Funcao SUBSTITUI (var valor: T; var A: arvbb): arvbb;
Inicio
se (A^.dir = 0)
entao
SUBSTITUI A;
valor A^.info;
A A^.esq;
senao
SUBSTITUI SUBSTITUI (valor, A^.dir);
fim se;
Fim;
Procedimento REMOVE (valor: T; var A: arvbb);
var
n: arvbb;
Inicio
se ( VAZIA (A) )
entao Sair;
se ( valor = RAIZ (A) )
entao
n A;
se ( ESQUERDA (A) = 0 )
entao A A^.dir
senao se ( DIREITA (A) = 0 )
entao A A^.esq
senao n SUBSTITUI(A^.info, A^.esq);
fim se;
fim se;
DESALOCA (n);
senao
se ( valor <= RAIZ (A)
entao REMOVE (valor, A^.esq)
senao REMOVE (valor, A^.dir);
fim se;
fim se;
Fim;
Busca em árvore de busca binária
Funcao BUSCA (valor: T; A: arvbb): logico;
Inicio
se ( VAZIA (A) )
entao BUSCA falso
senao se ( valor = RAIZ (A) )
entao BUSCA verdadeiro
senao se ( valor < RAIZ (A) )
entao BUSCA BUSCA (valor, ESQUERDA (A) )
senao BUSCA BUSCA (valor, DIREITA (A) );
fim se;
fim se;
fim se;
Fim;
Algumas aplicações de árvores
Consultas de dados;
Avaliação de expressões aritméticas;
Geração de índices remissivos;
Manipulação de arquivos indexados;
Implementação de filas de prioridades.
BIBLIOGRAFIA*
FORBELLONE, A. L.; EBERSPACHER, H. F. Lógica de programação: a construção de
algoritmos e estruturas de dados. São Paulo: Prentice Hall, 2005.
GUIMARAES, A. e LAGES, N. Algoritmos e estruturas de dados. Rio de Janeiro: LTC, 1994.
MANZANO, J. A. Lógica estruturada para programação de computadores. São Paulo: Érica,
2001.
PEREIRA, Silvio do L. Estruturas de dados fundamentais: conceitos e aplicações. 12 ed. São
Paulo: Érica, 2008.
* Esta apostila foi elaborada com base nas obras relacionadas neste item.