Sunteți pe pagina 1din 48

Prof: Jos Matias Pedro

MATERIAL DE APOIO A DISCIPLINA DE


ALGORITMO E ESTRUTURA DE DADOS

1
Prof: Jos Matias Pedro

1. Introduo
O curso de Estruturas de Dados discute diversas tcnicas de programao,
apresentando as estruturas de dados bsicas utilizadas no desenvolvimento de
software. O curso tambm introduz os conceitos bsicos da linguagem de
programao C, que utilizada para a implementao das estruturas de dados
apresentadas.
A linguagem de programao C tem sido amplamente utilizada na elaborao de
programas e sistemas nas diversas reas em que a informtica atua, e seu
aprendizado tornou-se indispensvel tanto para programadores profissionais como
para programadores que atuam na rea de pesquisa.
O conhecimento de linguagens de programao por si s no capacita
programadores necessrio saber us-las de maneira eficiente.
O projeto de um programa engloba a fase de identificao das propriedades dos
dados e caractersticas funcionais. Uma representao adequada dos dados, tendo
em vista as funcionalidades que devem ser atendidas, constitui uma etapa
fundamental para a obteno de programas eficientes e confiveis.
A linguagem C, assim como as linguagens Fortran e Pascal, so ditas linguagens
convencionais, projetadas a partir dos elementos fundamentais da arquitetura de
von Neuman, que serve como base para praticamente todos os computadores em
uso. Para programar em uma linguagem convencional, precisamos de alguma
maneira especificar as reas de memria em que os dados com que queremos
trabalhar esto armazenados e, freqentemente, considerar os endereos de
memria em que os dados se situam, o que faz com que o processo de programao
envolva detalhes adicionais, que podem ser ignorados quando se programa em uma
linguagem como Scheme. Em compensao, temos um maior controle da mquina
quando utilizamos uma linguagem convencional, e podemos fazer programas
melhores, ou seja, menores e mais rpidos.
A linguagem C prov as construes fundamentais de fluxo de controle necessrias
para programas bem estruturados: agrupamentos de comandos; tomadas de deciso
(if-else); laos com testes de encerramento no incio (while, for) ou no fim (do-while); e
seleo de um dentre um conjunto de possveis casos (switch). C oferece ainda
acesso a apontadores e a habilidade de fazer aritmtica com endereos. Por outro
lado, a linguagem C no prov operaes para manipular diretamente objetos
compostos, tais como cadeias de caracteres, nem facilidades de entrada e sada: no
h comandos READ e WRITE. Todos esses mecanismos devem ser fornecidos por
funes explicitamente chamadas. Embora a falta de algumas dessas facilidades
possa parecer uma deficincia grave (deve-se, por exemplo, chamar uma funo para
comparar duas cadeias de caracteres), a manuteno da linguagem em termos
modestos tem trazido benefcios reais. C uma linguagem relativamente pequena e,
no entanto, tornou-se altamente poderosa e eficiente.

2
Prof: Jos Matias Pedro

Armazenamento de dados e programas na memria


A memria do computador dividida em unidades de armazenamento chamadas bytes. Cada byte
composto por 8 bits, que podem armazenar os valores zero ou um. Nada alm de zeros e uns pode
ser armazenado na memria do computador. Por esta razo, todas as informaes (programas,
textos, imagens, etc.) so armazenadas usando uma codificao numrica na forma binria. Na
representao binria, os nmeros so representados por uma seqncia de zeros e uns (no nosso
dia a dia, usamos a representao decimal, uma vez que trabalhamos com 10 algarismos). Por
exemplo, o nmero decimal 5 representado por 101, pois 1*22 + 0*21 + 1*20 igual a 5 (da
mesma forma que, na base decimal, 456=4*102 + 5*101 + 6*100). Cada posio da memria (byte)
tem um endereo nico. No possvel enderear diretamente um bit. Se s podemos armazenar
nmeros na memria do computador, como fazemos para armazenar um texto (um documento ou
uma mensagem)? Para ser possvel armazenar uma seqncia de caracteres, que representa o texto,
atribui-se a cada caractere um cdigo numrico (por exemplo, pode-se associar ao caractere 'A' o
cdigo 65, ao caractere 'B' o cdigo 66, e assim por diante). Se todos os caracteres tiverem cdigos
associados (inclusive os caracteres de pontuao e de formatao), podemos armazenar um texto
na memria do computador como uma seqncia de cdigos numricos.
Um computador s pode executar programas em linguagens de mquina. Cada programa
executvel uma seqncia de instrues que o processador central interpreta, executando as
operaes correspondentes. Esta seqncia de instrues tambm representada como uma
seqncia de cdigos numricos. Os programas ficam armazenados em disco e, para serem
executados pelo computador, devem ser carregados (transferidos) para a memria principal. Uma
vez na memria, o computador executa a seqncia de operaes correspondente.

Interpretao versus Compilao


Uma diferena importante entre as linguagens C e Scheme que, via de regra, elas so
implementadas de forma bastante diferente. Normalmente, Scheme interpretada e C compilada.
Para entender a diferena entre essas duas formas de implementao, necessrio lembrar que os
computadores s executam realmente programas em sua linguagem de mquina, que especfica
para cada modelo (ou famlia de modelos) de computador. Ou seja, em qualquer computador,
programas em C ou em Scheme no podem ser executados em sua forma original; apenas
programas na linguagem de mquina ( qual vamos nos referir como M) podem ser efetivamente
executados.
No caso da interpretao de Scheme, um programa interpretador (IM), escrito em M, l o programa
PS escrito em Scheme e simula cada uma de suas instrues, modificando os dados do programa da
forma apropriada. No caso da compilao da linguagem C, um programa compilador (CM), escrito
em M, l o programa PC, escrito em C, e traduz cada uma de suas instrues para M, escrevendo
um programa PM cujo efeito o desejado. Como conseqncia deste processo, PM, por ser um
programa escrito em M, pode ser executado em qualquer mquina com a mesma linguagem de
mquina M, mesmo que esta mquina no possua um compilador.
Na prtica, o programa fonte e o programa objeto so armazenados em arquivos em disco, aos
quais nos referimos como arquivo fonte e arquivo objeto.

Execuo de um programa com linguagem Interpretada.

PS IM
Programa Fonte Interpretador Saida

Dados de Entrada

3
Prof: Jos Matias Pedro

Execuo de um programa com linguagem Compilada.

PC CM PM

Programa Fonte Compilador Programa Objecto

Execuo
PM Saida
Dados de Entrada
Programa Objecto
Devemos notar que, na 1 Figura ( Execuo de programa com linguagem Interpretada), o
programa fonte um dado de entrada a mais para o interpretador. No caso da
compilao, na 2 Figura (Execuo de um programa com linguagem compilada), identificamos
duas fases: na primeira, o programa objeto a sada do programa compilador e, na
segunda, o programa objeto executado, recebendo os dados de entrada e gerando
a sada correspondente.

1.1. Lgica de Programao

O que Lgica?
a arte de pensar corretamente e, visto que a forma mais complexa do pensamento
o raciocnio, a Lgica estuda ou tem em vista a correo do raciocnio.
Podemos ainda dizer que a lgica tem em vista a ordem da razo. Isto d a entender
que a nossa razo pode funcionar desordenadamente, pode pr as coisas de pernas
para o ar. Por isso a Lgica ensina a colocar Ordem no Pensamento.
Exemplo:
a) Todo o mamfero animal.
Todo cavalo mamfero.
Portanto, todo cavalo animal.
b) Todo mamfero bebe leite.
O homem bebe leite.
Portanto, todo homem mamfero e
animal.

O que Algoritmo?
Algoritmo uma seqncia de passos que visam atingir um objetivo bem definido. Ou
ainda podemos definir, um algoritmo como uma sequncia extremamente precisa de
instrues que, quando lida e executada por uma outra pessoa, produz o resultado
esperado, isto , a soluo de um problema. Esta sequncia de instrues nada
mais nada menos que um registro escrito da sequncia de passos necessarios que
devem ser executados para manipular informaes, ou dados, para se chegar na
resposta do problema.
Em geral um algoritmo destina-se a resolver um problema: fixa um padro de
comportamento a ser seguido, uma norma de execuo a ser trilhada, com o objetivo
de alcanar a soluo de um problema.

Uma receita de bolo de chocolate um bom exemplo de um algoritmo.


Bata em uma batedeira a manteiga e o aucar. Junte as gemas uma a uma

4
Prof: Jos Matias Pedro

at obter um creme homogneo. Adicione o leite aos poucos. Desligue a batedeira e adicione
a farinha de trigo, o chocolate em p, o fermento e reserve. Bata as claras em neve e junte-
as massa de chocolate misturando delicadamente. Unte uma forma retangular pequena
com manteiga e farinha e leve para assar em forno mdio pr-aquecido por aproximadamente
30 minutos. Desenforme o bolo ainda quente e reserve.
Este um bom exemplo de algoritmo pois podemos extrair caracteristicas bastante
interessantes do texto. Em primeiro lugar, a pessoa que escreveu a receita no
necessariamente a mesma pessoa que vai fazer o bolo. Logo, podemos estabelecer,
sem prejuzo, que foi escrita por um mas ser executada por outro.
Outras caractersticas interessantes que esto implicitas so as seguintes:
_ as frases so instrues no modo imperativo: bata isso, unte aquilo. So ordens,
no sugestes. Quem segue uma receita obedece quem a escreveu;
_ as instrues esto na forma sequencial: apenas uma pessoa executa. No existem
aes simultneas.
_ existe uma ordem para se executar as instrues: primeiro bata a manteiga e
o aucar; depois junte as gemas, uma a uma, at acabar os ovos; em seguida
adicione o leite.
_ algumas instrues no so executadas imediatamente, preciso entrar em um
modo de repetio de um conjunto de outras instrues: enquanto houver ovos no
usados, junte mais uma gema. S pare quando tiver usado todos os ovos.
_ algumas outras instrues no foram mencionadas, mas so obviamente necess
arias que ocorram: preciso separar as gemas das claras antes de comear a tarefa
de se fazer o bolo, assim como preciso ainda antes quebrar os ovos.
_ algumas instrues, ou conjunto de instrues, podem ter a ordem invertida: pode-
se fazer primeiro a massa e depois a cobertura, ou vice-e-versa. Mas nunca
se pode colocar no forno a assadeira antes de se chegar ao trmino do preparo
da massa.

Algoritmo para fazer um bolo de chocolate.


incio
Providencie todos os ingredientes da receita.
Providencie uma forma pequena.
Ligue o forno em temperatura mdia.
Coloque a menteiga na batedeira.
Coloque o aucar na batedeira.
Ligue a batedeira.
Enquanto um creme homogneo no for obtido, junte mais uma gema.
Adicione aos poucos o leite.
Desligue a batedeira.
Adicione a farinha de trigo.
Adicione o chocolate em p.
Adicione o fermento.
Reserve a massa obtida em um lugar temporrio.
Execute o algoritmo para obter as claras em neve.
Junte as claras em neve massa de chocolate que estava reservada.
Misture esta massa delicadamente.
Execute o algoritmo para untar a forma com manteiga e farinha.
Coloque a forma no forno.
Espere 30 minutos.
Tire a forma do forno.
Desenforme o bolo ainda quente.
Separe o bolo em um lugar temporrio.
Faa a cobertura segundo o algoritmo de fazer cobertura.
Coloque a cobertura no bolo.
fim.

5
Prof: Jos Matias Pedro

O que padro de comportamento?


Imagine a seguinte seqncia de nmeros: 1, 3, 5, 7, 9, 11....
Para determinar o stimo elemento da srie, precisamos descobrir qual a sua regra
de formatao, isto , seu padro de comportamento, (2n+1).
Para tal, observamos que a srie obedece a uma constncia; visto que existe uma
diferena constante entre cada elemento, a qual pode ser facilmente determinada,
somos capazes de determinar o stimo e qualquer outro termo.

O que um programa?
Para que um computador possa desempenhar uma tarefa necessrio que esta seja
detalhada passo-a-passo, numa forma compreensvel pela mquina, utilizando aquilo
que se chama de programa. Neste sentido, um programa de computador nada mais
que um algoritmo escrito numa forma compreensvel pelo computador (linguagem
de programao). Ou ainda, um programa a codificao em alguma linguagem
formal que garanta que os passos do algoritmo sejam executados da maneira como
se espera por quem executa as instrues.
E assim tambm com os algoritmos escritos para computador, voc deve especificar
todos os passos, para que o computador possa chegar ao objetivo.
Exemplo1:
Dados os nmeros naturais(N)
0, 1, 2, 3, 4, 5, 6, ...
passo1: faa N igual a zero
passo2: some 1 a N
passo3: volte ao passo 2
Exemplo2:
Soma dos primeiros 100 nmeros naturais:
passo1: faa N igual a zero
passo2: some 1 a N
passo3: se N for menor ou igual a 100
ento volte ao passo 2
seno pare.

