Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_...

24
Apostila de COM146 Algoritmos e Estruturas de Dados I Disciplina do 1 o módulo do Curso de Ciência da Computação da Universidade Federal de Lavras Olinda Nogueira Paes Cardoso 1 Agosto de 2002 1 Professora Auxiliar do Departamento de Ciência da Computação da UFLA

Transcript of Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_...

Page 1: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

Apostila de COM146 Algoritmos e Estruturas de Dados I

Disciplina do 1o módulo do Curso de Ciência da Computação da Universidade Federal de Lavras

Olinda Nogueira Paes Cardoso1

Agosto de 2002

1 Professora Auxiliar do Departamento de Ciência da Computação da UFLA

Page 2: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

1

ÍNDICE

1. Introdução......................................................................................................................................... 2

1.1. Necessidade do uso da lógica ...................................................................................................... 2 1.2. Conceitos Básicos......................................................................................................................... 2 1.3. Conceito de Algoritmo................................................................................................................... 2

2. PORTUGOL...................................................................................................................................... 3 2.1. Definição de Variáveis – Tipos Básicos........................................................................................ 3 2.2. Comandos Básicos ....................................................................................................................... 4 2.3. Blocos e Comandos Básicos de Controle..................................................................................... 4 2.4. Outras estruturas de repetição...................................................................................................... 7 2.5. Definição de novos tipos de dados ............................................................................................... 8 2.6. Estruturas Condicionais Encadeadas ........................................................................................... 8

3. Ordenação ........................................................................................................................................ 9 3.1. Ordenação de Vetores .................................................................................................................. 9

3.1.1. Ordenação por inserção ........................................................................................................ 9 3.1.2. Ordenação por seleção........................................................................................................ 10

4. Matrizes .......................................................................................................................................... 11 4.1. Matrizes Bidimensionais ............................................................................................................. 11

5. Registros......................................................................................................................................... 12 5.1. Registro com Vetor ..................................................................................................................... 12 5.2. Conjuntos de Registros............................................................................................................... 13

6. Arquivos .......................................................................................................................................... 13 6.1. Organização de Arquivos............................................................................................................ 14

6.1.1. Organização Seqüencial...................................................................................................... 14 6.2. Organização Direta ..................................................................................................................... 15

7. Modularização ................................................................................................................................ 17 7.1. Benefícios da modularização...................................................................................................... 18 7.2. Ferramentas para modularização ............................................................................................... 18 7.3. Modos de transferência de parâmetros ...................................................................................... 19

8. Recursividade ................................................................................................................................. 19 8.1. Exemplo de problema recursivo ................................................................................................. 20 8.2. Recursão × Iteração.................................................................................................................... 20 8.3. Exemplos mais famosos de problemas recursivos..................................................................... 20

9. Apontadores ................................................................................................................................... 21 10. Listas Lineares................................................................................................................................ 22

10.1. Operações em listas lineares .................................................................................................. 22 10.2. Implementação de listas lineares ............................................................................................ 23

Page 3: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

2

1. INTRODUÇÃO

1.1. NECESSIDADE DO USO DA LÓGICA

A lógica é a ciência que estuda as leis e os critérios de validade que regem o pensamento e a demonstração, ou seja, ciência dos princípios formais do raciocínio. A lógica é usada no dia a dia das pessoas que trabalham com computação para solucionar problemas de forma eficiente.

A técnica mais importante no projeto da lógica de programas é chamada programação estruturada, a qual consiste em uma metodologia de projeto que objetiva:

• agilizar a codificação da escrita da programação; • facilitar a depuração da leitura da mesma; • permitir a verificação de possíveis falhas apresentadas pelos programas; • facilitar as alterações e atualizações dos programas.

1.2. CONCEITOS BÁSICOS

O conceito central da programação em ciência da computação é o de algoritmo. Segundo Wirth a programação estruturada é “a arte ou técnica de construir e formular algoritmos de uma forma sistemática”.

Programas são “formulações concretas de algoritmos abstratos, baseados em representações e estruturas específicas de dados”. Estruturas de dados são usadas no algoritmo para representar as informações do problema a ser resolvido.

Na programação deve-se distinguir claramente dois aspectos: Aspecto estático – a formulação de um algoritmo consiste em um texto contendo comandos (instruções) que devem ser executados numa ordem prescrita. Aspecto dinâmico – os efeitos que são causados pela execução do programa no tempo, dado um conjunto de valores iniciais. 1.3. CONCEITO DE ALGORITMO

Um algoritmo é uma norma executável para estabelecer um certo efeito desejado, que na prática será geralmente a obtenção de uma solução a um certo tipo de problema.

Exemplo de algoritmo para trocar uma lâmpada queimada: “pegar uma lâmpada nova no armário”; “pegar a escada na área de serviço”; “subir na escada com a lâmpada nova na mão”; “retirar a lâmpada queimada”; “colocar a lâmpada nova”; “descer a escada”; “testar se a lâmpada nova está funcionando”

O símbolo de seqüenciamento (;) tem duas funções: no texto a de separar um comando do

outro; e no evento a de indicar que os comandos separados devem ser executados na mesma seqüência em que aparecem no texto.

O símbolo ; representa a mais simples estrutura de controle, a seqüência simples. Exercício: escreva um algoritmo para se trocar um pneu furado. Assuma que estão disponíveis um macaco e um estepe em boas condições.

Seguindo com o exemplo da troca de lâmpadas, vamos supor que há a possibilidade de que a

escada disponível não seja alta suficiente para alcançar a lâmpada e que, neste caso, gostaríamos prever este possível erro. Poderíamos reescrever o algoritmo desta forma:

“pegar uma lâmpada nova no armário”; “pegar a escada na área de serviço”; “subir na escada com a lâmpada nova na mão”;

Page 4: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

3

se “for possível alcançar a lâmpada a ser trocada” então “retirar a lâmpada queimada”; “colocar a lâmpada nova”;

“descer da escada”; “guardar a escada”;

Outro caso: supondo que havia várias lâmpadas para serem trocadas na casa. Poderíamos reescrever o algoritmo desta forma:

“pegar todas as lâmpadas novas no armário”; “pegar a escada na área de serviço”; enquanto “existirem lâmpadas novas disponíveis” faça

“subir na escada com uma lâmpada nova na mão”; se “for possível alcançar a lâmpada a ser trocada” então

“retirar a lâmpada queimada”; “colocar a lâmpada nova”;

“descer da escada”; “guardar a escada”;

2. PORTUGOL

