Anda di halaman 1dari 16

ESTRUTURA DE DADOS

Ementa:

1. Introdução.
2. Tipos de Dados e Abstração.
3. Matrizes.
4. Listas.
5. Pilhas e Filas.
6. Árvores.
7. Grafos.
8. Ordenação e Técnicas de Busca.

Bibliografia Básica:

• SZWARCFITER, J. L. Estrutura de Dados e Seus Algoritmos. Segunda Edição. LTC, 1994.


• ZIVIANI, N. Projeto de Algoritmos com Implementações em Pascal e C. Thomson, 2003.
• TENENBAUM, A. M; LANGSAM, Y., AUGENSTEIN, M. Estruturas de Dados Usando C.
Pearson, São Paulo, 1995.
• LEISERSON, C. E.; STEIN, C.; RIVEST, R. L., CORMEN, T.H. Algoritmos: Teoria e Prática.
Tradução da 2a. edição americana. Editora Campus, 2002.
• VELOSO, P.; SANTOS, C.; AZEREDO, P.; FURTADO, A. Estruturas de Dados.
Campus, Rio de Janeiro, 1983.
• PREISS, B. R. Estrutura de Dados e Algoritmos. Campus, Rio de Janeiro, 2001.
• PEREIRA, S. L. Estruturas de Dados Fundamentais. Editora Erica, São Paulo, 1995.
• SCHILD, H. C Completo e Total. Makron Books, 1996.
• CELES, W.; CERQUEIRA, R.; RANGEL, J.L. Introdução a Estruturas de Dados: com
técnicas de programação em C. Rio de Janeiro: Campus - Elsevier. 2004.
• MORAES, R.M. Estrutura de dados e algoritmos: uma abordagem didática. São Paula:
Futura. 2003.

Bibliografia Complementar:

• KNUTH, D. E. The art of computer programming – v. 1 – Fundamental Algorithms. Addison-


Wesley, 1972.
• WIRTH, N. Algoritmos e estrutura de dados. Prentice Hall do Brasil, Rio de Janeiro, 1989.

Download:

Notas de Aula no site: http://sites.google.com/site/edlab2ufjf/.


2

ESTRUTURA DE DADOS - NOTAS DE AULA

1. INTRODUÇÃO

1.1. Revisão de Construção de Algoritmos

1.1.1. Pseudolinguagem

• Os valores no texto (estático) de um programa (ou algoritmo) podem ser representados na


forma de constante ou de variável.
- Constante: é representado em um programa diretamente pelo seu valor.
- Variável: é representada por um nome que corresponde a uma posição da memória que
contém o seu valor.

• Tipos de Dados Básicos: todo valor (constante ou variável) de um programa tem um tipo
associado que determina o conjunto de valores que podem ser assumidos e o conjunto de
operações a que pode ser submetido. Existem quatro tipos básicos:
- Tipo inteiro ou int: Um objeto do tipo int pode assumir qualquer valor inteiro. As operações
possíveis são: +, -, *, /, div, e mod. Cada uma dessas operações recebe dois argumentos
inteiros e fornece um resultado inteiro, com exceção da divisão por / que oferece um
resultado real. Os seguintes operadores relacionais podem ser aplicados a dois argumentos
inteiros para compará-los <, ≤, =, >, ≥ e ≠, sendo que nessas operações o resultado é do tipo
lógico.
- Tipo real: Um objeto do tipo real pode assumir qualquer valor real. As operações
permissíveis sempre recebem dois argumentos reais e, de acordo com o resultado, são:
 resultado real: +, -, *, / ;
 resultado lógico: <, ≤, =, >, ≥ e ≠ .

- Tipo lógico ou log: Um objeto deste tipo assume um dos valores verdadeiro ou falso. As
operações aplicáveis a dois argumentos do tipo lógico são:
 conectivos lógicos: conjunção (e, ^), disjunção (ou, ∨), disjunção exclusiva (xou, ⊕),
negação (não, ¬). A negação trabalha com somente um argumento.
 conectivos relacionais: = e ≠.