Nos dois exemplos acima, o primeiro possui repertrio bem definido, mas no finito,
enquanto que o segundo tem um critrio de parada, ou seja, finito e descreve um
padro de comportamento, ou seja, temos um algoritmo.

Etapas de um programa
Ao montar um algoritmo, precisamos primeiro dividir o problema apresentado em
trs fases fundamentais, como se conceitua Processamento de Dados:

Entrada Processamento Saida

Entrada: So os dados de entrada do algoritmo.


Hardwares para entradas de dados: teclado, mouse, microfone, scanner.
Processamento: So os procedimentos utilizados para chegar ao resultado final.
Sada: So os dados j processados.
Hardwares para sada de dados: impressora, monitor, caixa de som

6
Prof: Jos Matias Pedro

1.2. Formas de Representao de Algoritmos


Existem diversas formas de representao de algoritmos, mas no h um consenso
com relao melhor delas.
Dentre as formas de representao de algoritmos mais conhecidas podemos citar:
Descrio Narrativa;
Fluxograma;
Pseudocdigo, tambm conhecido como Linguagem Estruturada ou Portugol.

Descrio Narrativa
Nesta forma de representao os algoritmos so expressos diretamente em
linguagem natural.
Como exemplo tem-se os algoritmo seguinte:
Troca de um pneu furado:
Afrouxar ligeiramente as porcas
Suspender o carro
Retirar as porcas e o pneu
Colocar o pneu reserva
Apertar as porcas
Abaixar o carro
Dar o aperto final nas porcas
Esta representao pouco usada na prtica porque o uso da linguagem natural
muitas vezes d oportunidade a ms interpretaes, ambigidades e imprecises.
Vantagens:
- O portugus bastante conhecido por ns;
Desvantagens:
- Impreciso;
- Pouca confiabilidade (a impreciso acarreta a desconfiana);
- Extenso (normalmente, escreve-se muito para dizer pouca coisa).

Fluxograma

uma representao grfica de algoritmos onde formas geomtricas diferentes


implicam aes (instrues, comandos) distintos. Tal propriedade facilita o
entendimento das idias contidas nos algoritmos e justifica sua popularidade.
Esta forma aproximadamente intermediria descrio narrativa e ao
pseudocdigo, pois menos imprecisa que a primeira e, no entanto, no se preocupa
com detalhes de implementao do programa, como o tipo das variveis usadas.

Terminal indica incio e/ou fim do fluxo de um


programa ou sub-programa.

Seta de Fluxo de dados indica o sentido do


fluxo. Serve para conectar os smbolos.

Entrada- Operao de entrada de dados.

Processamento operao de atribuio. Indica os

7
Prof: Jos Matias Pedro

clculos a efetuar, ou atribuies de valores.

Sada operao de sada de dados. Apresenta os


dados no monitor (ou algum outro dispositivo de sada).

Deciso (a ser tomada) indicando os desvios para outros pontos do fluxo,


dependendo do resultado da comparao.

Preparao grupo de operaes no includas na


diagramao (chave que modificar a execuo de um determinado programa).

Conector ao receber duas Setas de Fluxo de dados, normalmente aps


o fechamento dos processos decorrentes de uma deciso.

Conector de seo quando for necessrio particionar o fluxograma.


Coloca um nmero idntico em ambas as sees / pginas, indicando a sua
continuao.

Abaixo est a representao do algoritmo de clculo da mdia de um aluno sob a


forma de um fluxograma.

Inicio

N1

N2

Media ((N1+N2)/2)

8
Prof: Jos Matias Pedro

Media>=7

Reprovado Aprovado

Fim
Exemplo de um fluxograma convencional

De modo geral, um fluxograma se resume a um nico smbolo inicial por onde a


execuo do algoritmo comea, e um ou mais smbolos finais, que so pontos onde
a execuo do algoritmo se encerra. Partindo do smbolo inicial, h sempre um nico
caminho orientado a ser seguido, representando a existncia de uma nica seqncia
de execuo das instrues.
Isto pode ser melhor visualizado pelo fato de que, apesar de vrios caminhos
poderem convergir para uma mesma figura do diagrama, h sempre um nico
caminho saindo desta. Excees a esta regra so os smbolos finais, dos quais no
h nenhum fluxo saindo, e os smbolos de deciso, de onde pode haver mais de um
caminho de sada (usualmente dois caminhos), representando uma bifurcao no
fluxo.

Pseudocdigo
De forma semelhante como os programas so escritos. Tambm chamado de
Portugol ou Portugus Estruturado.
A linguagem de Programao mais prxima o Pascal, com codificao quase
idntica ao Ingls Estruturado.
Esta forma de representao de algoritmos rica em detalhes, como a definio dos
tipos das variveis usadas no algoritmo. Por assemelhar-se bastante forma em que
os programas so escritos, encontra muita aceitao.
A forma geral da representao de um algoritmo na forma de pseudocdigo a
seguinte:
algoritmo <Nome_do_Programa>
<declarao_de_variveis>
inicio
<corpo do algoritmo>
FimAlgoritmo
Algoritmo uma palavra que indica o incio da definio de um algoritmo em Portugol
ou Pseudocdigo.
<Nome_do_Programa> um nome simblico dado ao algoritmo com a finalidade de
distingui-los dos demais.

9
Prof: Jos Matias Pedro

<declarao_de_variveis> consiste em uma poro opcional onde so declaradas as


variveis globais usadas no algoritmo principal e, eventualmente, nos subalgoritmos.
Incio e FimAlgoritmo so respectivamente as palavras que delimitam o incio e o
trmino do conjunto de instrues do corpo do algoritmo.
Abaixo apresentamos exemplo na forma de um pseudocdigo, da representao do
algoritmo do clculo da mdia de um aluno.

AlgoritmoCalculoMedia:
var n1, n2, media: real
inicio
leia n1
leia n2
media ((n1 + n2) / 2)
se (media >= 7) entao
escreva (aprovado)
senao
escreva (reprovado)
fimse
FimAlgoritmo

1.3. Estrutura de Controle


Estrutura Seqencial
o conjunto de aes primitivas que sero executadas numa seqncia linear de
cima para baixo e da esquerda para direita, isto , na mesma ordem em que foram
escritas.
Como podemos perceber, todas as aes devem pular uma linha, o que objetiva
separar uma ao de outra.
Variveis;
(declarao de variveis)
incio
comando 1
comando 2
comando 3
FimAlgoritmo.
Algoritmo - 01: Criar um programa que efetue a leitura de dois valores
numricos. Faa a operao de soma entre os dois valores e apresente
o resultado obtido.
Planejamento
Problema: Calcular a soma de dois nmeros.
Objetivo: Apresentar a soma de dois nmeros.
Entradas: N1, N2
Sadas: SOMA
Projeto
Clculos:
SOMA <- N1 + N2

10
Prof: Jos Matias Pedro

Fluxograma:

INICIO

N1

N2

SOMA N1+N2

SOMA

FIM

Portugol:
algoritmo Algoritmo01
var
N1 : real
N2 : real
SOMA : real
inicio
leia N1
leia N2
SOMA <- N1 + N2
escreva SOMA
FimAlgoritmo

Estruturas de Seleo ou Deciso


Uma estrutura de deciso permite a escolha de um grupo de aes e estruturas a ser
executado quando determinadas condies, representadas por expresses lgicas,
so ou no satisfeitas.

Deciso Simples
Se <condio> entao
{bloco verdade}
Fimse
<condio> uma expresso lgica, que, quando inspecionada, pode gerar um
resultado falso ou verdadeiro.
Se .V., a ao primitiva sob a clusula ser executada; caso contrrio, encerra o
comando, neste caso, sem executar nenhum comando.
Algoritmo - 02: Ler dois valores numricos, efetuar a adio e apresentar o
resultado caso o valor somado seja maior que 10.
Planejamento

11
Prof: Jos Matias Pedro

Problema: Calcular a soma de dois nmeros e apresentar o resultado com condio.


Objetivo: Apresentar o resultado apenas se for maior que 10.
Entradas: N1, N2
Sadas: SOMA
Projeto
Clculos e detalhamento:
SOMA <- N1 + N2
Se (SOMA > 10) entao
escreva SOMA
Fimse
Portugol
Algoritmo Algoritmo02
Var
N1: Real
N2: Real
SOMA: Real
Inicio
Leia N1
Leia N2
SOMA <- N1 + N2
Se (SOMA > 10) entao
Escreva SOMA
Fimse
FimAlgoritmo

Deciso Composta
Se <condio> entao
{bloco verdade}
Senao
{bloco falso}
Fimse
<condio> uma expresso lgica, que, quando inspecionada, pode gerar um
resultado falso ou verdadeiro.
Algoritmo - 03: Ler dois valores numricos, efetuar a adio. Caso o valor
somado seja maior ou igual a 10, este dever ser apresentado somando-se a
ele mais 5, caso o valor somado no seja maior ou igual a 10, este dever ser
apresentado subtraindo-se 7.
Planejamento
Problema: Calcular a soma de dois nmeros e apresentar o resultado com uma
condio.
Objetivo: Apresentar o Resultado se for igual ou maior que 10 adicionando mais 5,
se no for igual ou maior que 10 dever subtrair 7.
Entradas: A, B
Sadas: TOTAL
Auxiliares: SOMA
Projeto
Clculos e detalhamento:
SOMA <- A + B
Se (SOMA >= 10) Ento
TOTAL <- SOMA + 5

12
Prof: Jos Matias Pedro

Seno
TOTAL <- SOMA - 7
Fimse
Portugol
Algoritmo Algoritmo03
Var
A: Real
B: Real
SOMA: Real
TOTAL: Real
Inicio
Leia A
Leia B
SOMA A + B
Se (SOMA >= 10) entao
TOTAL SOMA + 5
Senao
TOTAL SOMA 7
Fimse
Escreva TOTAL
FimAlgoritmo

Seleo Mltipla
Esta estrutura evita que faamos muitos blocos se, quando o teste ser sempre em
cima da mesma varivel.
Exemplo:
Se (variavel = valor1) entao
comando1
Senao
Se (variavel = valor2) entao
Comando2
Senao
Se (variavel = valor3) entao
Comando3
Senao
Se (variavel = valor4) entao
Comando4
Senao
comando5
Fimse
Fimse
Fimse
Fimse
Com a estrutura de escolha mltipla, o algoritmo ficaria da seguinte maneira:
escolha variavel
caso valor1
comando1
caso valor2
comando2
caso valor3

13
Prof: Jos Matias Pedro

comando3
caso valor4
comando4
outrocaso
comando5
fimescolha

Estruturas de Repetio
Estas estruturas possibilitam que nosso algoritmo seja muito mais enxuto e fcil de
se programar.
Imagine um algoritmo de fatorial de 8:
variaveis
fat : real;
incio
fat <- 8 * 7
fat <- fat * 6
fat <- fat * 5
fat <- fat * 4
fat <- fat * 3
fat <- fat * 2
escreva (fat)
FimAlgoritmo.
O resultado ser o fatorial com certeza, mas, imagine se fosse o fatorial de 250. Ou
ainda, o usurio deseja fornecer o nmero e o algoritmo deve retornar o fatorial, qual
nmero ser digitado?
Quantas linhas devero ser escritas?
Para isso servem as estruturas de repetio, elas permitem que um determinado
bloco de comandos seja repetido vrias vezes, at que uma condio determinada
seja satisfeita.
Sero estudadas as seguintes estruturas de repetio:
- Enquanto - Faca, o teste realizado no Incio do looping
- Repita - Ateque, o teste realizado no Final do looping
- Para - de - ate - Passo - faca, cuja Estrutura utilizada uma varivel de Controle.

Enquanto ... Faca - Estrutura com teste no Incio


Esta estrutura faz seu teste de parada antes do bloco de comandos, isto , o bloco
de comandos ser repetido, at que a condio seja F. Os comandos de uma
estrutura enquanto .. faca poder ser executada uma vez, vrias vezes ou nenhuma
vez.
Enquanto < condio > Faa
< bloco de comandos >
FimEnquanto