A partir de agora será introduzida uma linguagem de expressão de algoritmos, o PORTUGOL. Serão apresentadas a sintaxe e a semântica dos comandos da linguagem. PORTUGOL é uma pseudolinguagem de programação utilizada para obter uma notação para algoritmos, a ser usada na definição, na criação, no desenvolvimento e na documentação de um programa.

O objetivo não é criar mais uma linguagem de programação, por isso as regras não precisam ser seguidas de forma muito rígida. Considerando o PORTUGOL, a sintaxe é definida e a forma apresentada e aceita como padrão. Para cada declaração e/ou comando a semântica deve ser explicada.

O identificador é o elemento básico da linguagem, a sua sintaxe é definida pelo diagrama apresentado na Figura 1:

Figura 1: Diagrama que representa um identificador 2.1. DEFINIÇÃO DE VARIÁVEIS – TIPOS BÁSICOS

No PORTUGOL existem quatro tipos básicos, isto é, tipos básicos de dados que podem ser utilizados: INTEIRO, REAL, CARACTER e LÓGICO.

Uma variável pode ser entendida como um local onde se pode colocar qualquer valor do conjunto de valores possíveis do tipo básico associado. O nome da variável é o identificador tal como definido anteriormente. Por exemplo:

Toda variável deve ser declarada conforme a sintaxe apresentada na Figura 2. A semântica de uma declaração de variáveis corresponde à criação de locais na memória,

rotulada com o nome do identificador (variável) e marcada com o tipo de valores que ela pode conter.

SOMA Variável SOMA

letra letra

dígito

Page 5: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

4

Figura 2: Diagrama que representa a definição de uma variável. 2.2. COMANDOS BÁSICOS

O comando de ATRIBUIÇÃO é utilizado para atribuir um valor a uma variável. Para isso usa-se o símbolo ←, conforme a seguinte sintaxe:

A notação usada para expressões é basicamente uma forma linear comumente usada na matemática, que pode conter operadores:

ARITMÉTICOS: +, -, /, *, raiz( ), **, sen( ), cos( ), mod, div,... LÓGICOS: e, ou, não ( Λ, V, ) RELACIONAIS: =, ≠, >, ≥ (ou >=), <, ≤ (ou <=)

É importante observar que o resultado da expressão (do lado direito do comando de

atribuição) deve ser coerente com o tipo declarado para a variável (do lado esquerdo).

2.3. BLOCOS E COMANDOS BÁSICOS DE CONTROLE

Um bloco pode ser definido como um conjunto de comandos com uma função bem definida. Serve também para definir os limites onde as variáveis declaradas em seu interior são conhecidas. Exemplo: início

< declaração de variáveis > < comandos >

fim

Uma seqüência simples é um conjunto de comandos separados por ponto e vírgula (;), que

serão executadas numa seqüência linear de cima para baixo. Exemplo: comando1; comando2; comando3; ... comandoN;

Quando a ação a ser executada depender de uma inspeção (teste), teremos uma alternativa, ou estrutura condicional, simples ou composta.

inteiro identificador

real

caracter

lógico

;

,

:

identificador ←←←← expressão ;

Page 6: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

5

Exemplo de uma estrutura condicional simples:

se < condição > então

comando1; comando2;

... comandoN; fim se;

Exemplo de uma estrutura condicional composta: se < condição >

então comando1; comando2; ... comandoN;

senão comando1’; comando2’; ... comandoN’;

fim se;

Nos comandos apresentados, < condição > é qualquer expressão cujo resultado seja falso ou verdadeiro. Exercício: Qual será o valor final das variáveis A e B depois da execução do seguinte algoritmo?

início inteiro: A, B; A ← 1; B ← 2; se A > B então

A ← 5; senão

A ← 10; fim se;

fim;

Uma estrutura de repetição é quando um conjunto de ações é executado repetidamente enquanto uma determinada condição permanece válida (ou seja, quando o resultado de da expressão é um valor lógico verdadeiro).

Exemplo de uma estrutura de repetição: enquanto < condição > faça

comando1; comando2; ... comandoN; fim enquanto;

Enquanto o valor da < condição > for verdadeiro, as ações dos comandos são executadas e quando se tornar falso, o comando é abandonado. Se já da primeira vez o resultado é falso, os comandos não são executados.

Page 7: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

6

Exercício: Qual será o valor final das variáveis declaradas no seguinte algoritmo, depois de sua execução?

início inteiro: A, B, C, I; A ← 1; B ← 1; I ← 1; enquanto I < 10 faça

C ← A + B; A ← B; B ← C; I ← I + 1; fim enquanto;

fim;

Até agora todos os valores calculados pelos algoritmos foram gerados e permaneceram na memória. Para obter ou para fornecer dados ao ambiente exterior ao algoritmo, por exemplo do teclado e para o vídeo, é preciso utilizar comandos de entrada e saída. O comando de entrada é leia e o comando de saída é imprima, e suas sintaxes são apresentadas a seguir.

Exemplo de um algoritmo que usa comandos de entrada e saída: início

inteiro: A, B, SOMA; leia (A, B); SOMA ← A + B; imprima (“A soma entre ”, A, “ e ”, B, “ é ”, SOMA);

fim; Exercício: Escreva o algoritmo que realize a multiplicação de dois números inteiros, utilizando apenas o operador da soma (+).

Regras práticas para a construção de algoritmos legíveis:

• Procure incorporar comentários no algoritmo para descrever o significado das variáveis e ações utilizadas. Para isso use chaves {}.

• Escolha nomes de variáveis que sejam significativos, isto é, que traduzam o tipo de informação a ser armazenada na variável. Por exemplo: NOTA, SOMA, MÉDIA, etc.

• Grife as palavras-chave (escritas com letras minúsculas) do algoritmo, destacando as estruturas de controle.

• Procure alinhar os comandos de acordo com o nível a que pertencem, isto é, destaque a estrutura na qual estão contidos.

identificador imprima ; ( )

expressão

caracter

,

identificador leia ; ( )

,

Page 8: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

7

Exercícios de Fixação: Construa algoritmos para solucionar os seguintes problemas.

1. Como saber se um número é divisível por outro. 2. Calcular a média final de um aluno que realizou 4 avaliações, sabendo que todas têm o mesmo

peso. 3. Dados 3 números, verificar qual deles é o menor.

2.4. OUTRAS ESTRUTURAS DE REPETIÇÃO

Serão apresentadas a seguir outras duas estruturas de repetição que também podem ser utilizadas na construção de algoritmos usando o PORTUGOL, são elas: repita e para.

A estrutura de repetição repita difere da enquanto no que diz respeito ao momento em que o teste da condição é submetido.

repita comando1; comando2; ... comandoN; até < condição >;