Qualquer dessas operações fornece resultado do tipo log.

- Tipo caráter (ou caracter) ou car: Os dígitos decimais, as letras, e os caracteres especiais (+,
., /, ,, etc...) constituem os chamados caracteres alfanuméricos ou conjunto de valores
onde um objeto do tipo car pode assumir o seu valor. São exemplos de constantes do tipo
car: ´7´, ´R´, ´x´, ´*´. Dois argumentos do tipo car podem ser submetidos às operações de
comparação (<, ≤, =, >, ≥ e ≠ ), que fornecem resultado lógico.

• Declaração de variáveis:
- Sintaxe: tipo : nome (s);
- Implicações da declaração de uma variável:
 alocação de um espaço na memória que possa conter um valor do seu tipo.
 associação do endereço dessa posição da memória ao nome da variável.
A partir daí, toda vez que esta variável for referenciada em qualquer comando do programa,
o computador vai trabalhar com o conteúdo de seu endereço, que é o valor da variável.
3

• Comando de atribuição:
- Sintaxe: uma variável ← expressão;
- Semântica: atribuir à variável o resultado da expressão.
- Expressões aritméticas: fornecem resultado numérico (int ou real).
 Operações básicas: +, -, *, /.
 Exponenciação: ( A + B ) ** N
 Funções matemáticas comuns: sen (X), cos (X), abs (X) (| X |), raiz (X), arctan (X),
exp (X) (eX), log (X), ln (X) etc.
 Operadores para inteiros: M mod I  resto (módulo) da divisão inteira de M por I
M div I  quociente da divisão inteira de M por I
- Expressões lógicas: fornecem resultado lógico
 Conectivos lógicos:
(e, ^)  conjunção;
(ou, ∨)  disjunção;
(xou, ⊕)  disjunção exclusiva;
(não, ¬)  negação.
 Conectivos relacionais: (<, ≤, =, >, ≥ e ≠)
- Prioridade de execução das operações em uma expressão:
1º. Parênteses (dos mais internos para os mais externos);
2º. Expressões aritméticas:
1. funções
2. **
3. * e /
4. + e -
3º. Comparações: <, ≤, =, >, ≥ e ≠
4º. não
5º. e
6º. ou e xou
7º. Da esquerda para a direita quando houver indeterminações

• Comentários: {...}

• Pseudo-comandos: “ . . . ”;
Exemplo: “Calcular o valor da raiz da equação”;

• Entrada e saída de dados:


leia ( A, X, LOGIC ); {serão lidos os valores das variáveis A, X e LOGIC, nesta ordem}
imprima (‘VALOR LIDO PARA N = ’, N , ‘ FATORIAL DE N = ’, FAT) ; {imprime a
mensagem VALOR LIDO PARA N = , o valor da variável N, a mensagem
FATORIAL DE N = e o valor da variável FAT}

• Estruturas de controle:
- Seqüência simples: conjunto de comandos, separados por ponto-e-vírgula (;), que serão
executados numa sequência linear de cima para baixo.
 Sintaxe:
C1;
C2;
...
Cn;
 Semântica: o controle de fluxo de execução entra na estrutura, executa comando por
comando, de cima para baixo e sai da estrutura.
4

- Alternativa: ocorre quando uma ação a ser executada depende de uma inspeção ou teste de
uma condição. Pode ser:
 alternativa simples:
 sintaxe: se <condição>
então
C;
fim-se;

 Semântica: o controle de fluxo de execução entra na estrutura e testa a condição. Se


ela for verdadeira, então executa a ação C e sai da estrutura. Se ela for falsa, sai da
estrutura sem executar a ação C.

 alternativa dupla:
 sintaxe:
se <condição>
então
C1 ;
senão
C2 ;
fim-se;

 Semântica: o controle de fluxo de execução entra na estrutura e testa a condição. Se


ela for verdadeira, então executa a ação C1 e sai da estrutura. Senão (se ela for falsa),
executa a ação C2 e sai da estrutura.

 alternativa múltipla escolha:


 sintaxe:
escolha < expressão >
caso V11: V12:...: C1;
caso V21: V22:...: C2;
....................
caso Vn1: Vn2:...: Cn;
senão: Cn + 1;
fim – escolha;

 Semântica: o controle de fluxo de execução entra na estrutura e calcula o resultado


da expressão. Se esse resultado for igual a V11 ou V12 ou . . ., executa a ação C1 e sai
da estrutura passando para o comando seguinte ao fim – escolha, se for igual a V21 ou
V22 ou . . ., executa a ação C2 e sai da estrutura passando para o comando seguinte ao
fim – escolha e assim por diante. Se o resultado da expressão não for encontrado,
executa a ação Cn+1 e sai da estrutura passando para o comando seguinte ao fim –
escolha.

- Repetição:
 Com teste no início:
 sintaxe:
enquanto <condição> faça
C ;
fim-enquanto;
5

 Semântica: o controle de fluxo de execução entra na estrutura e testa a condição. Se


ela for verdadeira, inicia a repetição da seguinte seqüência:
1. executa a ação C
2. quando atinge o fim-enquanto, retorna ao início da estrutura
3. testa novamente a condição
Enquanto a condição for verdadeira, a seqüência será repetida. Quando a condição
fornecer resultado falso, o controle sai da estrutura passando para o comando
seguinte ao fim-enquanto.

 Com teste no final:


 sintaxe: repita
C ;
até <condição>;

 Semântica: o controle de fluxo de execução entra na estrutura, executa a ação C e


testa a condição. Se ela for falsa, inicia a repetição da seguinte seqüência:
1. retorna ao início da estrutura
2. executa a ação C
3. testa novamente a condição
Enquanto a condição for falsa, a seqüência será repetida. Quando a condição
fornecer resultado verdadeiro, o controle sai da estrutura passando para o comando
seguinte ao até < condição >.

 Com variável de controle:


 sintaxe:
para V de I até L passo P faça
C;
fim-para ;

sendo que: V é a variável de controle


I é o valor inicial de V
L é o valor limite de V
P é o incremento sofrido por V após cada execução da ação C.

 Semântica: o controle de fluxo de execução entra na estrutura, V é inicializada com I


(V ← I) e inicia-se a repetição da seguinte seqüência:
1. executa a ação C
2. quando atinge o fim-para, retorna ao início da estrutura
3. V é acrescida de P (V ← V + P)
4. se V ≤ L, repete novamente a seqüência
Quando V assumir um valor superior a L, o controle sai da estrutura passando para o
comando seguinte ao fim-para.

1.1.2. Métodos de Criação de Programas

a. Método Direto (Desenvolvimento de baixo para cima):

Ideal para programas ou trechos lógicos suficientemente simples que possam ser
expressados diretamente em pseudo-linguagem.
Este método é constituído dos seguintes passos:
1. Entender perfeitamente o problema a resolver.
6

Se o problema não possui um enunciado, descrevê-lo especificando claramente todos os


dados de entrada e os resultados a produzir.
2. A partir do enunciado, começar a elaborar uma lista que pode ser chamada de descrição
de variáveis, contendo para cada variável, o seu nome, uma descrição de seu objetivo no
algoritmo e o seu tipo. Essa lista pode ser iniciada pelos dados de entrada e pelos
resultados a produzir.
3. Desenvolver um estudo de métodos, isto é, para cada resultado pedido deve-se
estabelecer um ou mais métodos para sua obtenção (fórmulas matemáticas, técnicas de
controle de processamento etc.), escolhendo no final, aquele considerado mais
conveniente. Caso o método escolhido necessite utilizar novas variáveis, deve-se tomar
o cuidado de descrevê-las imediatamente, mantendo a lista de descrição de variáveis
sempre atualizada.
4. Elaborar um algoritmo baseado no estudo de métodos, observando a seguinte seqüência:

início
definição de novos tipos;
declaração de variáveis;
inicialização de variáveis;
corpo do algoritmo controlando:
leitura;
processamento;
saída;
fim.
5. Testar o algoritmo, considerando se possível, todas as alternativas de entrada.
6. Traduzir o algoritmo para uma linguagem de programação obtendo um programa que
deve ainda ser testado exaustivamente no computador e finalmente documentado.