Para ... de ... ate ... Passo ... faca Estrutura com varivel de Controle
Nas estruturas de repetio vistas at agora, acorrem casos em que se torna difcil
determinar quantas vezes o bloco ser executado. Sabemos que ele ser executado
enquanto uma condio for satisfeita - enquanto..faa, ou at que uma condio seja
satisfeita - repita...at. A estrutura para .. passo repete a execuo do bloco um
nmero definido de vezes, pois ela possui limites fixos:
Para <varivel> <- <valor> at <valor> passo N faca

14
Prof: Jos Matias Pedro

< bloco de comandos >


FimPara

Saber Lgica de Programao mais importante que saber uma Linguagem de


Programao. Sem saber como se planeja um programa, dificilmente poder ser
implantada tanto na Linguagem de Programao mais antiga como nas mais
modernas.

2.1. Dados Homogneos


Uma estrutura de dados, que utiliza somente um tipo de dado, em sua definio
conhecida como dados homogneos. Variveis compostas homogneas
correspondem a posies de memria, identificadas por um mesmo nome,
individualizado por ndices e cujo contedo composto do mesmo tipo. Sendo os
vetores (tambm conhecidos como estruturas de dados unidimensionais) e as
matrizes (estruturas de dados bidimensionais) os representantes dos dados
homogneos.

Vetor
O vetor uma estrutura de dados linear que necessita de somente um ndice para
que seus elementos sejam endereados. utilizado para armazenar uma lista de
valores do mesmo tipo, ou seja, o tipo vetor permite armazenar mais de um valor em
uma mesma varivel. Um dado vetor definido como tendo um nmero fixo de clulas
idnticas (seu contedo dividido em posies). Cada clula armazena um e
somente um dos valores de dados do vetor. Cada uma das clulas de um vetor possui
seu prprio endereo, ou ndice, atravs do qual pode ser referenciada. Nessa
estrutura todos os elementos so do mesmo tipo, e cada um pode receber um valor
diferente [ 3, 21, 4].
Algumas caractersticas do tipo vetor([10]):
Alocao esttica (deve-se conhecer as dimenses da estrutura no momento
da declarao)
Estrutura homognea
Alocao seqencial (bytes contguos)
Insero/Excluso
o Realocao dos elementos
o Posio de memria no liberada

Exemplo de um vetor
Vetor
Nota 9.5 7.4 8.1 4.6 4.5
1 2 3 4 5 ndices.
A figura acima mostra um vetor de notas de alunos, a referncia NOTA[4] indica o valor 4.6 que se
encontra na coluna indicada pelo ndice 4.

A definio de um vetor em C se d pela sintaxe: tipo_do_dado nome_do_vetor[


tamanho_do_vetor ].
Programa : Declarao de vetor em C.
int i[ 3];
i[0]= 21;
i[1]= 22;
i[ 2]= 24;

15
Prof: Jos Matias Pedro

char c[4];
c[0]=a;
c[1]=b;
c[ 2]=c;
c[ 3]=d;

Programa: Exemplo de uso de vetores.


/* programa_vetor*/
#include <stdio.h>
#define TAMANHO 5
int main (void)
{
int iIndice;
int iValorA;
int iSoma;
int aVetor [TAMANHO];
float fMedia;
for (iIndice = 0; iIndice < TAMANHO; iIndice++)
{
printf("Entre com o valor %d:", iIndice + 1);
scanf("%d", &iValorA);
aVetor[iIndice] = iValorA;
}
iSoma = 0;
for (iIndice=0; iIndice < TAMANHO; iIndice++)
{
iSoma += aVetor[iIndice];
}
fMedia = (float) iSoma/TAMANHO;
printf ("Media : %f\n", fMedia);
return 0;
}

Lembrete: Caso seja colocada num programa a instruo a [2]++ est sendo dito
que a posio do vetor a ser incrementada.

2.1.2- Matriz
Uma matriz um arranjo bidimensional ou multidimensional de alocao esttica e
seqencial. A matriz uma estrutura de dados que necessita de um ndice para
referenciar a linha e outro para referenciar a coluna para que seus elementos sejam
endereados. Da mesma forma que um vetor, uma matriz definida com um tamanho
fixo, todos os elementos so do mesmo tipo, cada clula contm somente um valor e
os tamanhos dos valores so os mesmos (em C, um char ocupa 1 byte e um int 4
bytes) [3 , 21, 4].
Os elementos ocupam posies contguas na memria. A alocao dos elementos da
matriz na memria pode ser feita colocando os elementos linha-por linha ou coluna-
por-coluna.

Matriz de 2x2 Matriz M

16
Prof: Jos Matias Pedro

i/j 0 1

0
0
1
0
Sendo C a quantidade de 0colunas por linhas, i o nmero da linha e j a posio do
elemento dentro linha, possvel definir a frmula genrica para acesso na memria,
onde Posij = endereo inicial + ((i-1) * C * tamanho do tipo do elemento) + ((j-1) * tamanho
do tipo do elemento).
Uma matriz consiste de dois ou mais vetores definidos por um conjunto de elementos. Cada
dimenso de uma matriz um vetor. O primeiro conjunto (dimenso) considerado o primeiro
vetor, o segundo conjunto o segundo vetor e assim sucessivamente.
A definio de uma matriz em C se d pela sintaxe:
tipo_do_dado nome_da_matriz[ quantidade_linhas ] [ quantidade_colunas ]

Matriz Letras
1 2 3 4 5 6 Colunas
1 1 1
1 M A R3 C O S

2 N A S S E R
1
3 D O N A L D
1

Linhas

A matriz LETRAS composta de 18 elementos (3 linhas e 6 colunas), a referncia a


MATRIZ[3][3] (onde o primeiro 3 indica a linha e o segundo 3 indica a coluna) retorna
o elemento N; no caso de MATRIZ[2][5] (segunda linha e terceira coluna) ir retornar
o elemento E. Como uma matriz de strings (linguagem C), a chamada a MATRIZ[3]
ir reproduzir o valor DONALD.
A linguagem C permite ainda trabalhar com matrizes de vrias dimenses (matrizes
n-dimensionais), embora o seu uso fique mais restrito em aplicaes cienticas face
sua pouca praticidade de uso. A definio de uma matriz de vrias dimenses em
C se d pela sintaxe:
tipo_do_dado nome_da_matriz[tamanho_dimenso_1] [tamanho_dimenso_2]
[tamanho_dimenso_3] ... [tamanho_dimenso_n]

Exemplo de uso de Matriz com duas dimenses:


/* programa_matriz */
#include <stdio.h>
#define DIMENSAO 2
int main (void)

17
Prof: Jos Matias Pedro

{
int iLinha, iColuna;
int iDeterminante;
int iValorA;
int aMatriz [DIMENSAO][DIMENSAO]
/* Uma regra que se pode sempre levar em considerao:
para cada dimenso de uma matriz, sempre haver um lao (normalmente um for). Se
houver duas dimenses, ento haver dois laos. */
for (iLinha=0; iLinha < DIMENSAO; iLinha++)
{
for (iColuna=0; iColuna < DIMENSAO; iColuna++)
{
printf ("Entre item %d %d:", iLinha + 1, iColuna + 1);
scanf ("%d", &iValorA);
matriz [iLinha][iColuna] = iValorA;
}
}
iDeterminante = aMatriz[0][0] * aMatriz [1][1] -
aMatriz[0][1] * aMatriz [1][0];
printf ("Determinante : %d\n", iDeterminante);
return 0;
}
Exemplo de uso de Matriz com Varias dimenses:
/* programa_matriz */
#include <stdio.h>
#define DIM_1
#define DIM_2
#define DIM_3
#define DIM_4
int main (void)
{
int i,j,k,l;
int aMatriz [DIM_1][DIM_2 ][DIM_3 ][DIM_4];
/* Cdigo para zerar uma matriz de quatro dimenses */
for (i=0; i < DIM_1; i++)
{
for (j=0; j < DIM_ ; j++)
{
for (k=0; k < DIM_ ; k++)
{
for (l=0; l < DIM_4; l++)
{
aMatriz [i][j][k][l] = i+j+k+l;
}
}
}
}
/* Uma regra que se pode sempre levar em considerao: para cada dimenso de uma
matriz, sempre haver um lao (normalmente um for). Se houver quatro dimenses ento
haver quatro laos */
For (i=0; i < DIM_1; i++)
{
for (j=0; j < DIM_2 ; j++)
{
for (k=0; k < DIM_3 ; k++)

18
Prof: Jos Matias Pedro

{
for (l=0; l < DIM_4; l++)
{
printf("\nValor para matriz em [%d] [%d] [%d] [%d] = %d", i,j,k,l, aMatriz[i][j][k][l]);
}
}
}
}
return 0;
}

Ponteiro
A linguagem C implementa o conceito de ponteiro. O ponteiro um tipo de dado como
int, char ou float. A diferena do ponteiro em relao aos outros tipos de dados que
uma varivel que seja ponteiro guardar um endereo de memria.
Por meio deste endereo pode-se acessar a informao, dizendo que a varivel
ponteiro aponta para uma posio de memria. O maior problema em relao ao
ponteiro entender quando se est trabalhando com o seu valor, ou seja, o endereo,
e quando se est trabalhando com a informao apontada por ele.
Operador & e *
O primeiro operador de ponteiro &. Ele um operador unrio que devolve o
endereo na memria de seu operando. Por exemplo: m = &count; pe o endereo
na memria da varivel count em m. Esse endereo a posio interna da varivel
na memria do computador e no tem nenhuma relao com o valor de count. O
operador & tem como significado o endereo de. O segundo operador *, que o
complemento de &. O * um operador unrio que devolve o valor da varivel
localizada no endereo que o segue. Por exemplo, se m contm o endereo da
varivel count: q = *m; coloca o valor de count em q. O operador * tem como
significado no endereo de.
A declarao de uma varivel ponteiro dada pela colocao de um asterisco (*) na
frente de uma varivel de qualquer tipo. Na linguagem C, possvel definir ponteiros
para os tipos bsicos ou estruturas. A definio de um ponteiro no reserva espao
de memria para o seu valor e sim para o seu contedo. Antes de utilizar um ponteiro,
o mesmo deve ser inicializado, ou seja, deve ser colocado um endereo de memria
vlido para ser acessado posteriormente.
Um ponteiro pode ser utilizado de duas maneiras distintas. Uma maneira trabalhar
com o endereo armazenado no ponteiro e outro modo trabalhar com a rea de
memria apontada pelo ponteiro. Quando se quiser trabalhar com o endereo
armazenado no ponteiro, utiliza-se o seu nome sem o asterisco na frente. Sendo
assim qualquer operao realizada ser feita no endereo do ponteiro. Como, na
maioria dos casos, se deseja trabalhar com a memria apontada pelo ponteiro,
alterando ou acessando este valor, deve-se colocar um asterisco antes do nome do
ponteiro. Sendo assim, qualquer operao realizada ser feita no endereo de
memria apontado pelo ponteiro. O programa seguinte demostra a utilizao de
ponteiros para acesso memria.

/* programa_matriz */
#include <stdio.h>
int main (void)
{
int *piValor; /* ponteiro para inteiro */

19
Prof: Jos Matias Pedro

int iVariavel = 27121975


piValor = &iVariavel; /* pegando o endereo de memria da varivel */
printf ("Endereco: %d\n", piValor);
printf ("Valor : %d\n", *piValor);
*piValor = 180119 82 ;
printf ("Valor alterado: %d\n", iVariavel);
printf ("Endereco : %d\n", piValor);
return 0;
}

Passando variveis para funes por referncia


O ponteiro utilizado para passar variveis por referncia, ou seja, variveis que
podem ter seu contedo alterado por funes e mantm este valor aps o trmino da
funo.
Na declarao de uma funo, deve-se utilizar o asterisco antes do nome do
parmetro, indicando que est sendo mudado o valor naquele endereo passado
como parmetro. No programa seguinte visto um exemplo de uma varivel sendo
alterada por uma funo.
Programa : ponteiro como referencia em funo

/* programa_ponteiro*/
#include <stdio.h>
Void soma (int, int, int *);
int main (void)
{
int iValorA;
int iValorB;
int iResultado;
printf ("Entre com os valores:");
scanf ("%d %d", &iValorA, &iValorB);
printf("Endereco de iResultado = %d\n", &iResultado);
Soma (iValorA, iValorB, &iResultado) ;/* est sendo passado o endereo de memria
da varivel, qualquer alterao estar
sendo realizada na memria */
printf ("Soma : %d\n", iResultado);
return 0;
}
void soma (int piValorA, int piValorB, int * piResultado)
{
printf("Endereco de piResultado = %d\n", piResultado);
/* o valor est sendo colocado diretamente na memria */
*piResultado = piValorA + piValorB;
return;
}