Na estrutura enquanto o teste é realizado antes da execução do primeiro loop, ou seja, pode ser que os comandos não sejam realizados sequer uma vez, caso a condição seja falsa já na primeira vez em que foi testada. Já na repita os comandos sempre serão executados pelo menos uma vez, até que a condição seja testada, no final da estrutura.

A estrutura de repetição para difere das estruturas enquanto e repita, pois utiliza uma variável de controle, que atua como um contador de repetições.

para I de 1 até 10 passo 1 faça comando1; comando2; ... comandoN; fim para;

Observando o exemplo, percebe-se que foi utilizada uma variável do tipo inteiro (I), que deve

ter sido declarada anteriormente. Esta estrutura irá executar os comandos 10 vezes, pois possui I variando automaticamente de 1 em 1 (passo 1) até 10, ou seja, não é necessário fazer o incremento deste dentro da estrutura de repetição. Exercício: Avalie os algoritmos construídos até o presente momento e, quando achar viável, substitua a estrutura de repetição enquanto pela estrutura repita ou para, a fim de melhorar o seu desempenho.

O comando abandone só tem sentido dentro de um comando de repetição (enquanto, repita e para). Além disso, estará sempre associado ao teste de uma condição com comando se. Sintaxe: abandone;

A semântica do comando é a seguinte: quando o abandone é encontrado, o próximo comando a ser executado é o primeiro logo após o fim do comando de repetição mais interno onde este aparece. Exemplo: ... enquanto I > 0 faça I ← I + 1;

imprima (I); se I2 + 1 <= 150 então I ← I + 25; senão abandone;

Page 9: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

8

fim se; fim enquanto;

2.5. DEFINIÇÃO DE NOVOS TIPOS DE DADOS

Nem sempre os tipos básicos (inteiro, real, caracter e lógico) são suficientes para exprimir estruturas de dados em algoritmos. Daí a necessidade de novos tipos de dados serem criados. Um destes tipos é o vetor.

No PORTUGOL a criação de um vetor segue as especificações: tipo nome_do_tipo = vetor [li:ls] <tipo_básico>;

É dado um nome_do_tipo ao novo tipo de vetor criado, onde li é o limite inferior e ls é o limite superior de um intervalo que define o tamanho do vetor (valores inteiros), e tipo_básico é um dos tipos básicos já conhecidos. Esta especificação apenas indica um modelo para a criação de variáveis deste novo tipo. Para efetivar esta estrutura dentro do algoritmo, é necessário declará-la dando um nome a variável que será criada segundo o modelo especificado.

Exemplo: um vetor que armazena as notas de todos os alunos de uma turma com 25 alunos. tipo v = vetor [1:25] real;

v: NOTAS; O número de elementos de um vetor é dado por ls-li+1. Isto significa que as posições do vetor

são identificadas a partir de li, com incrementos unitários até ls. li li+1 li+2 … ls

Cada elemento de um vetor é tratado como se fosse uma variável simples. Para referência a um elemento do vetor utiliza-se o nome do vetor e a identificação do elemento (índice) entre colchetes ([ ]).

Por exemplo, se quisermos atribuir o valor de uma 45 a nota do 6o aluno (que é identificado pelo índice 6 do vetor de notas): NOTAS [6] ← 45;

Exemplo: O que será impresso no algoritmo abaixo? início

inteiro: I; tipo vc = vetor [1:7] caracter;

vc: DIAS; DIAS [1] ← “domingo”;

DIAS [2] ← “segunda-feira”; DIAS [3] ← “terça-feira”;

DIAS [4] ← “quarta-feira”; DIAS [5] ← “quinta-feira”;

DIAS [6] ← “sexta-feira”; DIAS [7] ← “sábado”;

para I de 1 até 7 passo 2 faça imprima (DIAS [I]);

fim para; fim.

Exercício: Um professor de uma turma com 30 alunos quer armazenar as notas de seus alunos em um vetor e depois calcular a média geral da turma. Escreva um algoritmo que solucione este problema usando uma estrutura de vetor. 2.6. ESTRUTURAS CONDICIONAIS ENCADEADAS

Existem casos em que é necessário se estabelecerem verificações de condições sucessivas, onde uma determinada ação poderá ser executada se um conjunto anterior de condições for satisfeito. Isto significa usar uma condição dentro de outra. Este tipo de estrutura pode possuir diversos níveis de condição, sendo chamada de aninhamento ou encadeamento de estruturas condicionais. Exemplo:

se <condição 1> então <comandos para condição 1 verdadeira> senão se <condição 2> então <comandos para a condição 1 falsa e 2 verdadeira> senão <comandos para a condição 1 e 2 falsas >

Page 10: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

9

fim se; {final do se mais interno} fim se; {final do primeiro se (mais externo)}

Exercício: Escreva um algoritmo que efetue o cálculo de reajuste de salário de um funcionário. Considere que o aumento será de 15% se o salário for até R$500,00, de 10% se for entre R$501,00 e R$1.000,00 e de 5% se for maior que R$1.000,00.

3. ORDENAÇÃO

A atividade de ordenação é o processo de rearranjo de um certo conjunto de objetos de acordo com um critério (ordem) específico. O objetivo da ordenação é facilitar a localização dos membros de um conjunto de dados. 3.1. ORDENAÇÃO DE VETORES

A preocupação mais importante a ser estabelecida em relação aos métodos de ordenação de vetores corresponde ao uso econômico da memória disponível. Isto implica que a permutação de elementos, responsável por levar o elemento à ordem desejada, deve ser efetuada in situ, e que, portanto, são de menor interesse os métodos que efetuam o transporte físico dos elementos de um vetor A para um vetor resultante B.

Restringindo-se a escolha dos métodos, dentre as inúmeras soluções possíveis, de acordo com o critério de economia de memória, pode-se promover uma primeira classificação de acordo com a eficiência do mesmo em relação à economia de tempo.

Uma boa medida de eficiência é obtida contando-se o número C de comparações necessárias e o número M de movimentos (transposições) dos elementos. Estes números são funções do número n de elementos a serem ordenados.

Os métodos de ordenação que ordenam os elementos in situ podem ser classificados em três principais categorias:

• Ordenação por inserção • Ordenação por seleção • Ordenação por troca

Estes três princípios serão examinados e comparados. Os exemplos operam sobre a variável A,

cujos componentes serão ordenados e se referem a um vetor de inteiros de tamanho variável (N), definido como se segue: inteiro: N; tipo vet = vetor [1:N] inteiro; vet: A; 3.1.1. ORDENAÇÃO POR INSERÇÃO