Aplicação 1:
Desenvolver um algoritmo para ler vários números inteiros, calcular e imprimir:
1. Se o número lido não for negativo: o número lido e o seu fatorial.
2. No final: a quantidade de números negativos lidos.
Obs.: FLAG = 999

b. Método dos Refinamentos Sucessivos (Desenvolvimento de cima para baixo):

Ideal para programas mais complexos, onde deve-se manter inicialmente um nível mais alto
de abstração, isto é, manter a atenção em o que fazer e não em como fazer.
Usando a técnica dos refinamentos sucessivos, dispensa-se o estudo de métodos e parte-se
logo para a elaboração do algoritmo sendo que cada tarefa lógica distinta será representada por uma
frase descritiva da ação correspondente (pseudo-comando).
A primeira versão do algoritmo é rapidamente obtida pela listagem de alguns poucos
pseudo-comandos que deverão ser detalhados em refinamentos posteriores.
O refinamento de um pseudo-comando pode ser feito em função de outros pseudo-comandos
ou em função da própria pseudo-linguagem quando a ação lógica correspondente for considerada
mais elementar.
Depois que todos os pseudo-comandos já estiverem expressos em termos da pseudo-
linguagem, pode-se juntar as partes montando a versão final e completa do algoritmo.
Os pseudo-comandos originais são geralmente mantidos no texto do algoritmo sob a forma
de comentários melhorando a sua inteligibilidade.

c. Método Misto (Direto + Refinamentos Sucessivos):


7

Na prática este é o método mais usado, onde os controles de caráter geral e as ações mais
simples são desenvolvidas diretamente e a técnica de refinamentos sucessivos fica somente para as
ações lógicas que envolvem uma maior complexidade.
Observação: Em qualquer dos métodos, pode-se desenvolver as ações lógicas bem definidas na
forma de procedimentos (ou funções) a serem referenciados (chamados) no texto do algoritmo, no
momento da execução da ação correspondente. A utilização destes módulos funcionais constitui
uma técnica (programação modular) que geralmente diminui o esforço do programador e aumenta a
qualidade de seus programas.

1.1.3. Programação Modular

a) Procedimento

É um bloco precedido de um nome, podendo ter uma lista de parâmetros de entrada e/ou saída.
Por possuir nome, o procedimento pode ser referenciado (chamado para execução) em qualquer (um
ou mais) ponto do algoritmo.

- Sintaxe:
Procedimento <nome> (<lista de parâmetros formais>);
início
<declaração de variáveis locais >;
C1;
C2;
.
.
.
Cn;
fim; { nome }

- Forma de chamada:
<nome> (<lista de argumentos>);

- Observações:
 As variáveis locais só estarão alocadas durante o tempo de execução do procedimento.
 Os parâmetros formais são tratados como variáveis locais no procedimento, sendo que
um parâmetro formal de entrada assume seu valor na chamada do procedimento e um
parâmetro formal de saída tem seu valor final calculado dentro do procedimento
(passagem de parâmetros por referência).
 Um argumento (parâmetro efetivo) de entrada pode ser uma constante, uma variável ou
uma expressão e um argumento de saída só pode ser uma variável.
 A lista de parâmetros formais e a lista de argumentos têm que concordar em número,
ordem e tipo.

Aplicações:
2. Desenvolver um procedimento para dado um número inteiro não negativo, calcular o
seu fatorial.
3. Refazer a aplicação 1, substituindo o cálculo do fatorial pela chamada do procedimento
anterior.

b) Função
8

É um tipo especial de procedimento que retorna (sai) com um e somente um valor no


próprio nome da função (não possui parâmetros de saída) e que só pode ser chamada em uma
expressão.

Sintaxe:
função <nome> (<lista de parâmetros formais>): <tipo da função>;
início
<declaração de variáveis locais>;
C1;
C2;
.
.
.
Cn;
<nome> ← <expressão>; {valor que será retornado no nome da}
{função. O tipo de dado da <expressão> deve ser o}
{mesmo do <tipo da função> }
fim; {nome}