Acesso Linear
Supondo que, para um determinado clculo, necessario aceder aos lementos de
um vetor, o acesso a esses elementos pode ser feito de forma sequencial desde o
primeiro at ao ultimo. Esse um acesso de tipo linear.
Um acesso linear tem um nmero de iteraes proporcional ao nmero de elementos
do vetor, ou seja o numero total de intrues executadas proporcional ao nmero
de elementos de entrada, sendo, por isso, uma funo de ordem linear, ou O(n).

20
Prof: Jos Matias Pedro

O cdigo seguinte calcula a soma dos elementos de um vetor de inteiros. Para obter
essa soma, tm que se consultar todos os elementos e acomula-los numa varivel. O
acesso linear e sequencial, o numero de iteraes executadas pela instruo for
igual ao nmero de elemento do vetor, ou seja, o nmero total de instrues
executadas proporcional ao nmero de elemento do vetor.
Soma dos elementos de um vetor
int soma(int v[ ], int tamanho){
int j, total=0;
for(j=0; j<tamanho; j++)
total +=v[ j ];
return total;
}

Em C, o nome de um vetor uma variavel que contm o endereo da primeira posio


do vetor. Se uma funo, como a funo anterior, recebe o nome de um vetor por
parmetro, recebe apenas a informao sobre o local em memoria onde o vetor
comea, mas no recebe informao sobre o local onde o vetor termina ou sobre o
comprimento do vetor. Por este motivo, qualquer funo em C que recebe um vetor
por parmetro e precise de o consultar, tem que receber tambm, por parmetro, o
nmero de elemento do vetor.
No cabealho da funo anterior int soma (int v[ ], int tamanho), a varivel v recebe o
nome do vetor e a varivel tamanho recebe o nmero de elementos.
Segui-se um programa completo que utiliza a funo anterior para calcular a soma
dos elementos de um vetor.
# include <stdio.h>
int soma (int v[ ], int tamanho) {
int j, total=0;
for (j=0; j<tamanho; j++)
total +=v[ j ];
return total;
}
int main () {
int vtr [10]= {1, 4, 9, 16, 25, 36, 49, 64, 81, 100};
print f (soma: %d\n, soma(vtr,10));
}

Na funo main do cdigo anterior, a funo soma invocada com os parmetros vtr
e 10, que so o nome do vetor e o seu tamanho.

Pesquisa Linear
No caso de se pretender pesquisar um vetor para averiguar se um determinado valor
existe nesse vetor, necessario percorrer o vetor, de forma sistemtica para garantir
a correo na pesquisa.
A forma mais universal de pesquisa de um vetor a pesquisa linear: percorre-se o
vetor do inicio at ao fim e avalia-se, posio a posio, se o valor ai contido o valor
procurado.

21
Prof: Jos Matias Pedro

No exemplo seguinte a funo procura recebe, por parmetro, o vetor, o seu tamanho
e o valor a pesquisar; e devolve o valor 1 no caso de encontrar o valor procurado, e
o valor 0 no caso contrario. Note-se que necessario percorrer o vetor at ao fim para
se poder afirmar que o valor no existe, ou seja, a instruo return 0 s invocado
aps o ciclo for terminar.
# include<stdio.h>
int procura (int v [ ], int tam, int valor){
int i;
for (i=0; i<tam; i++)
if (v [ i] ==valor) return i;
return -1;
}
int main() {
int vtr[10]={5,2,-3,7,10,15,-2,12,8,0};
int posicao, valor;
printf (Insira um numero a pesquisar:);
scanf(%d, &valor);
posicao=procura(vtr,10,valor);
if (posicao!=-1) printf(Existe na posicao:%d\n, posicao);
else printf(No existe\n);
}
A funo anterior de ordem linear, ou O(n). Em mdia, e caso o valor pesquisado
exista no vetor, o nmero de iteraes n/2.
Por exemplo, se o vetor tiver 1000 elementos, so necessarias 500 iteraes, em
mdia, para se encontrar um determinado elemento, caso exista.

3.Analise de Complexidade
Algoritmo: sequencia de instrues necessarias para a resoluo de um problema
bem formulado (passiveis de implementao em computador)
Estrategia:
especificar (definir propriedades)
arquitectura (algoritmo e estruturas de dados)
analise de complexidade (tempo de execuo e memoria)
implementar (numa linguagem de programao)
testar (submeter entradas e verificar observancia das propriedades
especificadas)

3.1. Anlise de algoritmos


Provar que um algoritmo esta correcto
Determinar recursos exigidos por um algoritmo (tempo, espaco,etc.)
comparar os recursos exigidos por diferentes algoritmos que resolvem
o mesmo problema (um algoritmo mais eficiente exige menos recursos
para resolver o mesmo problema)

22
Prof: Jos Matias Pedro


prever o crescimento dos recursos exigidos por um algoritmo a medida
que o tamanho dos dados de entrada cresce.
Que dados usar ?
dados reais: verdadeira medida do custo de execuo
dados aleatorios: assegura-nos que as experincias testam o algoritmo
e no apenas os dados especificos.
Caso medio
dados perversos: mostram que o algoritmo funciona com qualquer tipo
de dados
Pior caso!
dados benficos:
Melhor caso

3.2. Complexidade espacial e temporal


Complexidade espacial de um programa ou algoritmo: espao de memoria que
necessita para executar at ao fim.
S(n) : espao de memoria exigido em funo do tamanho n da entrada
Complexidade temporal de um programa ou algoritmo: tempo que
demora a executar (tempo de execuo)
T(n) : tempo de execuo em funo do tamanho n da entrada
Complexidade - vs. Eficiencia
Por vezes estima-se a complexidade para o melhor caso (pouco util), o pior caso
(mais util) e o caso medio (igualmente util)

3.2.1. Complexidade temporal


Analise precisa uma tarefa complicada
algoritmo e implementado numa dada linguagem
a linguagem compilada e o programa executado num dado computador
dificil prever tempos de execuo de cada instruo e antever optimizaes
muitos algoritmos so sensiveis aos dados de entrada
muitos algoritmos no so bem compreendidos
Para prever o tempo de execuo de um programa
apenas necessario um pequeno conjunto de ferramentas matematicas

3.2.2. Notao O-grande


Para medir a eficincia de um algoritmo, tanto no tempo de execuo como no espao
requerido, utiliza-se frequentemente a notao O-grande, que uma notao
matemtica utilizada para analizar o comportamento de funes quando o argumento
tende para um valor limite particular ou para infinito.
T(n) = O(f(n)) (l-se: T(n) de ordem f(n)) se e so se existem constantes positivas c
e n0 tal que T(n) c.f(n) para todo o n > n0
De um modo informal, pode dizer-se que f(n) pode ser representada por O(g(n)) se
ambas as funes f e g crescem da mesma forma para valores grandes de n, ou seja
f e g so proporcionais.
Na tabela seguinte apresenta-se uma lista de classes de funes tipicamente
utilizadas para analise de algoritmos, por ordem crescente de crescimento de
funes.

23
Prof: Jos Matias Pedro

Ordens tipicas da eficincia de algoritmos


Notao Nome
O(1) Ordem constante
O(log n) Ordem logaritmica
O(n) Ordem linear
O(n.log n) Ordem linear-logaritmica
O(n2) Ordem quadrtica
O(n3) Ordem cbica
O(2n) Ordem exponencial
O(n!) Ordem factorial

3.2.3. Ordens de complexidade mais comuns:

Os Algoritmos tem tempo de execuo proporcional a:


1 : muitas instrues so executadas uma so vez ou poucas vezes (se isto acontecer
para todo o programa diz-se que o seu tempo de execuo constante)
log n : tempo de execuo logaritmico (cresce ligeiramente a medida que n cresce;
quando n duplica log n aumenta mas muito pouco; apenas duplica quando n aumenta
para n2)
n : tempo de execuo linear (tipico quando algum processamento e feito para
cada dado de entrada; situao optima quando necessario processar n dados de
entrada, ou produzir n dados na saida)
n log n : tipico quando se reduz um problema em subproblemas, se resolve estes
separadamente e se combinam as solues (se n igual a 1 milhao, n log n perto
de 20 milhes)
n2 : tempo de execuo quadratico (tipico quando necessario processar todos os
pares de dados de entrada; pratico apenas em pequenos problemas, ex: produto de
vectores)
n3 : tempo de execuo cubico (para n = 100, n3 = 1 milho, ex: produto de matrizes)
2n : tempo de execuo exponencial (provavelmente de pouca aplicao pratica;
tipico em solues de fora bruta; para n = 20, 2n = 1 milho; se n duplica, o tempo
passa a ser o quadrado)

24
Prof: Jos Matias Pedro

Exemplo de analise de complexidade- findmax()


Como calcular o tempo de execuo do algoritmo seguintes:

int findMax(int A[ ], int n ) {


int max =A[ 0]; 2 operaes
int i =1; 1 operao
While (i<= n-1){ n operaes

if (A[i]>max) 2 ops
max =A[i]; 2 ops n-1 vez
i=i+1; 2 ops
}
return max; 1 operao
}

Pressupostos (RAM -Random Access Memory):


memria ilimitada e endereos contm um nmero arbitrrio ou caracteres,
aceder ao contedo de um endereo custa uma unidade de tempo.
max= A[0]; 1 leitura de A[0] + 1 atribuio a max.
Determinar complexidade do findMax()
Casos possiveis:
caso mais favorvel (A[0] o maior elemento):
t(n) = 2 + 1 + n + 4(n 1) + 1 = 5n operaes primitivas
pior caso:
t(n) = 2 + 1 + n + 6(n 1) + 1 = 7n 2
caso mdio? difcil de calcular, depende da distribuio do input; usar teoria
de probabilidades.

25
Prof: Jos Matias Pedro

Normalmente, olharemos para o pior caso, pois d-nos um limite superior do tempo
de execuo.
Calculo do tempo de execuo
seja a o tempo observado para a operao primitiva mais rpida
seja b o tempo observado para a operao primitiva mais lenta
ento, o pior tempo de execuo T(n) ser
a(7n2) <=T(n) <=b(7n2) ) T(n) limitado por 2 funes lineares

Taxa de crescimento do tempo de execuo


Saliente-se que:
o tempo de execuo, T(n), pode ser afectado pela alterao do ambiente
hardware/software, mas
tal no acontece se considerarmos a taxa de crescimento de T(n).
Taxa de crescimento de T(n) corresponde
ao crescimento de T(n) quando se aumenta o valor de n.

O exemplo findMax()
mostrava T(n) limitado por 2 funes lineares em n, significando que o tempo
de execuo varia na mesma proporo que n.
Logo, diz-se que o crescimento de T(n) linear.

Como escolher um algoritmo?

Tempo de processamento
Um algoritmo que realiza uma tarefa em 10 horas melhor que outro que
realiza em 10 dias.

Quantidade de memria necessria


Um algoritmo que usa 1MB de memria RAM melhor que outro que usa 1GB
Estudar nmero de vezes que as operaes so executadas
Exemplo- tempo de processamento
Achar o mximo de um vetor
int vmax(int *vec, int n) { -
int i; -
int max = vec[0]; 1
for (i = 1; i < n; i++) { n-1
if (vec[i] > max) { n-1
max = vec[i]; A<n-1
} n-1
} n-1
return max; 1
}

Complexidade: f(n) = n-1


Esse algoritmo timo

Analise de tempo de processamento


Anlise de complexidade feita em funo de n
n indica o tamanho da entrada

26
Prof: Jos Matias Pedro

Nmero de elementos no vetor


Nmero de vrtices num grafo
Nmero de linhas de uma matriz
Diferentes entradas podem ter custo diferente
Melhor caso
Pior caso
Caso mdio

4. Tecnicas Gerais de projecto de Algoritmos


4.1. Recursividade
Recurso o processo de definir algo em termos de si mesmo e , algumas vezes,
chamado de definio circular. Assim, pode-se dizer que o conceito de algo recursivo
est dentro de si, que por sua vez est dentro de si e assim sucessivamente,
infinitamente.
O exemplo a seguir define o ancestral de uma pessoa:
Os pais de uma pessoa so seus ancestrais (caso base);
Os pais de qualquer ancestral so tambm ancestrais da pessoa inicialmente
considerada (passo recursivo).
Definies como estas so normalmente encontradas na matemtica. O grande apelo
que o conceito da recurso traz a possibilidade de dar uma definio finita para um
conjunto que pode ser infinito. Um exemplo aritmtico:
O primeiro nmero natural zero.
O sucessor de um nmero natural um nmero natural.
Na computao o conceito de recursividade amplamente utilizado, mas difere da
recursividade tpica por apresentar uma condio que provoca o fim do ciclo recursivo.
Essa condio deve existir, pois, devido s limitaes tcnicas que o computador
apresenta, a recursividade impedida de continuar eternamente.