Em cada passo, iniciando-se com i=2 e incrementando-se i de uma em uma unidade, o i-ésimo elemento da seqüência vai sendo comparado com os elementos anteriores e, se for o caso, retirado e inserido na posição apropriada.

O processo de ordenação por inserção será mostrado em um exemplo, em que são ordenados oito números (N=8) escolhidos aleatoriamente. O algoritmo deve fazer o seguinte:

para I de 2 até N faça X ← A[I]; inserir X no local adequado em A[1]...A[I]

fim para Valores iniciais 44 55 12 42 94 18 06 67 i = 2 44 55 12 42 94 18 06 67 i = 3 12 44 55 42 94 18 06 67 i = 4 12 42 44 55 94 18 06 67 i = 5 12 42 44 55 94 18 06 67 i = 6 12 18 42 44 55 94 06 67

Page 11: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

10

i = 7 06 12 18 42 44 55 94 67 i = 8 06 12 18 42 44 55 67 94

Para encontrar o local apropriado do elemento observado é conveniente utilizar, de modo alternado, operações de comparação e de movimentação, examinando X, e comparando-o com o elemento A[J], e então efetuando ou a inserção de X ou a movimentação do elemento A[J], e prosseguindo-se para a esquerda no tratamento dos outros elementos.

Para isso será necessário testar duas condições distintas que causam o término deste processo de análise:

• Um elemento A[J] é encontrado com um elemento de valor menor do que o seu • A extremidade esquerda é atingida

É um caso típico de uma repetição com duas condições de término, que conduz a utilização de

um elemento sentinela (para armazenar temporariamente o valor de algum elemento que está sendo analisado). Para isso, será utilizada uma posição do vetor A como sentinela, o A[0] que receberá o valor de X.

início inteiro: I, J, N, X; tipo vet = vetor [0:N] inteiro; vet: A; {supondo que o vetor A tenha sido preenchido}...

para I de 2 até N faça X ← A[I]; A[0] ← X; J ← I; enquanto X < A[J-1] faça A[J] ← A[J-1]; J ← J-1; fim enquanto; A[J] ← X;

fim para; fim;

3.1.2. ORDENAÇÃO POR SELEÇÃO

Este método é baseado no seguinte princípio: • Selecionar o elemento que apresenta o menor valor • Trocá-lo com o primeiro elemento da seqüência A[1] • Repetir estas operações, envolvendo agora os N–1 elementos restantes, depois os N–2

elementos, ..., até restar um só elemento, o maior deles. Valores iniciais 44 55 12 42 94 18 06 67 06 55 12 42 94 18 44 67 06 12 55 42 94 18 44 67 06 12 18 42 94 55 44 67 06 12 18 42 94 55 44 67 06 12 18 42 44 55 94 67 06 12 18 42 44 55 94 67 06 12 18 42 44 55 67 94

início inteiro: I, J, N, K; tipo vet = vetor [1:N] inteiro;

vet: A; {supondo que o vetor A tenha sido preenchido}... para I de 1 até N–1 faça K ← I; X ← A[I]; para J de I+1 até N faça se A[J] < X então K ← J;

Page 12: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

11

X ← A[K]; fim se;

fim para;

A[K] ← A[I]; A[I] ← X; fim para;

fim;

4. MATRIZES

Uma matriz é uma estrutura de dados homogênea, ou seja, todos os elementos de uma matriz são do mesmo tipo. Um vetor é uma matriz unidimensional, a partir de agora serão apresentadas matrizes com mais de uma dimensão. 4.1. MATRIZES BIDIMENSIONAIS

A forma mais comum de trabalhar com matrizes é utilizando duas dimensões, apesar de que em alguns casos possa ser necessário trabalhar com mais de duas. Uma matriz bidimensional é composta por linhas e colunas. As linhas podem ser consideradas como a primeira dimensão e as colunas a segunda dimensão. É preciso definir o tamanho de cada uma dessas dimensões, ou seja, o número de linhas e o número de colunas que esta matriz deverá possuir. Exemplo: Definição de uma matriz com 8 linhas e 5 colunas.

tipo mat = matriz [1..8, 1..5] inteiro; mat: TABELA;

TABELA 1 2 3 4 5 1 2 3 4 5 6 7 8

Exemplo de algoritmo utilizando matriz com duas dimensões: Seja uma matriz a representação das notas obtidas pelos alunos em uma determinada disciplina. A quantidade de linhas deverá ser equivalente ao número de alunos, neste caso 25. Cada coluna deverá conter o valor de uma das avaliações de cada aluno, neste caso são 3 avaliações. O algoritmo deve preencher a matriz com as notas.

início inteiro: I, J;

tipo m = matriz [1..25, 1..3] real; m: NOTAS;

para J de 1 até 3 faça imprima (“Digite as notas referentes a prova”, J); para I de 1 até 25 faça leia (NOTAS [I, J]);

fim para; fim para;

fim.

Page 13: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

12

Exercício: Escreva um algoritmo que receba as notas referentes a três avaliações realizadas por 25 alunos, e as armazene numa matriz, juntamente com a média total obtida pelo aluno. Sabendo que: as duas primeiras avaliações têm peso de 35 cada uma e a terceira tem peso de 30 pontos. Além disso, para cada média total deve ser enviada uma mensagem informando se o aluno foi aprovado (>=50) ou reprovado (<50) e qual foi a porcentagem da turma aprovada.

5. REGISTROS

O registro é um conjunto de dados logicamente relacionados e é uma das principais estruturas de dados. Um registro consiste em trabalhar vários dados de tipos diferentes em uma mesma estrutura e por isso é considerado heterogêneo.

Para se declarar um registro segue-se a sintaxe: tipo <identificador> = registro

<lista de campos e seus tipos> fim registro;

Por exemplo, seja um registro constituído dos campos referentes aos dados de um aluno da universidade, tais como, número de matrícula, nome completo, idade, turma, período em que se encontra e média geral. O algoritmo para criar o registro e ler os dados referentes a um aluno poderia ser o seguinte:

início tipo reg_aluno = registro

caracter: MAT caracter: NOME

inteiro: IDADE caracter: TURMA

inteiro: PERIODO real: MEDIA

fim registro; reg_aluno: ALUNO;

leia (ALUNO.MAT); leia (ALUNO.NOME);

leia (ALUNO.IDADE); leia (ALUNO.TURMA);

leia (ALUNO.PERIODO); leia (ALUNO.MEDIA);

fim 5.1. REGISTRO COM VETOR

Um ou mais campos de um registro pode ser do tipo vetor. A construção do registro é feita da mesma forma, porém o vetor a ser utilizado em sua estrutura deve ser declarado anteriormente. Exemplo:

início tipo vet = vetor [1..6] real;

tipo reg_aluno = registro caracter: MAT

caracter: NOME inteiro: IDADE

caracter: TURMA inteiro: PERIODO vet: NOTAS fim registro;

reg_aluno: ALUNO; inteiro: I; imprima (“Digite a matrícula do aluno”);

leia (ALUNO.MAT); imprima (“Digite o nome do aluno”);

leia (ALUNO.NOME);

Page 14: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

13

imprima (“Digite a idade do aluno”); leia (ALUNO.IDADE); imprima (“Digite a turma do aluno”);

leia (ALUNO.TURMA); imprima (“Digite o período do aluno”);

leia (ALUNO.PERIODO); imprima (“Digite as notas do aluno”); para I de 1 até 6 faça

leia (ALUNO.NOTAS[I]); fim para; fim 5.2. CONJUNTOS DE REGISTROS

No conjunto de registros são armazenados dados de várias ocorrências de um determinado tipo de registro. Por exemplo, para armazenar os dados de diversos alunos:

início inteiro: I, J, N;

tipo vet = vetor [1..6] real; tipo reg = registro

caracter: MAT caracter: NOME inteiro: IDADE caracter: TURMA inteiro: PERIODO vet: NOTAS fim registro; tipo aluno = conjunto [1..N] reg;

aluno: ALUNOS; imprima (“Digite o número de alunos”); leia (N); para I de 1 até N faça

imprima (“Digite a matrícula do aluno”); leia (ALUNOS[I].MAT); imprima (“Digite o nome do aluno”);

leia (ALUNOS[I].NOME); imprima (“Digite a idade do aluno”); leia (ALUNOS[I].IDADE); imprima (“Digite a turma do aluno”);

leia (ALUNOS[I].TURMA); imprima (“Digite o período do aluno”);

leia (ALUNOS[I].PERIODO); imprima (“Digite as notas do aluno”); para J de 1 até 6 faça

leia (ALUNOS[I].NOTAS[J]); fim para;

fim para; fim

6. ARQUIVOS

Até o momento, todas as estruturas de dados estudadas ficaram armazenadas no ambiente do algoritmo, ou seja, tinham duração apenas enquanto o algoritmo estava sendo executado. O arquivo é uma alternativa para estrutura de dados que pode ser fisicamente alocado em outro meio de armazenamento, em disco, por exemplo.

Page 15: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

14

Um registro é a parte lógica de uma estrutura de dados, enquanto que um arquivo, constituído por um conjunto de um ou mais registros, é a parte física, e pode ser chamado de registro físico. 6.1. ORGANIZAÇÃO DE ARQUIVOS

As operações básicas que podem ser feitas em um arquivo através de um algoritmo são: obtenção de um registro do arquivo, inserção de um novo registro, modificação ou exclusão de um registro.

A disposição (organização) de registros no arquivo pode favorecer determinadas operações em detrimento de outras. Conhecendo a organização, o projetista de algoritmos pode escolher aquela que seja mais adequada à solução do seu problema em termos de eficácia e eficiência.

Basicamente, existem duas possibilidades de organização de arquivos: • Seqüencial – na qual os registros são obtidos ou inseridos no arquivo em ordem seqüencial; • Direta – em que o acesso do registro é feito de forma direta através do uso de um

identificador para o registro. O fato de o arquivo ser armazenado em uma memória secundária o torna independente de

qualquer algoritmo, ou seja, ele pode ser criado, consultado, processado e até mesmo removido por algoritmos distintos.

Sendo o arquivo uma estrutura fora do ambiente do algoritmo, para que este tenha acesso aos dados do arquivo são necessárias as operações de leitura e escrita de registros no arquivo.

No algoritmo, o arquivo deve ser declarado e aberto antes que o acesso possa ser feito. No final do algoritmo, ou quando houver necessidade, o arquivo deve ser fechado.

A declaração de um arquivo é feita através da especificação: arquivo <organização> de NOME-DO-REGISTRO: NOME;

Exemplo:

tipo reg_aluno = registro ... fim registro;

reg_aluno: ALUNO; arquivo seqüencial de ALUNO: ALUNOS;

A declaração do arquivo é a definição, para o algoritmo, do modelo e dos nomes que estarão associados à estrutura de dados. A associação deste modelo ao arquivo físico é feita no algoritmo com um comando de abertura:

abra NOME-DO-ARQUIVO <tipo de utilização>; onde o tipo de utilização pode ser para leitura, escrita ou ambos. Exemplos:

abra ALUNOS leitura; abra ALUNOS escrita; abra ALUNOS; Para se desfazer a associação entre o modelo e o arquivo físico, usa-se o comando de

fechamento: feche NOME-DO-ARQUIVO;

Os formatos para leitura e escrita de um arquivo são dependentes do seu tipo de organização. 6.1.1. ORGANIZAÇÃO SEQÜENCIAL

A principal característica da organização seqüencial é a de que os registros são armazenados contiguamente, isto é, um após o outro na ordem em que foram inseridos. A acesso aos registros do arquivo, tanto na leitura quanto na escrita, são feitos seqüencialmente, ou seja, a leitura de um registro só é possível após a leitura de todos os registros anteriores e a escrita de um registro só é feita após o último registro.

Page 16: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

15

O comando de leitura de um registro em um arquivo seqüencial é: leia NOME-DO-ARQUIVO . NOME-DO-REGISTRO;

E o comando de escrita similar é: escreva NOME-DO-ARQUIVO . NOME-DO-REGISTRO; Observação: Para escrever dados numa próxima posição do arquivo, deve-se incluir “próximo” ao comando de escrita. Por exemplo, escreva próximo ARQUIVO.REGISTRO; Observação: Existe uma variável lógica pré-definida para cada arquivo chamada FDA (Fim De Arquivo), que indica se um arquivo chegou ou não ao seu último arquivo. Exercício 1: Supondo-se a existência de um arquivo A composto por nomes, salários e número de horas trabalhadas por mês dos funcionários de uma empresa, faça um algoritmo que crie um novo arquivo B, com a mesma estrutura, porém contendo apenas os dados referentes aos funcionários que trabalharam mais de 400 horas / mês. Exercício 2: Crie um algoritmo para entrar com dados, a partir do teclado, de um novo funcionário no arquivo A. 6.2. ORGANIZAÇÃO DIRETA