Forma de chamada:
...<nome> (<lista de argumentos>); ... {dentro de uma
expressão }

Aplicações:
4. Desenvolver uma função para dado um número inteiro não negativo, calcular o seu
fatorial.
5. Refazer a aplicação 1, substituindo o cálculo do fatorial pela chamada da função
anterior.

1.2. Recursividade ou Recorrência

Um procedimento ou função é recursivo ou recorrente quando possui a característica de


chamar a si próprio. A recursividade pode ser direta, quando o procedimento (ou função) chama a si
próprio, ou indireta, quando chama um outro que por sua vez referencia o procedimento que o
chamou.

Ex.:
9

Recursividade Direta: Recursividade Indireta:


procedimento A . . . procedimento A . . .
--------------- ---------------
A . . . B . . .
--------------- ---------------
fim-procedimento A; fim-procedimento A;
procedimento B . . .
---------------
A . . .
---------------
fim-procedimento B;

A recursividade pode ser usada em três situações:

1. Quando o problema estiver definido recursivamente.

Ex.: Função para o cálculo do fatorial de um número N, inteiro e não negativo.

 Solução iterativa:

função FAT (int: N) : int;


início
int: C, F;
F ← 1;
para C de 1 até N faça
F ← F * C;
fim-para;
FAT ← F;
fim-função; {FAT}

 Solução recursiva:

1, se N = 0
Por definição: N!=
N * (N – 1) !, se N > 0

função FAT (N) : int;


int: N;
início
se N = 0 então
FAT ← 1; {passo básico}
senão
FAT ← N * FAT (N – 1); {passo recursivo}
fim-se;
fim-função; {FAT}

2. Para substituir declarações de iteração por recorrência.

Ex.: Procedimento para classificar em ordem crescente os N elementos inteiros do vetor A.

 Solução iterativa:
10

tipo V = vet [1..N] int; {N deve ter valor definido}


procedimento SORT (V:A, int: N);
início
int : I, J, K, T;
para I de 1 até N – 1 faça
J ← I;
para K de I + 1 até N faça
se A [K] < A [J]
então
J ← K;
fim-se; 1
fim-para;
T ← A [I];
A [I] ←A [J];
A [J] ← T;
fim-para; 2

 Transformação de iteração em recorrência:

a. Representar todas as iterações por enquanto-faça:

tipo V = vet [1 . . N] int;


procedimento SORT (V:A, int: N);
início
int : I, J, K, T;
I ← 1;
enquanto I <= N – 1 faça
J ← I;
K ← J + 1;
enquanto K <= N faça
se A [K] < A [J] então
J ← K;
fim-se;
K ← K + 1;
fim-enquanto; 1
T ← A [I]; {troca o valor de A[I] por A[J]}
A [I] ←A [J];
A [J] ← T;
I ← I + 1; 2
fim-enquanto;
fim-procedimento; {SORT}

b. Substituir cada enquanto-faça por um procedimento recursivo, onde a declaração


enquanto <condição> faça deve ser substituído por uma alternativa se
<condição> então e o fim-enquanto pela chamada recursiva.
No exemplo, do enquanto-faça mais interno pode-se montar o procedimento:

enquanto-faça mais interno (1): Procedimento recursivo:


11

... tipo X = vet [1 . . N] int;


K ← J + 1; procedimento MIN(int: K, N,
ref int J, X:A);
enquanto K <= N faça
início
se A [K] < A [J] então se K <= N então
J ← K; se A [K] < A [J] então
fim-se; J ← K;
K ← K + 1; fim-se;
fim-enquanto; {K ← K + 1;
T ← A [I]; MIN (K, N, J, A); ou}
... MIN (K + 1, N, J, A);
fim-se;
fim-procedimento; {MIN}

Do enquanto-faça mais externo:

enquanto-faça mais externo: Procedimento recursivo:


tipo V = vet [1..N]int; tipo X = vet[1..N] int;
procedimento SORT (V:A, int:N); procedimento SORT1
início (int:I, N, ref X:A);
int : I, J, T; {K} início
I ← 1; int : J, T;
enquanto I <= N – 1 faça se I <= N – 1 então
J ← I; J ← I;
{K ← J + 1; MIN (J + 1, N, A, J);
MIN (K, N, A, J); ou} 1 T ← A [I];
MIN (J + 1, N, A, J); A [I] ←A [J];
T ← A [I]; A [J] ← T;
A [I] ←A [J]; {I ← I + 1;
A [J] ← T; SORT1 (I, N, A); ou}
I ← I + 1; SORT1 (I + 1, N, A);
fim-enquanto; fim-se;
fim-procedimento; {SORT} fim-procedimento; {SORT1}

O procedimento SORT passa a ser:

tipo V = vet [1 . . N] int;


procedimento SORT (V:A, int: N);
início
{int : I;
I ← 1;
12

SORT1 (I, N, A); ou}


SORT1 (1, N, A);
fim-procedimento; {SORT}

onde seu único objetivo é chamar o procedimento SORT1. Portanto, pode-se ignorar
este procedimento SORT, iniciando o processo pela chamada: SORT1 (1, N, A);.

3. Aplicação em estruturas de dados recursivas

A recursividade é essencial nos algoritmos aplicados a estruturas recursivas como listas e


árvores, que serão estudadas mais tarde.

Aplicação 6:

Desenvolver um programa para ler um valore inteiro N ≥ 1, calcular e imprimir os N


primeiros termos da série de Fibonacci: 1, 1, 2, 3, 5, 8, 13, 21, . . .
Obs.: Apresentar duas soluções:
• Iterativa
• Recursiva, onde o programa principal deve chamar um procedimento recursivo que controla
a geração e impressão dos N termos e sendo que a geração de cada termo deve ser realizada
por uma função recursiva.

1.3. Análise de Programas

Um programa pode ser analisado sob vários aspectos: atinge os objetivos propostos, está
correto (corretude), possui boa documentação, possui boa estrutura lógica dos módulos funcionais,
facilidade de alterações e manutenção, o código está inteligível, amigabilidade e funcionalidade
operacional, possui bom desempenho etc.
Embora todos os aspectos sejam igualmente importantes, destaca-se exclusivamente a
análise relativa ao desempenho do programa, que trata da medida do tempo de computação e da
ocupação de espaço de memória. Com o grande desenvolvimento tecnológico do hardware, a
memória vem oferecendo cada vez mais capacidade de armazenamento a um custo cada vez menor,
tornando a questão de ocupação de espaço de memória pouco relevante. Por isso, nesta análise será
dada ênfase somente à medida do tempo de computação.

Seja um programa com x instruções:


x
Tempo total = ∑ [(tempo de execução da instrução i) * (número de vezes que a
i =1
instrução i é executada)]

Tempo de execução da instrução i é sempre de difícil obtenção por causa de fatores como:
• a tradução da linguagem fonte para a de máquina feita por um compilador específico;
• conjunto das instruções da linguagem de máquina;
• tempo gasto por cada instrução de máquina;
• a máquina e os recursos (rede, sistema operacional etc.) usados;
• etc.

Em razão destas dificuldades, avalia-se o tempo total considerando somente o número de


vezes que uma instrução é executada. Esse número é chamado de contagem de freqüência ou
simplesmente freqüência.
13

A freqüência pode variar de um conjunto de dados para outro. Por isso, deve-se estar atento
na escolha adequada do conjunto de dados para efeito desta contagem.

Exemplos de determinação de freqüência de uma instrução:


a. Um comando simples qualquer pertencente a uma seqüência simples:
. . .
. . .
X ← X + 1; tem freqüência f = 1
. . .
. . .

b. Se esta instrução pertencer ao domínio de uma estrutura de repetição:


. . .
para I de 1 até N faça
...
N
X ← X + 1; ela passa a ter freqüência f = ∑1 = N
i =1
...
fim-para;
...
Deve-se observar que, neste caso, o comando para-faça tem freqüência igual a N.