Funo para clculo de Fatorial


Na linguagem C, as funes podem chamar a si mesmas. A funo recursiva se um
comando no corpo da funo a chama. Para uma linguagem de computador ser
recursiva, uma funo deve poder chamar a si mesma. Um exemplo simples a
funo fatorial, que calcula o fatorial de um inteiro. O fatorial de um nmero N o
produto de todos os nmeros inteiros entre 1 e N. Por exemplo,3 fatorial (ou 3!) 1 *
2 *3 = 6. O programa abaixo apresenta uma verso iterativa para clculo do fatorial
de um nmero.
Programa: fatorial (verso iterativa)
int fatorialc ( int n )
{
int t, f;
f = 1;
for (t = 1; t<=n; t++)
f=f*t
return f;
}
Mas multiplicar n pelo produto de todos os inteiros a partir de n-1 at 1 resulta no
produto de todos os inteiros de n a 1. Portanto, possvel dizer que fatorial:
0! = 1
1! = 1 * 0!
2! = 2* 1!

27
Prof: Jos Matias Pedro

3! = 3* 2 !
4! = 4* 3 !
Logo o fatorial de um nmero tambm pode ser definido recursivamente (ou por
recorrncia) atravs das seguintes regras (representao matemtica):
n! = 1, se n = 0
n! = n * (n-1)! , se n > 0
O programa seguinte mostra a verso recursiva do programa fatorial.
int fatorialr( int n)
{
int t, f;
/* condio de parada */
if( n == 1 || n == 0)
{
return 1;
}
f = fatorialr(n-1)*n; /* chamada da funo */
return f;
}

A verso no-recursiva de fatorial deve ser clara. Ela usa um lao que executado
de 1 a n e multiplica progressivamente cada nmero pelo produto mvel.
A operao de fatorial recursiva um pouco mais complexa. Quando fatorialr
chamada com um argumento de 1, a funo devolve 1. Caso contrrio, ela devolve o
produto de fatorialr(n-1)*n. Para avaliar essa expresso, fatorialr chamada com n-
1. Isso acontece at que n se iguale a 1 e as chamadas funo comecem a retornar.
Calculando o fatorial de 2, a primeira chamada a fatorialr provoca uma segunda
chamada com o argumento 1. Essa chamada retorna 1, que , ento, multiplicado por
2 (o valor original e n). A resposta ento 2.
Para melhor entendimento, interessante ver como o programa executado
internamente no computador. No caso do programa iterativo (programa seguinte)
necessrio duas variveis f e t para armazenar os diversos passos do processamento.
Por exemplo, ao calcular fatorial de 6, o computador vai passar sucessivamente pelos
seguintes passos (tabela ).
Tabela : Clculo de fatorial de 6

t f
1 1
2 2
3 6
4 24
5 120
6 720

No programa recursivo nada disto acontece. Para calcular o fatorial de 6, o


computador tem de calcular primeiro o fatorial de 5 e s depois que faz a
multiplicao de 6 pelo resultado (120). Por sua vez, para calcular o fatorial de 5 , vai
ter de calcular o fatorial de 4. Resumindo, aquilo que acontece internamente uma
expanso seguida de uma contrao:
fatorialr(6)
6 * fatorialr(5)

28
Prof: Jos Matias Pedro

6 * 5 * fatorialr(4)
6 * 5 * 4 * fatorialr(3)
6 * 5 * 4 * 3 * fatorialr(2)
6 * 5 * 4 * 3 * 2 * fatorialr(1)
6*5*4*3*2*1
6*5*4*3*2
6*5*4*6
6 * 5 * 24
6 * 120
720

Quando uma funo chama a si mesma, novos parmetros e variveis locais so


alocados na pilha e o cdigo da funo executado com essas novas variveis.
Uma chamada recursiva no faz uma nova cpia da funo; apenas os argumentos
so novos. Quando cada funo recursiva retorna, as variveis locais e os parmetros
so removidos da pilha e a execuo recomea do ponto da chamada funo dentro
da funo.

Nmeros de Fibonacci
Fibonacci (matemtico da Renascena italiana) estabeleceu uma srie curiosa de
nmeros para modelar o nmero de casais de coelhos em sucessivas geraes.
Assumindo que nas primeiras duas geraes s existe um casal de coelhos, a
seqncia de Fibonacci a seqncia de inteiros: 1, 1, 2, 3 , 5 , 8, 13 , 21,3 4, ....
No programa seguinte mostrada uma verso iterativa para calcular o n-simo termo
da seqncia de Fibonacci.
Programa: Clculo do n-simo termo de Fibonacci (verso iterativa)
int fibc (int n)
{
int l, h, x, i;
if ( n <=2 )
return 1;
l = 0;
h = 1;
for(i=2 ; i<= n; i++)
{
/* Clculo do prximo nmero da seqncia. */
x = l;
l = h;
h = x + l;
}
return h;
}
O n-simo nmero definido como sendo a soma dos dois nmeros anteriores.
Logo, fazendo a definio recursiva:
fib(n) = n se n <=
fib(n) = ib(n- ) + ib(n-1) se n >
A sua determinao recursiva impe o clculo direto do valor para dois elementos de
base (a primeira e a segunda gerao). No programa seguintes mostrada a verso
recursiva para calcular o n-simo termo da seqncia de Fibonacci.
Programa: Clculo do n-simo termo de Fibonacci (verso recursiva)
int fibr (int n )

29
Prof: Jos Matias Pedro

{
if ( n <= )
{
return 1;
}
/* chama a si prprio 2 vezes!!! */
return fibr (n-1) + fibr (n-2 );
}