A principal característica da organização direta é a facilidade de acesso a um registro desejado, pois, ao contrário da organização seqüencial, para acessar um determinado registro não é preciso percorrer todos os anteriores a ele, o acesso é feito diretamente. Este acesso direto é possível porque a posição do registro no espaço físico do arquivo é univocamente determinada a partir de um dos campos do registro, escolhido no momento de criação do arquivo direto como sua chave. Exemplo: Suponha-se um arquivo seqüencial contendo dados dos alunos, como na tabela abaixo: MATRICULA NOME TURMA PERIODO 9800012 Maria Araújo A 3 9900001 Joaquim Silva A 2 9800002 Carlos Menezes B 3 9900010 Fátima Andrade C 1 9900025 Ana Lúcia Dias B 1 9800005 Marcelo Costa C 3 9900003 Flávio Martins C 2 9800040 Luiz Carvalho A 2

Um algoritmo para encontrar os dados do aluno e imprimir seu nome, cuja matrícula seja 9900003, seria o seguinte:

início tipo regaluno = registro

caracter: MATRICULA caracter: NOME caracter: TURMA inteiro: PERIODO fim registro;

regaluno: DADOS; arquivo seqüencial de DADOS: ALUNOS;

abra ALUNOS leitura; repita

leia ALUNOS.DADOS; se DADOS.MATRICULA = “9900003” então imprima (DADOS.NOME); abandone; fim se;

até ALUNOS.FDA; feche ALUNOS;

fim.

Page 17: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

16

Se a organização for direta, a disposição dos registros no arquivo não será necessariamente a apresentada no arquivo seqüencial.

Através de funções internas ao computador, cada registro será alocado em uma posição univocamente determinada pela chave escolhida, como mostra a tabela abaixo: {função que associa chave ao registro} MATRICULA NOME TURMA PERIODO 9800002 Maria Araújo A 3 9800005 Joaquim Silva A 2 9800012 Carlos Menezes B 3 9800040 Fátima Andrade C 1 9900001 Ana Lúcia Dias B 1 9900003 Marcelo Costa C 3 9900010 Flávio Martins C 2 9900025 Luiz Carvalho A 2

Para se ter acesso a um registro, basta efetuar-se a leitura no arquivo usando a chave, no caso o número da matrícula, desejada. Não há necessidade do algoritmo fazer nenhum tipo de pesquisa.

O mecanismo de gerência do arquivo direto no computador é capaz de associar a chave ao registro procurado. Caso a chave não exista, uma condição de chave inválida (INV) poderá ser testada. A escolha da chave é feita pelo usuário no momento da criação do arquivo de organização direta e, em geral, é um dos campos do registro. Observações importantes:

• Cada registro deverá ser gravado usando sua chave. • Não pode haver registros usando a mesma chave (é única).

As operações de leitura e escrita num arquivo de organização direta são indicadas nos

algoritmos pelos seguintes comandos: leia item [chave] NOME-ARQUIVO.NOME-REGISTRO; e escreva item [chave] NOME-ARQUIVO.NOME-REGISTRO;

O algoritmo desenvolvido anteriormente para encontrar os dados do aluno e imprimir seu

nome, cuja matrícula seja 9900003, seria: início

tipo regaluno = registro caracter: MATRICULA caracter: NOME caracter: TURMA inteiro: PERIODO fim registro;

regaluno: DADOS; arquivo direto de DADOS chave MATRICULA: ALUNOS;

abra ALUNOS leitura; leia item [9900003]ALUNOS.DADOS; se ALUNOS.INV {erro se a chave não for encontrada}

então imprima (“Aluno não existe”); senão imprima (DADOS.NOME);

fim se; feche ALUNOS; fim.

Page 18: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

17

Exercícios: 1. Escreva um algoritmo que abra um arquivo VENDAS contendo os seguintes campos: código da peça, quantidade vendida, valor unitário, cliente e data. E crie dois outros arquivos da seguinte forma:

• Contendo os nomes dos clientes que compraram mais de R$500,00 (valor total da compra). • Contendo os códigos das peças que venderam mais de 100 unidades para um cliente, no mês

de junho de 2002. 2. No mesmo arquivo da questão anterior, faça a busca de uma determinada peça por seu código. Implemente com os dois tipos de organização de arquivos e discuta os problemas relacionados a estas diferentes soluções.

7. MODULARIZAÇÃO

No fim da década de 60, alguns problemas no desenvolvimento de sistemas de programação levaram os países desenvolvidos a um evento chamado “crise de software”. Os custos das atividades de programação mostravam a cada ano uma clara tendência a se elevarem muito em relação aos custos dos equipamentos, e isto era devido ao avanço tecnológico na fabricação dos equipamentos de computação e a lenta evolução de técnicas aplicadas ao desenvolvimento de software.

A ausência de uma metodologia para a construção de programas conduzia a programas geralmente cheios de erros e com altos custos de desenvolvimento que, conseqüentemente, exigiam custos elevados para a sua correção e manutenção futuras. A programação estruturada foi o resultado de uma série de estudos e propostas de metodologias para desenvolvimento de software. Uma das técnicas aplicadas na programação estruturada, a modularização de programas é uma ferramenta para a elaboração de programas visando, os aspectos de confiabilidade, legibilidade, manutenibilidade e flexibilidade, dentre outros.

A modularização é um processo que aborda os aspectos da decomposição de algoritmos em módulos. Módulo é um grupo de comandos, constituindo um trecho do algoritmo, com uma função bem definida e o mais independente possível em relação ao resto do algoritmo. Exemplo – Seja um algoritmo para calcular o salário líquido de um empregado, com as seguintes etapas:

início Leia os dados do empregado Determine o salário Escreva o salário

fim. Onde “Determine o salário” pode ser refinado como:

Calcule as vantagens Calcule as deduções SALARIOLIQ ← VANTAGENS – DEDUÇÕES

No refinamento anterior não houve preocupação de como o processo de cálculo das

vantagens e deduções seria efetuado. Essas ações constituem funções bem definidas e que serão executadas por módulos específicos, neste caso, o algoritmo anterior ficaria:

início Leia os dados do empregado

Ative o módulo “Cálculo das vantagens” Ative o módulo “Cálculo das deduções” SALARIOLIQ ← VANTAGENS – DEDUÇÕES

Escreva o salário fim.

Exemplo da descrição estrutural da modularização:

Módulo Principal

Módulo Vantagens Módulo Deduções

Page 19: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

18

A maneira mais intuitiva de proceder a modularização de problemas é feita definindo um

módulo principal de controle e módulos específicos para as funções do algoritmo. Recomenda-se que os módulos de um programa tenham um tamanho limitado, pois módulos muito grandes são difíceis de ser compreendidos e, em geral, são multifuncionais.