c. Se a estrutura do exemplo anterior pertencer a outra iteração:


...
para J de 1 até N faça
...
para I de 1 até N faça
...
N N
X ← X + 1; o comando passa a ter freqüência f = ∑∑1 = N2
j =1 i =1
...
fim-para;
...
fim-para;
...

Se a instrução considerada é a que possui maior freqüência no programa, então ela passa a
ser chamada de ordem de grandeza de crescimento de tempo do programa. A ordem de grandeza
de um algoritmo é o principal parâmetro para análise de sua execução, sendo a sua determinação
obtida geralmente por fórmulas como:

• 1 O(1)
N
• ∑1 = N
i =1
O(N)
N
• ∑ i = [ N ( N + 1 ) ] / 2 = N2 / 2 + termos de grau inferior
i =1
O ( N2 )
14

N
• ∑i 2
= [ N ( N + 1 ) ( 2N + 1 ) ] / 6 = N3 / 3 + termos de grau inferior O ( N3 )
i =1

• ... ...
N
• ∑i K
= NK + 1 / (K + 1) + termos de grau inferior, K ≥ 0 O ( NK + 1 )
i =1

Quando se diz que um programa possui O ( NK + 1 ) isto significa que o seu crescimento de
tempo de execução é proporcional a NK + 1.

Exemplo: Analisar a solução iterativa de um algoritmo que leia um valor inteiro N, calcule e
imprima o seu fatorial. Se o valor lido para N for negativo, imprimir uma mensagem de erro.

início
int: N, FAT, C;
(1) leia (N);
(2) se N < 0 então
(3) imprima (N, ‘NEGATIVO’);
senão
(4) FAT ← 1;
(5) para C de 1 até N faça
(6) FAT ← FAT * C;
fim-para
(7) imprima (FAT);
fim-se;
fim.

Considerando as seguintes situações para execução: N < 0, N = 0, N = 1 e N > 1, as


freqüências seriam:

Instrução N<0 N=0 N=1 N>1


1 1 1 1 1
2 1 1 1 1
3 1 0 0 0
4 0 1 1 1
5 0 1 2 N+1
6 0 0 1 N
7 0 1 1 1
Total 3 5 7 2N + 5

O maior total de freqüência é 2N + 5, onde, ignorando as constantes 2 e 5, pode-se usar a


notação O (N), isto é, o tempo de computação deste algoritmo é proporcional a N.

A ordem de grandeza é de fundamental importância na comparação de soluções distintas de


um mesmo problema. Seja, por exemplo, dois algoritmos com mesmo objetivo com tempos de
computação O (N) e O (N2) e adotando as constantes 10 e ½ respectivamente, tem-se a seguinte
tabela de tempo:

N 10 N N2 / 2
1 10 0,5
15

5 50 12,5
10 100 50
15 150 112,5
20 200 200
25 250 312,5
30 300 450
35 350 612,5
. . .
. . .
. . .

Comparação dos Tempos de Execução

600

400
Tempo

200

0
1 5 10 15 20 25 30 35

N 10 N
N2 / 2

Obs.:
• O parâmetro N caracteriza a quantidade de entradas, a quantidade de saídas, a soma
dessas quantidades ou a grandeza de uma delas.
• As ordem de grandeza mais comuns nos algoritmos são:
O (1) → tempo de computação constante
O (log 2N) → logaritmo de base 2
O (N) → linear
O (N log 2N)
O (N 2) → quadrática
O (N 3) → cúbica
O (2 N) → exponencial

A figura a seguir exibe o gráfico das funções quadrática, cúbica e exponencial.


16

1500

2N

1000

Tempo

500
N3

N2

0
0 2 4 6 8 10
N

Tabela comparativa do tempo de execução:

log 2N N N log 2N N2 N3 2N
0 1 0 1 1 2
1 2 2 4 8 4
2 4 8 16 64 16
3 8 24 64 512 256
4 16 64 256 4096 65536
5 32 160 1024 32768 4,29496 x 10 9

Anda mungkin juga menyukai