Esta soluo (programa: Clculo do n-simo termo de Fibonacci (verso recursiva)


) muito mais simples de programar do que a verso iterativa (programa: Clculo do
n-simo termo de Fibonacci (verso iterativa)). Contudo, esta verso ineficiente, pois
cada vez que a funo fibr chamada, a dimenso do problema reduz-se apenas
uma unidade (de n para n-1), mas so feitas duas chamadas recursivas. Isto d
origem a uma exploso combinatorial e o computador acaba por ter de calcular o
mesmo termo vrias vezes.
Para calcular fibr (5) necessrio calcular fibr (4) e fibr (3). Conseqentemente, para
calcular fibr(4) preciso calcular fibr (3) e fibr (2). E assim sucessivamente. Este tipo
de processamento inadequado, j que o computador obrigado a fazer trabalho
desnecessrio. No exemplo, usando o programa(Clculo do n-simo termo de
Fibonacci (verso recursiva)), para calcular fibr(5) foi preciso calcular fibr(4) 1 vez,
fibr(3) 2 vezes, fibr(2) 3 vezes e fibr(1) 2 vezes. No programa iterativo (programa:
Clculo do n-simo termo de Fibonacci (verso iterativa), apenas era necessrio calcular
fibc(5), fibc(4), fibc(3), fibc(2) e fibc(1) 1 vez.

Dados Heterogneos
Uma estrutura de dados chamada de heterognea quando envolve a utilizao de
mais de um tipo bsico de dado (inteiro ou caractere, por exemplo) para representar
uma estrutura de dados. Normalmente, este tipo de dado chamado de registro.
Um registro uma estrutura de dados que agrupa dados de tipos distintos ou, mais
raramente, do mesmo tipo. Um registro de dados composto por certo nmero de
campos de dados, que so itens de dados individuais. Registros so conjuntos de
dados logicamente relacionados, mas de tipos diferentes (numricos, lgicos,
caractere etc).
O conceito de registro visa facilitar o agrupamento de variveis que no so do
mesmo tipo, mas que guardam estreita relao lgica. Registros correspondem a
conjuntos de posies de memria conhecidos por um mesmo nome e
individualizados por identiicadores associados a cada conjunto de posies.
O registro um caso mais geral de varivel composta na qual os elementos do
conjunto no precisam ser, necessariamente, homogneos ou do mesmo tipo. O
registro constitudo por componentes. Cada tipo de dado armazenado em um
registro chamado de campo.
A linguagem C utiliza as estruturas para representar um registro. Com a estrutura
definida pode-se fazer atribuio de variveis do mesmo tipo de maneira simpliicada.
Programa: Exemplo de estrutura

struct Funcionario
{
char nome [40];
struct
{

30
Prof: Jos Matias Pedro

int dia;
int mes;
int ano;
} dataNasc;
char departamento[10];
float salario;
};
Para se fazer o acesso de um nico campo deve-se utilizar o nome da estrutura
seguido de um ponto e do nome do campo desejado da estrutura. A linguagem C
tambm permite que seja criado um vetor de estruturas (programa).
Programa : Exemplo de uso de estruturas com vetores
/* programa_estrutura */
#include <stdio.h>
struct DADO
{
char sNome[40];
int iIdade;
};
int main(void)
{
Struct DADO sDados [5];
/* A estrutura dividida em duas partes por um ponto (.). Tem-se o nome da estrutura
esquerda e o nome do campo direita. Neste exemplo, como est sendo manipulado um
vetor de estruturas, tambm tem ndice para cada linha do vetor. */
for(iIndice=0;iIndice<5 ;iIndice++)
{
printf("\nEntre com o Nome ->" );
scanf("%s", &sDados[iIndice].sNome );
printf("Entre com a Idade ->" );
scanf("%d", &sDados[iIndice].iIdade );
}
for(iIndice=0;iIndice<5 ;iIndice++)
{
printf("\n%s tem %d anos", sDados[iIndice].sNome, sDados[iIndice].iIdade);
}
return;
}
Lembrete: Estruturas so utilizadas para referenciar mltiplos tipos de dados.

Programa: insero numa lista


# include<stdio.h>
# include<stdlib.h>
# include <string.h>
#define MAX 60
#define LINHA 100
Struct aluno {
Int numero;
Char nome [MAX];
Struct aluno * prox;
};
Typedef struct aluno ALUNO;
Typedef ALUNO * Paulo;

31
Prof: Jos Matias Pedro

5. Estruturas de Dados Dinmicos Lineares


5.1. Listas
As listas so estruturas lineares de tamanho variavel, o que confere uma grande
flexibilidade para acomodar novos elementos, mas em contrapartida tm tempo de
Acesso relactivamente elevados comparando com as estruturas vetoriais.
Os elementos de uma lista so blocos autnomos que contm dados e informao de
ligao para os elementos adjacentes. As listas podem ser simplesmente ligadas,
contendo apenas o endereo do elemento seguinte; ou duplamente ligadas, contendo
o endereo do elemento anterior e do elemento seguinte.
As listas simplesmente ligadas tm uma estrutura mais simples, mas em certos casos,
obrigam a processamento mais morosos.
Na linguagem C, os vetores podem ser utilizados para representar uma lista, mas a
lista tambm pode ser implementada atravs de estruturas com alocao dinmica
de memria.
Na implementao de listas em C normalmente so utilizadas estruturas que, alm
de conter as informaes necessrias - cdigos, nomes etc. -, tero campos
adicionais para controle da prpria lista. Os campos adicionais de controle de lista
so ponteiros para a prpria lista (isto possvel atravs da capacidade da linguagem
de definio recursiva). Na manipulao de lista simples exigida a criao de um
ponteiro para manter o incio da lista.
Considere-se que se pretende criar uma lista de alunos apenas com informao sobre
o seu nmero de alunos e nome. A estrutura de cada elemento da lista pode ter o
formato apresentado no cdigo seguinte.
struct aluno {
int numero;
char nome [60];
Struct aluno* prox;
};
Typedef struct aluno ALUNO;
No cdigo anterior criada a estrutura struct aluno com o campo prox que indica qual
o proximo elemento da lista. tambm criado o nome ALUNO que pode ser usado
para substituir o tipo struct aluno

Gesto de uma Lista


como foi explicado anteriormente, uma lista um conjunto de elementos ligados entre
si por um campo que contm o endereo do proximo elemento. Mas faltam ainda
resolver os dois problemas seguintes:
Como aceder ao primeiro elemento da lista
Para onde apontar o campo prox do ultimo elemento da lista.
Para aceder ao primeiro elemento da lista necessrio criar uma variavel que
armazene o endereo do primeiro elemento. Por outro lado, o campo prox do ultimo
elemento tem que conter um valor que indique que no existem mais elementos na
lista.

Insero no inicio da lista


Uma lista uma estrutura de dados de dimenso variavel, que pode crescer ou
diminuir durante a execuo de um programa. Os elementos de uma lista so

32
Prof: Jos Matias Pedro

colocados num espao de memria fora do espao atribuido inicialmente ao


programa.

Impresso da lista
para imprimir sequencialmente todos os elementos de uma lista, pode percorrer-se a
lista do inicio at ao fim e imprimir cada elemento. A soluo usual, nestes casos,
utilizar um apontador auxiliar que, de inicio, tem o endereo da cabea da lista, e
depois vai recebendo sucessivamente o endereo do elemento seguinte

insero no fim da lista


para inserir um elemento no fim de uma lista necessrio percorrer a lista desde o
inicio at ao ltimo elemento.

Multiplas listas
A soluo anterior foi criada com o fito de ser simples, mas peca por falta de
generecidade. Com essa soluo, no caso de se pretender trabalhar com duas listas
de alunos em simultneo apontadas, por exemplo, pelas variveis turma1 e turma2,
necessario duplicar as funes de insero e impresso e substituir numas a
varivel cabea por turma1 e nas outras por turma2.
Para resolver este problema de falta de generecidade, deve passar-se a cabea da
lista, por parmetro, para as funes de insero e impresso.
A funo de impresso da lista apenas consulta a cabea no a modifica portanto
no levanta problemas adicionais.

Remoo
A remoo de um elemento de uma lista duplamente ligada uma operao
relactivamente simples de executar.
Note-se que necessrio tratar de forma diferente os casos extremos de remoo do
primeiro elemento e do ltimo elemento de uma lista.

5.2. Pilha
Uma das estruturas de dados mais simples a pilha. Possivelmente por essa razo,
a estrutura de dados mais utilizada em programao, sendo inclusive implementada
diretamente pelo hardware da maioria das mquinas modernas. A idia fundamental
da pilha que todo o acesso a seus elementos feito atravs do seu topo. Assim,
quando um elemento novo introduzido na pilha, passa a ser o elemento do topo, e
o nico elemento que pode ser removido da pilha o do topo. Isto faz com que os
elementos da pilha sejam retirados na ordem inversa ordem em que foram
introduzidos: o primeiro que sai o ltimo que entrou (a sigla LIFO last in, first out
usada para descrever esta estratgia).
Para entendermos o funcionamento de uma estrutura de pilha, podemos fazer uma
analogia com uma pilha de pratos. Se quisermos adicionar um prato na pilha, o
colocamos no topo. Para pegar um prato da pilha, retiramos o do topo. Assim, temos
que retirar o prato do topo para ter acesso ao prximo prato. A estrutura de pilha
funciona de maneira anloga. Cada novo elemento inserido no topo e s temos
acesso ao elemento do topo da pilha.
Existem duas operaes bsicas que devem ser implementadas numa estrutura de
pilha: a operao para empilhar um novo elemento, inserindo-o no topo, e a operao
para desempilhar um elemento, removendo-o do topo. comum nos referirmos a
essas duas operaes pelos termos em ingls push (empilhar) e pop (desempilhar).

33
Prof: Jos Matias Pedro

Interface do tipo pilha


Neste captulo, consideraremos duas implementaes de pilha: usando vetor e
usando lista encadeada. Para simplificar a exposio, consideraremos uma pilha que
armazena valores reais. Independente da estratgia de implementao, podemos
definir a interface do tipo abstrato que representa uma estrutura de pilha. A interface
composta pelas operaes que estaro disponibilizadas para manipular e acessar
as informaes da pilha. Neste exemplo, vamos considerar a implementao de cinco
operaes:
criar uma estrutura de pilha;
inserir um elemento no topo (push);
remover o elemento do topo (pop);
verificar se a pilha est vazia;
liberar a estrutura de pilha.
O arquivo pilha.h, que representa a interface do tipo, pode conter o seguinte cdigo:
typedef struct pilha Pilha;
Pilha* cria (void);
Void push (Pilha* p, float v);
float pop (Pilha* p);
int vazia (Pilha* p);
void libera (Pilha* p);
A funo cria aloca dinamicamente a estrutura da pilha, inicializa seus campos e
retorna seu ponteiro; as funes push e pop inserem e retiram, respectivamente, um
valor real na pilha; a funo vazia informa se a pilha est ou no vazia; e a funo
libera destri a pilha, liberando toda a memria usada pela estrutura.

Implementao de pilha com vetor


Em aplicaes computacionais que precisam de uma estrutura de pilha, comum
sabermos de antemo o nmero mximo de elementos que podem estar
armazenados simultaneamente na pilha, isto , a estrutura da pilha tem um limite
conhecido. Nestes casos, a implementao da pilha pode ser feita usando um vetor.
A implementao com vetor bastante simples. Devemos ter um vetor (vet) para
armazenar os elementos da pilha. Os elementos inseridos ocupam as primeiras
posies do vetor. Desta forma, se temos n elementos armazenados na pilha, o
elemento vet[n-1] representa o elemento do topo.
A estrutura que representa o tipo pilha deve, portanto, ser composta pelo vetor e pelo
nmero de elementos armazenados.
#define MAX 50
Struct pilha {
int n;
float vet[MAX];
};
A funo para criar a pilha aloca dinamicamente essa estrutura e inicializa a pilha
como sendo vazia, isto , com o nmero de elementos igual a zero.
Pilha* cria (void)
{
Pilha* p = (Pilha*) malloc(sizeof(Pilha));
p->n = 0; /* inicializa com zero elementos */
return p;
}

34
Prof: Jos Matias Pedro

Para inserir um elemento na pilha, usamos a prxima posio livre do vetor. Devemos
ainda assegurar que exista espao para a insero do novo elemento, tendo em vista
que trata-se de um vetor com dimenso fixa.
void push (Pilha* p, float v)
{
if (p->n == MAX) { /* capacidade esgotada */
printf("Capacidade da pilha estourou.\n");
exit(1); /* aborta programa */
}
/* insere elemento na prxima posio livre */
p->vet[p->n] = v;
p->n++;
}
A funo pop retira o elemento do topo da pilha, fornecendo seu valor como retorno.
Podemos tambm verificar se a pilha est ou no vazia.
float pop (Pilha* p)
{
float v;
if (vazia(p)) {
printf("Pilha vazia.\n");
exit(1); /* aborta programa */
}
/* retira elemento do topo */
v = p->vet[p->n-1];
p->n--;
return v;
}
A funo que verifica se a pilha est vazia pode ser dada por:
int vazia (Pilha* p)
{
return (p->n == 0);
}
Finalmente, a funo para liberar a memria alocada pela pilha pode ser:
void libera (Pilha* p)
{
free(p);
}

Implementao de pilha com lista


Quando o nmero mximo de elementos que sero armazenados na pilha no
conhecido, devemos implementar a pilha usando uma estrutura de dados dinmica,
no caso, empregando uma lista encadeada. Os elementos so armazenados na lista
e a pilha pode ser representada simplesmente por um ponteiro para o primeiro n da
lista.
O n da lista para armazenar valores reais pode ser dado por:
struct no {
float info;
struct no* prox;
};
typedef struct no No;
A estrutura da pilha ento simplesmente:

35
Prof: Jos Matias Pedro

struct pilha {
No* prim;
};
A funo cria aloca a estrutura da pilha e inicializa a lista como sendo vazia.
Pilha* cria (void)
{
Pilha* p = (Pilha*) malloc(sizeof(Pilha));
p->prim = NULL;
return p;
}
O primeiro elemento da lista representa o topo da pilha. Cada novo elemento
inserido no incio da lista e, conseqentemente, sempre que solicitado, retiramos o
elemento tambm do incio da lista. Desta forma, precisamos de duas funes
auxiliares da lista: para inserir no incio e para remover do incio. Ambas as funes
retornam o novo primeiro n da lista.

/* funo auxiliar: insere no incio */


No* ins_ini (No* l, float v)
{
No* p = (No*) malloc (sizeof (No));
p->info = v;
p->prox = l;
return p;
}
/* funo auxiliar: retira do incio */
No* ret_ini (No* l)
{
No* p = l->prox;
free (l);
return p;
}
As funes que manipulam a pilha fazem uso dessas funes de lista:
void push (Pilha* p, float v)
{
p->prim = ins_ini(p->prim,v);
}
float pop (Pilha* p)
{
float v;
if (vazia(p)) {
printf("Pilha vazia.\n");
exit(1); /* aborta programa */
}
v = p->prim->info;
p->prim = ret_ini(p->prim);
return v;
}
A pilha estar vazia se a lista estiver vazia:
int vazia (Pilha* p)
{
return (p->prim==NULL);

36
Prof: Jos Matias Pedro

}
Por fim, a funo que libera a pilha deve antes liberar todos os elementos da lista.
void libera (Pilha* p)
{
No* q = p->prim;
while (q!=NULL) {
No* t = q->prox;
free (q);
q = t;
}
free(p);
}
A rigor, pela definio da estrutura de pilha, s temos acesso ao elemento do topo.
No entanto, para testar o cdigo, pode ser til implementarmos uma funo que
imprima os valores armazenados na pilha. Os cdigos abaixo ilustram a
implementao dessa funo nas duas verses de pilha (vetor e lista). A ordem de
impresso adotada do topo para a base.
/* imprime: verso com vetor */
void imprime (Pilha* p)
{
int i;
for (i=p->n-1; i>=0; i--)
printf("%f\n",p->vet[i]);
}
/* imprime: verso com lista */
void imprime (Pilha* p)
{
No* q;
for (q=p->prim; q!=NULL; q=q->prox)
printf("%f\n",q->info);
}

5.3. Filas
Uma fila um conjunto ordenado de itens a partir do qual se podem eliminar itens
numa extremidade - incio da fila - e no qual se podem inserir itens na outra
extremidade - final da fila.
Ela uma prima prxima da pilha, pois os itens so inseridos e removidos de cordo
com o princpio de que o primeiro que entra o primeiro que sai -first in, first out (FIFO). O
conceito de fila existe no mundo real, vide exemplos como filas de banco, pedgios,
restaurantes etc. As operaes bsicas de uma fila so:
insert ou enqueue - insere itens numa fila (ao final).
remove ou dequeue - retira itens de uma fila (primeiro item).
empty - verifica se a fila est vazia.
size - retorna o tamanho da fila.
front - retorna o prximo item da fila sem retirar o mesmo da fila.
A operao insert ou enqueue sempre pode ser executada, uma vez que
teoricamente uma fila no tem limite. A operao remove ou dequeue s pode ser
aplicado se a fila no estiver vazia, causando um erro de underlow ou fila vazia se esta
operao for realizada nesta situao.

37
Prof: Jos Matias Pedro

Filas em C
A exemplo do que ocorre com estrutura em pilha, antes de programar a soluo de
um problema que usa uma fila, necessrio determinar como representar uma fila
usando as estruturas de dados existentes na linguagem de programao. Novamente
na linguagem C podemos usar um vetor. Mas a fila uma estrutura dinmica e pode
crescer infinitamente, enquanto que um vetor na linguagem C tem um tamanho fixo.
Contudo, pode-se definir este vetor com um tamanho suficientemente grande para
conter a fila.

5.4. rvores
Nos captulos anteriores examinamos as estruturas de dados que podem ser
chamadas de unidimensionais ou lineares, como vetores e listas. A importncia
dessas estruturas inegvel, mas elas no so adequadas para representarmos
dados que devem ser dispostos de maneira hierrquica. Por exemplo, os arquivos
(documentos) que criamos num computador so armazenados dentro de uma
estrutura hierrquica de diretrios (pastas). Existe um diretrio base dentro do qual
podemos armazenar diversos subdiretrios e arquivos. Por sua vez, dentro dos sub-
diretrios, podemos armazenar outros sub-diretrios e arquivos, e assim por diante,
recursivamente. A Figura seguinte mostra uma imagem de uma rvore de diretrio no
Windows 2000.

Figura: Um exemplo de rvore de diretrio.

Neste captulo, vamos introduzir rvores, que so estruturas de dados adequadas para
a representao de hierarquias. A forma mais natural para definirmos uma estrutura
de rvore usando recursividade. Uma rvore composta por um conjunto de ns.
Existe um n r, denominado raiz, que contm zero ou mais sub-rvores, cujas razes
so ligadas diretamente a r. Esses ns razes das sub-rvores so ditos filhos do n
pai, r. Ns com filhos so comumente chamados de ns internos e ns que no tm
filhos so chamados de folhas, ou ns externos. tradicional desenhar as rvores
com a raiz para cima e folhas para baixo, ao contrrio do que seria de se esperar.

Sub-rvores

Estrutura de rvore

38
Prof: Jos Matias Pedro

Observamos que, por adotarmos essa forma de representao grfica, no


representamos explicitamente a direo dos ponteiros, subentendendo que eles
apontam sempre do pai para os filhos.

rvores AVL
As rvores AVL so rvores binarias ordenadas e equilibradas. O nome AVL
composto pelas iniciais dos apelidos dos matemticos G.M.Adelson-Velskii e E. M.
Landis que propuseram este tipo de estruturas pela primeira vez, em 1962.

rvores binrias
Um exemplo de utilizao de rvores binrias est na avaliao de expresses. Como
trabalhamos com operadores que esperam um ou dois operandos, os ns da rvore
para representar uma expresso tm no mximo dois filhos. Nessa rvore, os ns
folhas representam operandos e os ns internos operadores. Uma rvore que
representa, por exemplo a expresso (3+6)*(4-1)+5 ilustrada na Figura seguinte.

*
5
+

+
-

3 6 4 1
+ +
rvore da expresso: (3+6) * (4-1) + 5.
Numa rvore binria, cada n tem zero, um ou dois filhos. De maneira recursiva,
podemos definir uma rvore binria como sendo:
uma rvore vazia; ou
um n raiz tendo duas sub-rvores, identificadas como a sub-rvore da direita
(sad) e a sub-rvore da esquerda (sae).
A Figura a seguir ilustra uma estrutura de rvore binria. Os ns a, b, c, d, e, f formam
uma rvore binria da seguinte maneira: a rvore composta do n a, da subrvore
esquerda formada por b e d, e da sub-rvore direita formada por c, e e f. O n a
representa a raiz da rvore e os ns b e c as razes das sub-rvores. Finalmente, os
ns d, e e f so folhas da rvore.

39
Prof: Jos Matias Pedro

a
A
F
b i c
F g F
i u i
g r g
d a e u f
u
F Exemplo1de rvore
F binria
r F
r
Representao em C ai 3 i a i
Anlogo ao que fizemos para g as demais
. gestruturas
1 degdados, podemos definir um tipo
1
para representar uma rvore u binria. Para
u simplificar ua discusso, vamos considerar
3 5 3
que a informao que queremos
r armazenar
r nos rns da rvore so valores de
caracteres simples. Vamos a
. inicialmente .
discutir como podemos representar uma
a s a 5 a
5
estrutura de rvore binria em C. Que estrutura podemos usar para representar um
1 e 1 a 1
n da rvore? a
Cada n deve armazenars3trs informaes:
g 3 a sinformao3 propriamente dita, no caso
.
um caractere, e dois ponteiros parau as . e
sub-rvores, . esquerda e direita. Ento a
e
5
estrutura de C para representar o n 5 5
i da rvoreg pode ser dada por:
g
struct arv { a r a u a
u
char info; s s s
i i
struct arv* esq; ie e e
struct arv* dir; r l r
g u g i g
}; i
u u u
Da mesma forma que uma s
l lista encadeada l representada por um ponteiro para o
primeiro n, a estrutura dauirvore como
t um i todo i
u representada por um ponteiro para
o n raiz. r r r s r
s
Como acontece com qualquer i TAD a(tipo iabstrato i
t de dados), as operaes que fazem
t
l
sentido para uma rvorerbinria dependem
u l l
essencialmente
r da forma de utilizao
que se pretende fazer da urvore. Nas m u
funes a que u
se seguem, consideraremos que
a
existe o tipo Arv definido por:
s s s
a u
typedef struct arv Arv; ut t t
Como veremos as funes e
m que manipulam m
rvores so,
r s r a r em geral, implementadas de
forma recursiva, usando aa definio recursiva da estrutura.
a a
t apenas a
e operaes
Vamos procurar identificar e e descrever cuja utilidade seja a mais
geral possvel. Uma operaou u s
que rprovavelmente u
dever ser includa em todos os
s
casos a inicializao detmuma rvoreu m
vazia. Como
t m
uma rvore representada pelo
endereo do n raiz, umaarvore vazia t atem que a
r ser representada pelo valor NULL.
r
e uma rvore
Assim, a funo que inicializa
u u e u podeeser simplesmente:
vazia
Arv* inicializa(void) s s s
t r t
{ t t t
return NULL; u a u
r d r r r
} r
u podemos e uter uma u
a operao
a
Para criar rvores no vazias, que cria um n raiz dadas
t
a informao e suas duasdsub-rvores, t
esquerda d t
e direita. Essa funo tem como
valor de retorno o endereo u do n raiz
r u
criado e pode user dada por:
e
Arv* cria(char c, Arv* sae, r Arv* sad){
v r r

Arv* p= (Arv*) malloca (sizeof
o
(Arv));
a r a
r
d r d v d
v
e e e o e
o 40
b r
r
r i r e r
e
Prof: Jos Matias Pedro

p->info = c;
p->esq = sae;
p->dir = sad;
return p;
}
As duas funes inicializa e cria representam os dois casos da definio recursiva
de rvore binria: uma rvore binria (Arv* a;) vazia (a = inicializa();) ou
composta por uma raiz e duas sub-rvores (a = cria(c,sae,sad);). Assim, com posse
dessas duas funes, podemos criar rvores mais complexas.

rvores Binrias de Pesquisa


Para a busca em uma rvore binria por um valor especico deve-se examinar a raiz. Se o
valor for igual raiz, o valor existe na rvore. Se o valor for menor do que a raiz, ento deve-
se buscar na subrvore da esquerda, e assim recursivamente em todos os ns da subrvore.
Similarmente, se o valor for maior que a raiz, ento deve-se buscar na subrvore da direita.
At alcanar o n-folha da rvore, encontrando-se ou no o valor requerido. Esta operao
efetua log n operaes no caso mdio e n no pior caso quando a rvore est desequilibrada;
neste caso, a rvore considerada uma rvore degenerada.

6. Algoritmos de ordenamento
Ordenao o processo de arranjar um conjunto de informaes semelhantes em
uma ordem crescente ou decrescente. Especificamente, dada uma lista ordenada i
de n elementos, ento: i1 <= i2 <= ... <= In. Algoritmo de ordenao em cincia da
computao um algoritmo que coloca os elementos de uma dada sequncia em
uma certa ordem - em outras palavras, efetua sua ordenao completa ou parcial. As
ordens mais usadas so a numrica e a lexicogrfica.
Existem vrias razes para se ordenar uma sequncia, uma delas a possibilidade
se acessar seus dados de modo mais eficiente.

6.1. Bubble Sort


O algoritmo de ordenao BubbleSort um mtodo simples de ordenao por troca.
Sua popularidade vem do seu nome fcil e de sua simplicidade. Porm, uma das
piores ordenaes j concebidas. Ela envolve repetidas comparaes e, se
necessrio, a troca de dois elementos adjacentes.
Inicialmente percorre-se a lista da esquerda para a direita, comparando pares de elementos
consecutivos, trocando de lugar os que esto fora de ordem. Atabela abaixo exemplifica o
mtodo BubbleSort.
BubbleSort: primeira varredura (tabela n 1)
Troca L[1] L[2] L[3] L[4] L[5]
1 com 2 10 9 7 13 5
2 com 3 9 10 7 13 5
4 com 5 9 7 10 5 13
Fim da Varredura 9 7 10 5 13

Aps a primeira varredura ( tabela n 1 ), o maior elemento encontra-se alocado em sua


posio definitiva na lista ordenada. Logo, a ordenao pode continuar no restante da lista
sem considerar o ltimo elemento (tabela n 2 ).
Na segunda varredura, o segundo maior elemento encontra-se na sua posio
definitiva e o restante da ordenao realizada considerando apenas os ltimos
elementos (7, 9 e 5 ). Logo so necessrios elementos - 1 varreduras, pois cada
varredura leva um elemento para sua posio definitiva.

41
Prof: Jos Matias Pedro

BubbleSort - segunda varredura


Troca L[1] L[2] L[3] L[4] L[5]
Troca 1 com 2 9 7 10 5 13
Troca 3 com 4 7 9 10 5 13
Fim da varredura 7 9 5 10 13

6.2. QuickSort
O algoritmo QuickSort do tipo diviso e conquista. Um algoritmo deste tipo resolve
vrios problemas quebrando um determinado problema em mais (e menores)
subproblemas.
O algoritmo, publicado pelo professor C.A.R. Hoare em 1962 , baseia-se na idia
simples de partir um vetor (ou lista a ser ordenada) em dois subvetores, de tal maneira
que todos os elementos do primeiro vetor sejam menores ou iguais a todos os
elementos do segundo vetor. Estabelecida a diviso, o problema estar resolvido,
pois aplicando recursivamente a mesma tcnica a cada um dos subvetores, o vetor
estar ordenado ao se obter um subvetor de apenas 1 elemento.
Os passos para ordenar uma sequncia S = {a1; a2 ; a3 ; ; an} dado por:
Seleciona um elemento do conjunto S. O elemento selecionado (p) chamado
de piv.
Retire p de S e particione os elementos restantes de S em seqncias distintas,
L e G.
A partio L dever ter os elementos menores ou iguais ao elemento piv p,
enquanto que a partio G conter os elementos maiores ou iguais a p.
Aplique novamente o algoritmo nas parties L e G.

Para organizar os itens, tais que os menores fiquem na primeira partio e os maiores
na segunda partio, basta percorrer o vetor do incio para o fim e do fim para o incio
simultaneamente, trocando os elementos. Ao encontrar-se no meio da lista, tem-se a
certeza de que os menores esto na primeira partio e os maiores na segunda
partio.
A figura seguinte, ilustra o que se passa quando se faz a partio de um vetor com a
sequncia de elementos S = { 7, 1, 3, 9, 8, 4, 2, 7, 4, 2, 3, 5 }. Neste caso o piv 4, pois o
valor do elemento que est na sexta posio (e 6 igual a 1 +12/2).
A escolha do elemento piv arbitrria, pegar o elemento mdio apenas uma das
possveis implementaes no algoritmo. Outro mtodo para a escolha do piv
consiste em escolher trs (ou mais) elementos randomicamente da lista, ordenar esta
sublista e pegar o elemento mdio.
Primeira troca: posies 1 e 11. Piv= 4.
7 1 3 9 8 4 2 7 4 2 3 5
Segunda troca: posies 4 e 10
3 1 3 9 8 4 2 7 4 2 7 5
Terceira troca: posies 5 e 9
3 1 3 2 8 4 2 7 4 9 7 5
Quarta troca: posies 6 e 7
3 1 3 2 4 4 2 7 8 9 7 5

3 1 3 2 4 2 4 7 8 9 7 5
Os precursos cruzaram-se; agora repete-se o procedimento recursivamente, para os
subvetores entre as posies 1 e 6 e entre as posies 7 e 12.

42
Prof: Jos Matias Pedro

O QuickSort pode ser implementado pelos algoritmos. O tempo de execuo do


algoritmo depende do fato de o particionamento ser balanceado ou no, e isso por
sua vez depende de quais elementos so usados para particionar. Se o valor do piv,
para cada partio, for o maior valor, ento o algoritmo se tornar numa ordenao
lenta com um tempo de processamento n2.
A complexidade dada por n log2 n no melhor caso e caso mdio. O pior caso dado
n2 comparaes.

6.3. MergeSort
Como o algoritmo QuickSort, o MergeSort outro exemplo de algoritmo do tipo diviso
e conquista, sendo um algoritmo de ordenao por intercalao ou segmentao. A
idia bsica a facilidade de criar uma seqncia ordenada a partir de duas outras
tambm ordenadas. Para isso, o algoritmo divide a seqncia original em pares de
dados, ordena-as; depois as agrupa em sequncias de quatro elementos, e assim por
diante, at ter toda a seqncia dividida em apenas duas partes.
Ento, os passos para o algoritmo so:
Dividir uma seqncia em duas novas seqncias.
Ordenar, recursivamente, cada uma das seqncias (dividindo novamente, quando
possvel).
Combinar (merge) as subseqncias para obter o resultado final.
Nas figuras seguintes podem ser vistos exemplos de ordenao utilizando os passos do
algoritmo.

3 1 4 1 5 9 2 6 5 4

3 1 4 1 5 9 2 6 5 4

3 1 4 1 5 9 2 6 5 4

3 1 4 1 5 9 2 6 5 4

1 3 1 5 2 9 6 5 4

4 5
1 5

1 4 5 4 5 6

1 1 3 4 5 2 4 5 6 9

1 1 2 3 4 4 5 5 6 9

Exemplo: Ordenao MergeSort

43
Prof: Jos Matias Pedro

A complexidade do algoritmo dada por n log n em todos os casos. A desvantagem


deste algoritmo precisar de uma lista (vetor) auxiliar para realizar a ordenao,
ocasionando em gasto extra de memria, j que a lista auxiliar deve ter o mesmo
tamanho da lista original.

7. Tabela de Disperso
Neste captulo, vamos estudar as estruturas de dados conhecidas como tabelas de
disperso (hash tables), que, se bem projetadas, podem ser usadas para buscar um
elemento da tabela em ordem constante: O(1). O preo pago por essa eficincia ser
um uso maior de memria, mas, como veremos, esse uso excedente no precisa ser
to grande, e proporcional ao nmero de elementos armazenados.
Para apresentar a idia das tabelas de disperso, vamos considerar um exemplo
onde desejamos armazenar os dados referentes aos alunos de uma disciplina. Cada
aluno individualmente identificado pelo seu nmero de matrcula. Podemos ento
usar o nmero de matrcula como chave de busca do conjunto de alunos
armazenados. Na UMN, o nmero de matrcula dos alunos dado por uma seqncia
de oito dgitos, sendo que o ltimo representa um dgito de controle, no sendo
portanto parte efetiva do nmero de matrcula. Por exemplo, na matricula 9711234-4,
o ultimo dgito 4, aps o hfen, representa o dgito de controle. O nmero de matrcula
efetivo nesse caso composto pelos primeiros sete dgitos: 9711234.
Para permitir um acesso a qualquer aluno em ordem constante, podemos usar o
nmero de matrcula do aluno como ndice de um vetor vet. Se isso for possvel,
acessamos os dados do aluno cuja matrcula dado por mat indexando o vetor
vet[mat]. Dessa forma, o acesso ao elemento se d em ordem constante, imediata. O
problema que encontramos que, nesse caso, o preo pago para se ter esse acesso
rpido muito grande.
Vamos considerar que a informao associada a cada aluno seja representada pela
estrutura abaixo:
struct aluno {
int mat;
char nome[81];
char email[41];
char turma;
};
typedef struct aluno Aluno;
Como a matrcula composta por sete dgitos, o nmero inteiro que conceitualmente
representa uma matrcula varia de 0000000 a 9999999. Portanto, precisamos
dimensionar nosso vetor com dez milhes (10.000.000) de elementos. Isso pode ser
feito por:
#define MAX 10000000
Aluno vet[MAX];
Dessa forma, o nome do aluno com matrcula mat acessado simplesmente por:
vet[mat].nome. Temos um acesso rpido, mas pagamos um preo em uso de
memria proibitivo. Como a estrutura de cada aluno, no nosso exemplo, ocupa pelo
menos 127 bytes, estamos falando num gasto de 1.270.000.000 bytes, ou seja, acima
de 1 Gbyte de memria. Como na prtica teremos, digamos, em torno de 50 alunos
cadastrados, precisaramos apenas de algo em torno de 6.350 (=127*50) bytes.
Para amenizar o problema, j vimos que podemos ter um vetor de ponteiros,em vez
de um vetor de estruturas. Desta forma, as posies do vetor que no correspondem
a alunos cadastrados teriam valores NULL. Para cada aluno cadastrado, alocaramos

44
Prof: Jos Matias Pedro

dinamicamente a estrutura de aluno e armazenaramos um ponteiro para essa


estrutura no vetor. Neste caso, acessaramos o nome do aluno de matrcula mat por
vet[mat]->nome. Assim, considerando que cada ponteiro ocupe 4 bytes, o gasto
excedente de memria seria, no mximo, aproximadamente 40 Mbytes. Apesar de
menor, esse gasto de memria ainda proibitivo. A forma de resolver o problema de
gasto excessivo de memria, mas ainda garantindo um acesso rpido, atravs do
uso de tabelas de disperso (hash table) que vamos tratar:
Idia central
A idia central por trs de uma tabela de disperso identificar, na chave de busca,
quais as partes significativas. Na UMN, por exemplo, alm do dgito de controle,
alguns outros dgitos do nmero de matrcula tm significados especiais, conforme
ilustra a Figura seguinte:
9 7 1 1 2 3 4_4

Indicadores sequenciais

Periodo de ingresso

Ano de ingresso

Numa turma de aluno, comum existirem vrios alunos com o mesmo ano e perodo
de ingresso. Portanto, esses trs primeiros dgitos no so bons candidatos para
identificar individualmente cada aluno. Reduzimos nosso problema para uma chave
com os quatro dgitos seqenciais. Podemos ir alm e constatar que os nmeros
seqenciais mais significativos so os ltimos, pois num universo de uma turma de
alunos, o dgito que representa a unidade varia mais do que o dgito que representa
o milhar.
Desta forma, podemos usar um nmero de matrcula parcial, de acordo com a
dimenso que queremos que tenha nossa tabela (ou nosso vetor). Por exemplo, para
dimensionarmos nossa tabela com apenas 100 elementos, podemos usar apenas os
ltimos dois dgitos seqenciais do nmero de matrcula. A tabela pode ento ser
declarada por:
Aluno* tab[100].
Para acessarmos o nome do aluno cujo nmero de matrcula dado por mat, usamos
como ndice da tabela apenas os dois ltimos dgitos. Isso pode ser conseguido
aplicando-se o operador modulo (%): vet[mat%100]->nome.
Desta forma, o uso de memria excedente pequeno e o acesso a um determinado
aluno, a partir do nmero de matrcula, continua imediato. O problema que surge
que provavelmente existiro dois ou mais alunos da turma que apresentaro os
mesmos ltimos dois dgitos no nmero de matrcula. Dizemos que h uma coliso,
pois alunos diferentes so mapeados para o mesmo ndice da tabela. Para que a
estrutura funcione de maneira adequada, temos que resolver esse problema, tratando
as colises.
Existem diferentes mtodos para tratarmos as colises em tabelas de disperso, e
estudaremos esses mtodos mais adiante. No momento, vale salientar que no h
como eliminar completamente a ocorrncia de colises em tabelas de disperso.
Devemos minimizar as colises e usar um mtodo que, mesmo com colises,
saibamos identificar cada elemento da tabela individualmente.
Funo de disperso
A funo de disperso (funo de hash) mapea uma chave de busca num ndice da
tabela. Por exemplo, no caso exemplificado acima, adotamos como funo de hash a
utilizao dos dois ltimos dgitos do nmero de matrcula. A implementao dessa

45
Prof: Jos Matias Pedro

funo recebe como parmetro de entrada a chave de busca e retorna um ndice da


tabela. No caso d a chave de busca ser um inteiro representando o nmero de
matrcula,essa funo pode ser dada por.
int hash (int mat)
{
return (mat%100);
}
Podemos generalizar essa funo para tabelas de disperso com dimenso N. Basta
avaliar o modulo do nmero de matrcula por N:
int hash (int mat)
{
return (mat%N);
}

Uma funo de hash deve, sempre que possvel, apresentar as seguintes


propriedades:
Ser eficientemente avaliada: isto necessrio para termos acesso rpido, pois
temos que avaliar a funo de hash para determinarmos a posio onde o elemento
se encontra armazenado na tabela.
Espalhar bem as chaves de busca: isto necessrio para minimizarmos as
ocorrncias de colises. Como veremos, o tratamento de colises requer um
procedimento adicional para encontrarmos o elemento. Se a funo de hash resulta
em muitas colises, perdemos o acesso rpido aos elementos. Um exemplo de
funo de hash ruim seria usar, como ndice da tabela, os dois dgitos iniciais do
nmero de matrcula todos os alunos iriam ser mapeados para apenas trs ou
quatro ndices da tabela.
Ainda para minimizarmos o nmero de colises, a dimenso da tabela deve guardar
uma folga em relao ao nmero de elementos efetivamente armazenados. Como
regra emprica, no devemos permitir que a tabela tenha uma taxa de ocupao
superior a 75%. Uma taxa de 50% em geral traz bons resultados. Uma taxa menor
que 25% pode representar um gasto excessivo de memria.

7.1. Tratamento de coliso


Existem diversas estratgias para tratarmos as eventuais colises que surgem
quando duas ou mais chaves de busca so mapeadas para um mesmo ndice da
tabela de hash. Nesta seo, vamos apresentar algumas dessas estratgias
comumente usadas. Para cada uma das estratgias, vamos apresentar as duas
principais funes de manipulao de tabelas de disperso: a funo que busca um
elemento na tabela e a funo que insere ou modifica um elemento. Nessas
implementaes, vamos considerar a existncia da funo de disperso que mapeia
o nmero de matrcula num ndice da tabela, vista na seo anterior.
Em todas as estratgias, a tabela de disperso em si representada por um vetor de
ponteiros para a estrutura que representa a informao a ser armazenada, no caso
Aluno. Podemos definir um tipo que representa a tabela por:
#define N 100
typedef Aluno* Hash[N];
Vale lembrar que uma tabela de disperso nunca ter todos os elementos
preenchidos (j mencionamos que uma ocupao acima de 75% eleva o nmero de
colises, descaracterizando a idia central da estrutura). Portanto, podemos garantir
que sempre existir uma posio livre na tabela.

46
Prof: Jos Matias Pedro

Na operao de busca, considerando a existncia de uma tabela j construda, se


uma chave x for mapeada pela funo de disperso (funo de hash h) para um
determinado ndice h(x), procuramos a ocorrncia do elemento a partir desse ndice,
at que o elemento seja encontrado ou que uma posio vazia seja encontrada. Uma
possvel implementao mostrada a seguir. Essa funo de busca recebe, alm da
tabela, a chave de busca do elemento que se busca, e tem como valor de retorno o
ponteiro do elemento, se encontrado, ou NULL no caso do elemento no estar
presente na tabela.
Aluno* busca (Hash tab, int mat)
{
int h = hash(mat);
while (tab[h] != NULL) {
if (tab[h]->mat == mat)
return tab[h];
h = (h+1) % N;
}
return NULL;
}
Devemos notar que a existncia de algum elemento mapeado para o mesmo ndice
no garante que o elemento que buscamos esteja presente. A partir do ndice
mapeado, temos que buscar o elemento utilizando, como chave de comparao, a
real chave de busca, isto , o nmero de matrcula completo.
A funo que insere ou modifica um determinado elemento tambm simples.
Fazemos o mapeamento da chave de busca (no caso, nmero de matrcula) atravs
da funo de disperso e verificamos se o elemento j existe na tabela. Se o elemento
existir, modificamos o seu contedo; se no existir, inserimos um novo na primeira
posio que encontrarmos na tabela, a partir do ndice mapeado. Uma possvel
implementao dessa funo mostrada a seguir. Essa funo recebe como
parmetros a tabela e os dados do elemento sendo inserido (ou os novos dados de
um elemento j existente). A funo tem como valor de retorno o ponteiro do aluno
modificado ou do novo aluno inserido.
Aluno* insere (Hash tab, int mat, char* nome, char* email, char turma)
{
int h = hash (mat);
while (tab[h]!= NULL) {
if (tab[h] ->mat == mat)
break;
h = (h+1) % N;
}
if (tab[h]==NULL) { /* no encontrou o elemento */
tab[h] = (Aluno*) malloc(sizeof(Aluno));
tab[h]->mat = mat;
}
/* atribui informao */
strcpy(tab[h]->nome,nome);
strcpy (tab[h]->email,email);
tab[h]->turma = turma;
return tab[h];
}
Apesar de bastante simples, essa estratgia tende a concentrar os lugares ocupados
na tabela, enquanto que o ideal seria dispersar.

47
Prof: Jos Matias Pedro

Referncias Bibliograficas
Apostila_estrutura_dados, profs. Waldemar Celes e Jos Lucas Rangel
PUC-RIO-Curso de engenharia-2002.
Algoritmos e Estrutura de Dados I, Marcos Castilho, Fabiano Silva Daniel. Agosto de
2015.
Projecto e Analise de algoritmos: Introduo, prof. Humberto Brando.
Introduo a complexidade de algoritmos :Fernando Silva, DCC-FCUP. Estrutura de
Dados.
Linguagem C: Algoritmos de Ordenao, prof. Paulo R.S.L. Coelho.
Tabela de Disperso, W. Celes e J.L.Rangel.
Algoritmo e C: Alexandre Pereira.
WEISS, Mark Allen. Data Structures and Problem Solving Using java, 3nd Edition.
Pearson, Addison Wesley, 2006.

DROZDEK, Adam. Data Structures and Algorithms in C++. Thomson Learning lnc,
2005.

SEDGEWICK, Robert. Algorithms in C: parts 1-4, Fundamentals, Data Structures,


Sorting, Searching. Addison Wesley, 1998.

SEDGEWICK, Robert. Algorithms in C: Part 5, Graph Algorithms. Addison Wesley,


2002.

48

S-ar putea să vă placă și