As linguagens de programação dispõem de recursos que facilitam a construção e manipulação de módulos, permitindo não só a modularização dos comandos do programa, como também dos dados utilizados.

Cada módulo pode definir as próprias estruturas de dados, suficientes e necessárias apenas para atingir o objetivo final do módulo. Todo módulo é constituído por uma seqüência de comandos que operam sobre um conjunto de objetos, que podem ser globais ou locais.

Objetos globais são entidades que podem ser usadas em módulos internos a outro módulo do algoritmo onde foram declaradas.

Objetos locais são entidades que só podem ser usadas no módulo do algoritmo onde foram declaradas. Estes objetos não possuem nenhum significado fora deste módulo.

São exemplos de objetos globais ou locais: variáveis, arquivos, outros módulos, etc. A comunicação entre módulos deverá ser feita através de vínculos, utilizando-se objetos

globais ou transferência de parâmetros. 7.1. BENEFÍCIOS DA MODULARIZAÇÃO

A independência do módulo permite uma manutenção mais simples e evita efeitos colaterais no restante do algoritmo;

• A elaboração do módulo pode ser feita independentemente e em época diferente do restante do algoritmo;

• Testes e correções dos módulos podem ser feitos separados; • Um módulo pode ser utilizado em outros algoritmos que requeiram o mesmo processamento

por ele executado.

7.2. FERRAMENTAS PARA MODULARIZAÇÃO

Sub-rotinas e funções são módulos que servem aos objetivos: • Evitar que em certa seqüência de comandos necessária em vários locais de um algoritmo

tenha que ser escrita repetidamente nesses locais; • Dividir e estruturar um algoritmo em partes fechadas e logicamente coerentes; • Aumentar a legibilidade de um algoritmo.

Sub-rotinas e funções são módulos hierarquicamente subordinados a um algoritmo,

comumente chamado de módulo principal. Da mesma forma uma sub-rotina ou uma função pode conter outras sub-rotinas e funções aninhadas.

A sub-rotina e a função são criadas através das suas declarações em um algoritmo e para serem executadas, necessitam de ativação por um comando de chamada. A declaração de uma sub-rotina ou função é constituída de um cabeçalho, que a identifica e contém seu nome e uma lista de parâmetros formais, e de um corpo que contém declarações locais e os comandos. Criação de sub-rotina

subrotina NOME (lista-de-parâmetros-formais) declarações dos objetos locais a sub-rotina comandos da sub-rotina

fim subrotina; Chamada da sub-rotina NOME (lista-de-parâmetros-atuais);

As funções têm a característica de retornar ao algoritmo que as chamou um valor associado

ao nome da função.

Page 20: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

19

Criação de função

função tipo NOME (lista-de-parâmetros-formais) declarações dos objetos locais a função

comandos da função fim função;

Chamada da função NOME (lista-de-parâmetros-atuais);

Como esta função irá retornar um valor, este pode ser atribuído a alguma variável, contanto que esta seja de tipo compatível.

A ← NOME (lista-de-parâmetros-atuais);

Ao terminar a execução dos comandos de uma sub-rotina ou função, o fluxo de controle retorna ao comando seguinte àquele que provocou a chamada. 7.3. MODOS DE TRANSFERÊNCIA DE PARÂMETROS

Os parâmetros de uma sub-rotina ou função classificam-se em: de entrada – são aqueles que têm seus valores estabelecidos fora da sub-rotina ou função e não podem ser modificados dentro dela. de saída – são aqueles que têm seus valores estabelecidos dentro da sub-rotina ou função. de entrada-saída – são aqueles que têm seus valores estabelecidos fora da sub-rotina ou função, mas podem ter seus valores alterados dentro dela.

A vinculação entre módulos pode ser feita através da transferência ou passagem de parâmetros, que associam parâmetros atuais com parâmetros formais. Dentre os modos de transferência de parâmetros, pode-se destacar: a passagem por valor, a passagem por resultado e a passagem por referência.

Na passagem de parâmetros por valor, as alterações feitas nos parâmetros formais, dentro da sub-rotina ou função, não se refletem nos parâmetros atuais. O valor do parâmetro atual é copiado no parâmetro formal, na chamada da sub-rotina ou função. Assim, quando a passagem é por valor significa que o parâmetro é de entrada.

Na passagem de parâmetros por resultado, as alterações feitas nos parâmetros formais, na sub-rotina ou função, refletem-se nos parâmetros atuais. O valor do parâmetro formal é copiado no parâmetro atual, ao retornar da sub-rotina ou função. Assim, quando a passagem é por resultado significa que o parâmetro é de saída.

Na passagem de parâmetros por referência, a toda alteração feita num parâmetro formal corresponde a mesma alteração feita no seu parâmetro atual associado. Neste caso, quando a passagem é por valor significa que o parâmetro é de entrada-saída.

8. RECURSIVIDADE

Um objeto é dito recursivo se ele consistir parcialmente ou for definido em termos de si próprio.

Page 21: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

20

Uma função é recursiva quando no corpo dessa função existe uma chamada a si própria,

podendo utilizar os mesmos parâmetros de entrada (correndo riscos de provocar um ciclo infinito) ou outros.

8.1. EXEMPLO DE PROBLEMA RECURSIVO

Imagine que temos um monte de pregos e queremos saber quantos são. Se pegarmos num

prego, sabemos que temos um prego, mas não sabemos quantos ainda existem no monte restante... efetuamos a mesma operação (recursividade) e somamos o prego ao que já temos.

Fazemos o mesmo até não existir mais pregos para contar, isto é, pegamos num e somamos aos que temos, repetimos a mesma operação perguntando sempre entre as operações, "ainda há mais pregos para contar?", caso haja, repetimos, caso contrário paramos.

A recursividade é uma ferramenta muita poderosa quando bem implementada, senão pode ser muita perigosa. É preciso ter cuidado com as condições de parada, se faltar alguma condição de parada ou alguma condição de parada está errada pode acontecer um ciclo infinito. 8.2. RECURSÃO ×××× ITERAÇÃO

Paradigma iterativo: uma seqüência de instruções é executada de uma forma repetitiva, controlada por uma dada condição (ciclo iterativo).

Paradigma recursivo: • existência de casos simples, em que a resposta é determinada diretamente; • ser possível uma decomposição recursiva de uma instância do problema, em instâncias

mais simples da mesma forma.

Numa função recursiva, são criadas várias ativações dela própria que desaparecem à medida que a execução avança. Em cada momento apenas uma das ativações está ativa, estando as restantes à espera que essa termine para continuarem.

Os dois paradigmas são equivalentes: dada uma função recursiva existe sempre uma iterativa e vice-versa.

8.3. EXEMPLOS MAIS FAMOSOS DE PROBLEMAS RECURSIVOS Fatorial: Cálculo de n! = n x (n - 1) x...x 1 Seqüência de Fibonacci: 0 1 1 2 3 5 8 13 21 34 55 89 144 Exemplo de problema: Considerar uma população de coelhos que se reproduz segundo as seguintes regras: Cada par de coelhos produz um novo par por mês Os coelhos são férteis a partir do segundo mês Os coelhos não morrem Supondo que nasce um par de coelhos em Janeiro, quantos pares de coelhos existem no fim do ano? Algoritmo Determinar o número de pares em cada mês: 0 1 2 3 4 5 6 7 8 9 10 11 12 0 1 1 2 3 5 8 13 21 34 55 89 144 Generalizando, ao fim de n > 1 etapas temos: fn = fn - 1+fn - 2 e f0=0 e f1=1.

Page 22: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

21

O Puzzle das Torres de Hanói: (inventado por Eduard Lucas (1880)) São dados três suportes (a, b e c) e n discos de tamanhos diferentes. Os discos estão empilhados num dos suportes por ordem crescente de tamanhos. Pretende-se mover os discos para outro suporte de modo que: em cada passo exatamente um disco seja movido de um suporte para o outro um disco não pode nunca estar por cima de um menor o terceiro suporte pode ser usado como auxiliar

9. APONTADORES

É na memória RAM que são carregados os nossos programas e também onde são armazenadas as variáveis que fazem parte dos programas. A memória RAM pode ser vista como um enorme vetor de Bytes consecutivos, cada um ocupando uma posição bem determinada, que é identificada por um número único que varia entre 0 e a totalidade de Bytes.

Para os programadores, é muito mais simples referenciar uma variável pelo seu nome do que referenciá-la pela posição que essa variável ocupa em memória. O compilador associa a cada nome de variável uma posição única em memória, capaz de suportar os dados do tipo dessa variável. Sempre que num programa se faz referência a uma variável, na realidade é o endereço ou conjunto de endereços que essa variável ocupa, que está sendo referenciado.

O apontador é um mecanismo particularmente flexível de manipulação de dados, pois permite manipular diretamente dados contidos em endereços específicos de memória. Supondo que exista um apontador denominado ptr, que como qualquer variável ocupa uma posição em memória. Como ptr é um apontador, deverá conter o endereço de memória de outra variável (notar que o endereço de uma variável não é mais do que o número da casa que ocupa em memória). A Figura 3 mostra este exemplo.

Figura 3: Exemplo de apontador.

Page 23: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

22

10. LISTAS LINEARES

Uma lista linear é uma estrutura dinâmica caracterizada por uma seqüência ordenada de

elementos, no sentido da sua posição relativa: E1, E2, ..., En, onde: • Existem n elementos na seqüência; • E1 é o primeiro elemento da seqüência; • En é o último elemento da seqüência; • Para todo i, j entre 1 e n, se i < j, então o elemento Ei antecede o elemento Ej; • Caso i = j – 1, Ei é o antecessor de Ej e Ej é o sucessor de Ei.

Exemplos de listas lineares:

1) Fila de clientes de um banco, onde existem o primeiro e o último da fila, e uma “ordem” de atendimento.

2) Pilha de processos de uma repartição a serem atendidos.

10.1. OPERAÇÕES EM LISTAS LINEARES Criar – uma estrutura dinâmica será criada durante a execução do programa; Destruir – depois de ser utilizada, a estrutura deve ser destruída; Percorrer – todos os elementos da lista podem ser utilizados, sendo que para isso a lista tem que ser percorrida; Buscar – um determinado elemento da lista pode ser identificado ou por sua posição, ou por seu conteúdo; Inserir – um novo elemento é colocado na lista numa determinada posição e n aumenta em 1; Remover – um elemento é retirado da lista numa determinada posição e n diminui em 1.

Os diferentes tipos de listas lineares, possuem características especiais com relação a forma como são manipuladas.

Filas - Uma fila (queue) é uma lista linear onde as operações de inserção são efetuadas apenas no final e as operações de retirada apenas no início, ou seja:

• A inserção de um novo elemento X o torna o último da fila; • A retirada é sempre efetuada sobre o elemento E1.

Devido às características das operações da fila, o primeiro elemento a ser inserido será o

primeiro a ser retirado. Estruturas deste tipo são chamadas de FIFO (First In, First Out). Pilhas - Uma pilha (stack) é uma lista linear onde tanto a operação de inserção, quanto a de retirada são efetuadas no final, ou seja:

• A inserção de um novo elemento X o torna o último da pilha; • A retirada é sempre efetuada sobre o elemento En.

X P T O R

P

T

O

R

Page 24: Apostila de COM146 Algoritmos e Estruturas de Dados Ivaldick.com/files/Estrutura_de_ Dados_e_Algoritmos_Lavras.pdf · se “for possível alcançar a lâmpada a ser trocada” então

23

Devido às características das operações da pilha, o primeiro elemento a ser inserido será o último a ser retirado e o último a ser inserido será o primeiro a ser retirado. Estruturas deste tipo são chamadas de LIFO (Last In, First Out). 10.2. IMPLEMENTAÇÃO DE LISTAS LINEARES Alternativas:

• Contigüidade física (com o uso de vetores) • Encadeada (com o uso de apontadores)

Contigüidade Fila: 1 2 3 4 5 6 7 8 9 10 R O T P X

início fim Pilha:

6 topo 5 X 4 P 3 T 2 O 1 R

Encadeamento Simples

Exercícios: 1) Considere um conjunto de informações relativas a alunos, constituído de nome, número de matrícula e data de nascimento. Organize estas informações em uma lista encadeada, ordenada pelo nome do aluno. Escreva funções que efetuem as seguintes ações:

• imprimir os nomes e números de matrícula dos alunos que nasceram após uma determinada data (passada como parâmetro);

• procurar as informações relativas a um determinado aluno, cujo número de matrícula é passado como parâmetro;

• incluir um novo aluno na lista, respeitando a ordenação. 2) Construa um procedimento que recebe uma lista encadeada (endereço inicial no apontador Lista) e monta uma nova lista a partir dos dados desta, com os elementos em ordem inversa. Somente a lista final deve estar alocada ao final da execução do procedimento. 3) Escreva um procedimento que recebe duas filas, que contém valores numéricos ordenados. O procedimento deverá formar uma terceira fila, também ordenada, na qual estarão os valores armazenados nas filas originais. Considere duas possibilidades: as filas implementadas sobre arranjos, e as filas implementadas através de apontadores.

P R O T X