2. www.professoresalgoritmos.com
Índice
1- Registros
2- Alocação Dinâmica de Memória
3- Ponteiros
4- Análise de Complexidade
5- Técnicas de Análise de Algoritmos
6- Tipos Abstratos de dados (TAD)
7- TAD – Listas Encadeadas
8- TAD – Pilha
9- TAD – Fila
3. www.professoresalgoritmos.com
Índice
10- Recursividade
11- TAD – Árvores
12- Balanceamento em Árvores
13- Pesquisa em Memória Primária Árvore
Binária de Busca
14- Pesquisa em Memória Primária Tabela Hash
15- Pesquisa Digital Árvore TRIE
16- Ordenação
5. www.professoresalgoritmos.com
Definição
• Registros são estruturas de dados capazes de
agregar várias informações
• É possível gerar novos tipos de dados, não se
limitando apenas à utilização dos tipos de
dados primitivos (char, int, float, double)
• Cada informação contida em um registro é
chamada de campo ou membro
5
9. www.professoresalgoritmos.com
Declaração de Registros
• Definir uma estrutura não cria nenhuma
variável, somente informa ao compilador as
características de um novo tipo de dados
• Não há reserva de memória
• No exemplo, foi definido um novo tipo de
dado denominado Aluno
• A definição desse tipo pode vir antes da
função main() ou dentro dela
9
10. www.professoresalgoritmos.com
Declaração de Variáveis do Tipo
Registro
• Para utilizar uma struct, é necessária a
declaração de variáveis desse tipo:
• Para o nosso exemplo:
nome_do_registro nome_da_variável;
Aluno alu1, alu2;
variáveisTipo de dado
10
11. www.professoresalgoritmos.com
Declaração de Variáveis do Tipo
Registro
• A declaração reserva espaço de memória
suficiente para armazenar cada um dos
membros da estrutura (nome, idade, cel e
endereco) para a variável alu1 e alu2
• Também é possível declarar um vetor ou uma
matriz do tipo da estrutura, como:
Aluno alu[10], mat[2][3];
vetor matriz
11
12. www.professoresalgoritmos.com
Acesso a Membros de Estruturas
• Após a variável ser declarada, o programa
precisa manipular o conteúdo de cada campo
individualmente
• Para isso, é preciso informar o nome da
variável e o do campo desejado, separados
por um ponto
nome_da_variável.nome_do_campo
ponto
12
13. www.professoresalgoritmos.com
Acesso a Membros de Estruturas
• Para armazenar um determinado valor nas
variáveis do exemplo:
• Para armazenar um dado digitado pelo
usuário
strcpy(alu1.nome,”Maria”);
alu1.idade = 16;
gets(alu1.nome);
Cin<<alu1.idade;
13
14. www.professoresalgoritmos.com
Exemplo 1
int main()
{
//nesse exemplo a estrutura foi criada dentro da main()
struct Aluno
{
char nome[255];
char endereco[300];
int idade, cel;
} alu1;
cout<<"nCadastro - Aluno 1: ";
cout<<"nDigite o nome: ";
gets(alu1.nome);
cout<<"nDigite o endereco: ";
gets(alu1.endereco);
cout<<"nDigite a idade: ";
cin>>alu1.idade;
cout<<"nDigite o celular: ";
cin>>alu1.cel;
14
18. www.professoresalgoritmos.com
Declaração de Vetor do tipo Registro
• Pode-se criar vetores utilizando uma estrutura
de dados
• Alterando o exemplo para que sejam
armazenados os dados (nome, idade, cel,
endereco) de 10 alunos.
Aluno alu[10];
vetor
18
19. www.professoresalgoritmos.com
Acesso a Membros com Vetor de
Estruturas
• Para preencher o vetor todo com 10 alunos
for(i=0; i<10; i++)
{
gets(alu1[i].nome);
cin<<alu1[i].idade;
}
Índice do vetor
19
20. www.professoresalgoritmos.com
Exemplo 3
struct Aluno
{
char nome[255];
char endereco[300];
int idade, cel;
};
int main()
{
Aluno alu1[10];
int i;
for(i=0; i<10; i++)
{
cout<<"nCadastro - Aluno "<<i+1<<": ";
cout<<"nDigite o nome: ";
gets(alu1[i].nome);
cout<<"nDigite o endereco: ";
gets(alu1[i].endereco);
cout<<"nDigite a idade: ";
cin>>alu1[i].idade;
cout<<"nDigite o celular: ";
cin>>alu1[i].cel;
}
20
22. www.professoresalgoritmos.com
Passando Registros para Funções
• As estruturas podem ser passadas como
parâmetros de funções da mesma maneira
que uma variável simples
• O nome de uma estrutura não é um endereço,
portanto, ela pode ser passada por valor
• O exemplo a seguir apresenta uma função que
recebe duas estruturas como parâmetro e
imprime os valores da soma de seus membros
22
23. www.professoresalgoritmos.com
Exemplo 4
//A estrutura e a função estão antes da main()
struct Venda
{
int pecas;
float preco;
};
void listavenda(Venda c, Venda d)
{
cout<<"n**** Venda Total ****";
cout<<"nTotal de pecas: "<<(c.pecas + d.pecas);
cout<<"nPreco total: "<<((c.pecas*c.preco) + (d.pecas*d.preco));
}
23
24. www.professoresalgoritmos.com
Exemplo 4
int main()
{
Venda A, B;
cout<<"nVenda A";
cout<<"nInsira o numero de pecas: ";
cin>>A.pecas;
cout<<"nInsira o preco: ";
cin>>A.preco;
cout<<"nVenda B";
cout<<"nInsira o numero de pecas: ";
cin>>B.pecas;
cout<<"nInsira o preco: ";
cin>>B.preco;
listavenda(A,B);//chamada da função
cout<<"nFim do programa";
system("PAUSE");
return EXIT_SUCCESS;
}
24
25. www.professoresalgoritmos.com
Passando Registros para Funções por
referência
• A sintaxe das passagem de estrutura para funções
por referência é a mesma da passagem de
variáveis simples por referência
• Como as estruturas, em geral, são dados que
ocupam uma grande quantidade de memória, é
conveniente que se use passagem de parâmetro
por referência
Usando referência não há criação de uma
cópia da variável na função
25
26. www.professoresalgoritmos.com
Exemplo 5
//Alterando o exemplo anterior para que a função receba os parâmetros
//por referência
struct Venda
{
int pecas;
float preco;
};
void listavenda(Venda *c, Venda *d)
{
cout<<"n**** Venda Total ****";
cout<<"nTotal de pecas: "<<((*c).pecas+ (*d).pecas);
cout<<"nPreco total: "<<(((*c).pecas* (*c).preco)+((*d).pecas*
(*d).preco));
}
26
27. www.professoresalgoritmos.com
Exemplo 5
int main()
{
Venda A, B;
cout<<"nVenda A";
cout<<"nInsira o numero de pecas: ";
cin>>A.pecas;
cout<<"nInsira o preco: ";
cin>>A.preco;
cout<<"nVenda B";
cout<<"nInsira o numero de pecas: ";
cin>>B.pecas;
cout<<"nInsira o preco: ";
cin>>B.preco;
listavenda(&A,&B);//chamada da função
cout<<"nFim do programa";
system("PAUSE");
return EXIT_SUCCESS;
}
27
28. www.professoresalgoritmos.com
Exemplo 6
//Outra forma de passar a estrutura como parâmetros por referência
struct Venda
{
int pecas;
float preco;
};
void listavenda(Venda& c, Venda& d)
{
cout<<"n**** Venda Total ****";
cout<<"nTotal de pecas: "<<(c.pecas + d.pecas);
cout<<"nPreco total: "<<((c.pecas * c.preco)+(d.pecas * d.preco));
}
28
29. www.professoresalgoritmos.com
Exemplo 6
int main()
{
Venda A, B;
cout<<"nVenda A";
cout<<"nInsira o numero de pecas: ";
cin>>A.pecas;
cout<<"nInsira o preco: ";
cin>>A.preco;
cout<<"nVenda B";
cout<<"nInsira o numero de pecas: ";
cin>>B.pecas;
cout<<"nInsira o preco: ";
cin>>B.preco;
listavenda(A,B);//chamada da função
cout<<"nFim do programa";
system("PAUSE");
return EXIT_SUCCESS;
}
29
30. www.professoresalgoritmos.com
Funções que retornam um Registro
• A linguagem C++ permite que as funções
retornem uma estrutura completa para outra
função, como o exemplo:
Venda novavenda()
{
Venda x;
cout<<"nVenda A";
cout<<"nInsira o numero de pecas: ";
cin>>x.pecas;
cout<<"nInsira o preco: ";
cin>>x.preco;
return x;
}
30
32. www.professoresalgoritmos.com
Exercícios
2- Foi realizada uma pesquisa de algumas características físicas
de 50 habitantes de uma certa região. De cada habitante
foram coletados os seguintes dados: sexo, altura, idade e cor
dos olhos (A - Azuis, V - Verdes ou C – Castanhos).
Faça um programa que leia esses dados e armazene-os em
um registro do tipo vetor. Em seguida, determine:
a) a média de idade das pessoas com olhos castanhos e
altura superior a 1.60 m
b) a maior idade entre os habitantes
c) a quantidade de indivíduos do sexo feminino cuja idade
esteja entre 20 e 45 anos (inclusive) ou que tenham
olhos verdes e altura inferior a 1.70 m
d) o percentual de homens
32
33. www.professoresalgoritmos.com
Referência Bibliográfica
• ASCENCIO, Ana Fernanda Gomes e CAMPOS,
Edilene A. Veneruchi. Fundamentos da
Programação de Computadores – Algoritmos,
Pascal e C/C++. São Paulo: Pearson Prentice Hall,
2007. 2ª Edição. Capítulo 10.
• MIZRAHI, Victorine Viviane. Treinamento em
Linguagem C++. 2ª Ed. Módulo 1. São Paulo:
Pearson Prentice Hall, 2006. Capítulo 7.
33
35. www.professoresalgoritmos.com
Definição
• Na declaração de um vetor é preciso
dimensioná-lo, ou seja, saber, de antemão,
quanto de espaço é necessário
– Prever o número máximo de elementos no vetor
durante a codificação
Ex.: Suponha que desejamos desenvolver um
programa para calcular a média e a variância
das notas de uma prova. Mas não sabemos
que o número máximo de alunos?
35
36. www.professoresalgoritmos.com
Definição
• Solução:
– Dimensionar um vetor com um número
absurdamente alto, para não termos limitação no
momento da utilização do programa
– Essa solução pode levar a um desperdício de
memória ou uma limitação do número de alunos e
consequentemente do programa
36
37. www.professoresalgoritmos.com
Definição
• A linguagem C oferece meios de requisitar
espaços de memória em tempo de execução
• Assim, voltando ao exemplo, é possível
consultar o número de alunos e então fazer a
alocação do vetor dinamicamente, sem de
desperdício de memória
Alocação dinâmica de memória
37
38. www.professoresalgoritmos.com
Reserva de espaço de memória
Existem 3 maneiras de reservar espaço de memória:
1. Usar variáveis globais (e estáticas): o espaço
reservado existe enquanto o programa estiver sendo
executado
2. Usar variáveis locais: o espaço existe apenas
enquanto a função que declarou a variável está sendo
executada
3. Requisitar ao sistema, em tempo de execução, um
espaço de um determinado tamanho: Esse espaço
permanece reservado até que seja explicitamente
liberado pelo programa.
38
39. www.professoresalgoritmos.com
Função Malloc()
• Biblioteca: stdlib.h
• A função básica para alocar memória é a
malloc()
• A função recebe como parâmetro o número
de bytes que se deseja alocar e retorna o
endereço inicial da área da memória alocada
– Dessa forma, é necessário o uso de um ponteiro
para receber o endereço inicial do espaço alocado
39
42. www.professoresalgoritmos.com
Função Malloc()
• Como malloc retorna um ponteiro genérico,
para um tipo qualquer, representado por void *
– que pode ser convertido para o tipo apropriado na
atribuição:
int *v;
v = (int *)malloc(10 * sizeof(int));
Conversão para o tipo int
42
43. www.professoresalgoritmos.com
Função Malloc()
• Se não houver espaço livre suficiente para
realizar a alocação, a função retorna um
endereço nulo (NULL):
int *v;
v = (int *)malloc(10 * sizeof(int));
if( v == NULL)
{
cout<<“Memória insuficiente”;
exit(1); //aborta o programa e retorna 1
}
...
43
44. www.professoresalgoritmos.com
Função Free()
• Biblioteca: stdlib.h
• A função básica para liberar um espaço de
memória alocado dinamicamente é a free()
• A função recebe como parâmetro o ponteiro
da memória a ser liberada
int *v;
v = (int *)malloc(10* sizeof(int));
...
Free(v); //libera espaço de memória
44
45. www.professoresalgoritmos.com
Função Free()
• Só podemos passar para a função free() um
ponteiro (endereço) de memória que tenha
sido alocado dinamicamente
• Cuidado, pois não é possível acessar o espaço
da memória depois de liberado
45
46. www.professoresalgoritmos.com
Exercícios
1- Explique a vantagem de usar alocação dinâmica de
memória. Use exemplos.
2- O que é alocação dinâmica de memória?
3- Como podemos liberar um espaço de memória alocado
dinamicamente?
46
47. www.professoresalgoritmos.com
Exercícios
4- Escreva o que será impresso pelo programa abaixo.
int main ( )
{
int *A;
A = (int*)malloc(sizeof(int));
*A = 10;
cout<<"nvalor de A: "<<*A;
int *B;
B = A;
*B = 15;
cout<<"nvalor de B: "<<*B;
}
47
48. www.professoresalgoritmos.com
Exercícios
5- Escreva o que será impresso pelo programa abaixo.
int main ( )
{
int *A;
A = (int*)malloc(sizeof(int));
*A = 10;
cout<<"nPrimeiro valor de A: "<<*A;
int *B;
B= (int*)malloc(sizeof(int));
*B = *A;
*B = 15;
cout<<"nvalor de B: "<<*B;
cout<<"nSegundo valor de A: "<<*A;
} 48
49. www.professoresalgoritmos.com
Exercícios
6- Dado o código abaixo, indique o resultado do mesmo para cada
um dos valores de “*A”.
void main()
{
int *A;
A = (int*)malloc(sizeof(int));
*A = ??;
int *B;
B = (int*)malloc(sizeof(int));
*B = *A;
*B = 15;
cout<<"n "<<*B;
cout<<"n "<<*A;
}
*A = 10 Resposta:
(*A = _____ e *B = _____ )
*A = 35 Resposta:
(*A = _____ e *B = _____ )
• Substitua o valor do símbolo ‘??’ no
código por cada um dos valores
apresentados para *A abaixo. Em seguida,
mostre os resultados que serão impressos
na tela (*B e *A) para cada um dos
valores.
49
52. www.professoresalgoritmos.com
Definição
• Ponteiro é um endereço de memória
• Seu valor indica em que parte da memória do
computador uma variável está alocada, não o
que está armazenado nela
Ponteiro variável é um lugar na memória
que armazena o endereço de outra
variável
52
53. www.professoresalgoritmos.com
Razões para usar ponteiros
• Receber parâmetros em funções que
necessitem modificar o parâmetro original
• Criar estruturas complexas, como listas
encadeadas e árvores binárias, em que um
item deve conter referência a outro
• Alocar e desalocar memória do sistema
• Passar para uma função o endereço de outra
53
54. www.professoresalgoritmos.com
Ponteiros
• Dizemos que uma variável aponta para outra
variável quando a primeira contém o
endereço da segunda
• Endereço de memória: um endereço é a
referência que o computador usa para
localizar variáveis
– Toda variável ocupa uma certa localização na
memória e seu endereço é o do primeiro byte
ocupado por ela
54
65. www.professoresalgoritmos.com
Acesso aos campos da Estrutura
• Para acessar os campos da estrutura com um
ponteiro:
(*nome_ponteiro). Nome_campo
ponto
Os parênteses são indispensáveis, pois o operador
“*” tem precedência menor do que o operador de
acesso “.”
65
66. www.professoresalgoritmos.com
Acesso aos campos da Estrutura
• Outra forma de acessar os membros é:
• E para acessar o endereço de um campo:
nome_ponteiro -> Nome_campo
&nome_ponteiro -> Nome_campo
66
69. www.professoresalgoritmos.com
Exercícios
1- O que é um ponteiro?
2- Explique o que significa a instrução:
int *p;
3- Explique para que serve o operador & e o operador * nas
instruções abaixo:
a) p = &i;
b) *p = i;
69
70. www.professoresalgoritmos.com
Exercícios
4- Escreva o que será impresso pelo programa abaixo.
int main()
{
int x=3, y=7;
int *px=&x;
*px = 12;
y = *px;
cout<<"n y= "<<y;
cout<<"n x= "<<x;
cout<<"n";
system("PAUSE");
} 70
71. www.professoresalgoritmos.com
Exercícios
5- Escreva o que será impresso pelo programa abaixo.
int main()
{
int x=3, y=7;
int *px=&x, *py=&y;
y= 4;
cout<<"n *px= "<<*px;
cout<<"n *py= "<<*py;
cout<<"n";
system("PAUSE");
} 71
72. www.professoresalgoritmos.com
Exercícios
6- Escreva o que será impresso pelo programa abaixo.
void Troca (int *A, int B)
{
int temp;
temp = *A;
*A = B;
B = temp;
}
int main()
{
int x,y;
x = 5;
y = 3;
Troca(&x,y);
cout << x << endl << y;
getch();
}
72
73. www.professoresalgoritmos.com
Referência Bibliográfica
• MIZRAHI, Victorine Viviane. Treinamento em
Linguagem C++. 2ª Ed. Módulo 2. São Paulo:
Pearson Prentice Hall, 2006. Capítulo 11.
• Celes, Waldemar; Cerqueira, Renato e Rangel,
José Lucas. Introdução a Estruturas de Dados.
Rio de Janeiro: Elsevier, 2004 – 5ª impressão.
Capítulos 4 e 8.
73
75. www.professoresalgoritmos.com
Introdução
• O projeto de um algoritmo deve considerar o
desempenho que este terá após sua
implementação.
• Várias soluções podem surgir e aspectos de
tempo de execução e espaço ocupado são
pontos muito relevantes na escolha da
solução mais adequada.
75
76. www.professoresalgoritmos.com
Introdução
• Analisar um algoritmo significa predizer os
recursos computacionais que o algoritmo requer
quando da sua execução: memória, largura de
banda de comunicação, hardware de
computação.
• Recurso mais considerado: tempo de
processamento.
• Em geral, existem vários algoritmos para
solucionar um mesmo problema e a análise é
capaz de identificar qual é o mais eficiente.
76
77. www.professoresalgoritmos.com
Introdução
• A área de análise de algoritmos pode
considerar dois tipos de problemas distintos:
– Análise de um algoritmo em particular: custo para
a resolução de um problema específico.
– Análise de uma classe de algoritmos: um conjunto
de algoritmos para resolver um problema
específico é estudado, para determinar qual o
melhor.
77
78. www.professoresalgoritmos.com
Introdução
• Interesse: expressão ou fórmula matemática (modelo
matemático) que represente o tempo de execução de
um algoritmo.
• Aspectos mais importantes da análise de tempo:
– quantidade de elementos a processar (tamanho da
entrada);
– forma como os elementos estão dispostos na entrada.
• Tempo de execução de um algoritmo: uma função f(n),
onde n é o tamanho da entrada.
• A função f deve expressar o número de operações
básicas, ou passos, executados pelo algoritmo.
78
79. www.professoresalgoritmos.com
Introdução
• Interesse: expressão ou fórmula matemática (modelo
matemático) que represente o tempo de execução de
um algoritmo.
• Aspectos mais importantes da análise de tempo:
– quantidade de elementos a processar (tamanho da
entrada);
– forma como os elementos estão dispostos na entrada.
• Tempo de execução de um algoritmo: uma função f(n),
onde n é o tamanho da entrada.
• A função f deve expressar o número de operações
básicas, ou passos, executados pelo algoritmo.
79
81. www.professoresalgoritmos.com
Complexidade de Tempo
• A complexidade de tempo de um algoritmo
tem por objetivo avaliar sua eficiência.
• Para medir o custo de execução de um
algoritmo comum definir uma função de custo
ou função de complexidade f, em que f(n) é a
medida do tempo necessário para executar
um algoritmo para um problema de tamanho
n
81
82. www.professoresalgoritmos.com
Complexidade de Tempo
• A complexidade de tempo de um algoritmo
tem por objetivo avaliar sua eficiência.
• Para medir o custo de execução de um
algoritmo comum definir uma função de custo
ou função de complexidade f, em que f(n) é a
medida do tempo necessário para executar
um algoritmo para um problema de tamanho
n.
82
83. www.professoresalgoritmos.com
Complexidade de Tempo
• Função de complexidade de tempo do
algoritmo: se f(n) for uma medida da quantidade
do tempo necessário para executar um algoritmo
de tamanho n
• Função de complexidade de espaço do
algoritmo: se f(n) for uma medida da quantidade
de memória necessária para executar um
algoritmo de tamanho n
83
84. www.professoresalgoritmos.com
Complexidade de Tempo
• Melhor caso: corresponde ao menor tempo
de execução sobre todas as possíveis entradas
de tamanho n
• Caso médio (ou caso esperado): corresponde
à média dos tempos de execução de todas as
entradas de tamanho n
• Pior caso: corresponde ao maior tempo de
execução sobre todas as possíveis entradas de
tamanho n
84
85. www.professoresalgoritmos.com
Exemplo1
• Seja f uma função de complexidade tal que f(n) é
o número de registros consultados no arquivo, isto
é, o número de vezes que a chave de consulta é
comparada com a chave de cada registro.
Os casos a considerar são:
Melhor caso: f(n) = 1
Pior caso: f(n) = n
Caso médio: f(n) = (n+1)/2
85
86. www.professoresalgoritmos.com
Exemplo2
Considere o problema de encontrar o maior e o menor
elementos de um vetor de inteiros v[0..n-1], n>=1
void calculaMaxMin1(int vet[], int n)
{
int max = vet[0], min = vet[0];
for(int i=1; i<n; i++)
{
if(vet[i]>max)
max = vet[i];
if(vet[i]<min)
min = vet[i];
}
}
86
87. www.professoresalgoritmos.com
Exemplo2
Para o exemplo anterior, temos que f é uma função de
complexidade tal que f(n) é o número de comparações entre
os elementos de vet, se vet contiver n elementos, temos
que:
f(n) = 2(n-1), para n > 0
Esse programa pode ser facilmente melhorado.
Basta observar que a comparação vet[i] < min
somente é necessária quando o resultado da
comparação vet[i] > max é falso.
87
88. www.professoresalgoritmos.com
Exemplo2 – Nova versão
void calculaMaxMin2(int vet[], int n)
{
int max = vet[0], min = vet[0];
for(int i=1; i<n; i++)
{
if(vet[i]>max)
max = vet[i];
else if(vet[i]<min)
min = vet[i];
}
}
88
89. www.professoresalgoritmos.com
Exemplo2 – Nova versão
void calculaMaxMin2(int vet[], int n)
{
int max = vet[0], min = vet[0];
for(int i=1; i<n; i++)
{
if(vet[i]>max)
max = vet[i];
else if(vet[i]<min)
min = vet[i];
}
}
Melhor caso: f(n) = n-1
Pior caso: f(n) = 2(n-1)
Caso médio: f(n) = 3n/2-3/2
89
90. www.professoresalgoritmos.com
Exemplo2 – Nova versão
void calculaMaxMin(int vet[], int
n)
{
int max = vet[0], min = vet[0];
for(int i=1; i<n; i++)
{
if(vet[i]>max)
max = vet[i];
else if(vet[i]<min)
min = vet[i];
}
}
90
91. www.professoresalgoritmos.com
Comportamento Assintótico de
Funções
• O nº de comparações para encontrar o maior
elemento de um conjunto de n inteiros, ou
para ordenar os elementos de um conjunto
com n elementos, aumenta com n
• Parâmetro n fornece uma medida da
dificuldade para se resolver o problema
– O custo para obter uma solução para um dado
problema aumenta com o tamanho de n do
problema
91
92. www.professoresalgoritmos.com
Comportamento Assintótico de
Funções
• Para valores suficientemente pequenos de n,
qualquer algoritmo custa pouco para ser
executado, mesmo os algoritmos ineficientes
– Para problemas de tamanho pequeno a escolha
do algoritmo não é um problema crítico
– A análise de algoritmos é realizada apenas para
valores grandes de n
– A análise de um algoritmo geralmente conta com
apenas algumas operações elementares, e em
muitos casos com uma operação elementar
92
93. www.professoresalgoritmos.com
Notação Assintótica
• Passos:
1. Identificar o termo dominante da expressão que
descreve sua complexidade, ou seja, descreve a
ordem de crescimento assintótico desta
expressão.
2. Obter uma função que é um limitante superior
assintótico para a nossa expressão, isto é, para
instâncias arbitrárias de tamanho n podemos
resolver o problema em tempo menor ou igual a
O(n).
93
94. www.professoresalgoritmos.com
Notação O(f(n))
f(n) = O(1)
Complexidade constante, ou seja, independe do
tamanho da entrada n. As instruções são
executadas um número fixo de vezes
f(n) = O(log n)
Complexidade sub-linear ou logarítmica, ocorre
geralmente em problemas que dividem-se em
problemas menores em sua resolução
f(n) = O(n)
Complexidade linear, ou seja, quando um
pequeno trabalho é realizado sobre os
elementos de entrada n. Esta situação é boa
para algoritmos que tenham entrada e saída n.
94
95. www.professoresalgoritmos.com
Notação O(f(n))
f(n) = O(n log n)
Esta complexidade geralmente acontece com
algoritmos que separam o problema em
menores e unem as resoluções depois de
encontrá-las.
f(n) = O(n^2)
Complexidade quadrática, ou seja, quando
itens são processados aos pares, geralmente
quando temos um anel dentro do outro.
f(n) = O(2^n)
Complexidade exponencial. São algoritmos
péssimos em ponto de vista prático.
Geralmente são algoritmos utilizados para
resolução de problemas na força bruta.
95
96. www.professoresalgoritmos.com
Notação O(f(n))
f(n) = O(n!)
Complexidade fatorial. São algoritmos
piores que os exponenciais. Péssimos na
prática e resultado de aplicação d e força
bruta, não são recomendados para
resolução de problemas.
96
97. www.professoresalgoritmos.com
Notação Assintótica
• A tabela de classes de problemas está ordenada de
maneira crescente
• Para compararmos dois algoritmos, é necessário saber
primeiro a qual classe pertencem ao algoritmos.
– Se forem de classe diferentes s comparação fica fácil,
seguindo a ordem da tabela.
– Se forem da mesma classe, deverão ser comparados
por suas funções reais de complexidade de tempo,
lembrando que o caso comparado deve ser o mesmo
(ex. pior caso com pior caso)
97
98. www.professoresalgoritmos.com
Função
de custo
Tamanho n
10 20 30 40 50 60
n 0,00001 s 0,00002
s
0,00003
s
0,00004
s
0,00005
s
0,00006
n² 0,0001 s 0,0004 s 0,0009 s 0,0016 s 0,0035 s 0,0036 s
n³ 0,001 s 0,008 s 0,027 s 0,64 s 0,125 s 0,316 s
n⁵ 0,1 s 3,2 s 24,3 s 1,7 min 5,2 min 13 min
2^n 0,001 s 1 s 17,9 min 12,7 dias 35,7
anos
366 séc.
3^n 0,059 s 58 min 6,5 anos 3855
séc.
10⁸ séc. 10¹³ séc.
Comparação de funções de
complexidades
98
99. www.professoresalgoritmos.com
Trabalho de Pesquisa
• Fazer uma pesquisa sobre:
– Problemas P
– Problemas NP
– Problemas NP-completos
• Conceitue cada um. Dê exemplos.
• Não esqueça de colocar as referências usadas.
• Entregar impresso dia (06/09/2012).
• Pode ser feito em dupla.
99
100. www.professoresalgoritmos.com
Referência Bibliográfica
• ZIVIANI, Nivio. Projeto de Algoritmos com
implementações em Java e C++. São Paulo:
Thomson Learning, 2007.
• CORMEN, Thomas. Algoritmos: teoria e prática.
Campus, 2002.
• Notas de aula Profª Raquel Marcia Müller
(http://www.comp.uems.br/Members/rmmuller/
pg_aedii)
100
102. www.professoresalgoritmos.com
Introdução
• A análise de algoritmos ou programas utiliza
técnicas de matemática discreta, envolvendo
contagem ou enumeração dos elementos de
um conjunto que possuam propriedade
comum:
– Manipulação de somas, produtos, permutações,
fatoriais, coeficientes binomiais, solução de
equações de recorrência, entre outras.
102
104. www.professoresalgoritmos.com
Introdução
• Complexidade de tempo da maioria dos problemas é
polinomial ou exponencial.
– Polinomial: função de complexidade é O (p(n)) , onde p(n)
é um polinômio.
• Exemplos: pesquisa binária (O (log n)), pesquisa
seqüencial ( O (n)), ordenação por inserção (O (n²)), e
multiplicação de matrizes (O (n³)).
– Exponencial: função de complexidade é O (c^n), c> 1.
• Exemplo:Problema do Caixeiro Viajante(PCV) (O (n!)).
104
105. www.professoresalgoritmos.com
Complexidade
• O(1) ou constante
• O(log n) ou logaritímica
• O(n) ou linear
• O(n log n) ou n log de n
• O(n²) ou quadrática
• O(n³) ou cúbica
• O(n!) ou fatorial
Maior
Complexidade
105
110. www.professoresalgoritmos.com
Complexidade de algumas estruturas
de controle
• Regras rígidas sobre o cálculo da complexidade
de qualquer algoritmo não existem, cada caso
deve ser estudado em suas condições.
• No entanto, as estruturas de controle clássicas
da programação estruturada permitem uma
estimativa típica de cada uma.
• A partir disso, algoritmos construídos com
combinações delas podem ter sua
complexidade mais facilmente estabelecida.
110
111. www.professoresalgoritmos.com
• Comando simples : tem um tempo de
execução constante, O(c) = O(1).
• Seqüência: tem um tempo igual à soma dos
tempos de cada comando da seqüência; se
cada comando é O(1), assim, também será a
seqüência; senão, pela regra da soma, a
seqüência terá a complexidade do comando
de maior complexidade.
• Alternativa : qualquer um dos ramos pode ter
complexidade arbitrária; a complexidade
resultante é a maior delas; isto vale para
alternativa dupla (if-else) ou múltipla (switch).
111
112. www.professoresalgoritmos.com
• Repetições
• Repetição contada: é aquela em que cada iteração
(ou “volta”) atualiza o controle mediante uma
adição(geralmente, quando se usa uma estrutura do
tipo for, que especifica incremento/decremento
automático de uma variável inteira).
• Se o número de iterações é independente do tamanho
do problema, a complexidade de toda a repetição é
a complexidade do corpo da mesma, pela regra da
constante (ou pela regra da soma de tempos).
for (i=0; i<k ; i++)
trecho com O(g(n))
se k não é
f(n)então o
trecho é O(g(n))
112
113. www.professoresalgoritmos.com
• Se o número de iterações é função de n, pela regra do
produto teremos a complexidade da repetição como
a complexidade do corpo multiplicada pela função que
descreve o número de iterações. Isto é:
for (i=0; i<10 ; i++)
{
x = x+v;
printf (“%d”, x);
}
isto é O(1), logo
toda a repetição é
O(1)
113
114. www.professoresalgoritmos.com
for (i=0; i<n; i++)
trecho com O(g(n))
como o número de
iterações é f(n)=n
então o trecho é
O(n*g(n))
for (i=0; i<k*n ; i++)
trecho com O(log n)
o trecho é O(f(n)*g(n)),
no caso O(k*n*log n),
ou seja: O(n log n)
Exemplo:
114
115. www.professoresalgoritmos.com
• Uma aplicação comum da regra do produto é a
determinação da complexidade de repetições aninhadas.
• Exemplo:
• Exemplo:
for (i=0; i<n ; i++)
for (j=0; j<n ; j++)
trecho com O(1)
o trecho é
O(f(n)*g(n)), no caso
g(n)=n*1 (laço
interno); logo,
O(n*n), ou seja:
O(n²)
for (i=1; i<=n ; i++)
for (j=1; j<=i ; j++)
trecho com O(1)
o laço interno é
executado 1+2+3+...n-1
+n=n*(n+1)/2 vezes,
logo, O(n*(n+1)/2), ou
seja: O(0.5(n²+n)) ou
seja O(n²)
115
116. www.professoresalgoritmos.com
for (i=1; i<=n ; i++)
for (j=n; i<=j ; j--)
trecho com O(1)
o laço interno é
executado n+n-1+n-
2+...+2+1=n*(n+1)/2
vezes, ou seja: O(n²)
como no caso anterior
• Os dois últimos exemplos podem ser
generalizados para quaisquer aninhamentos de
repetições contadas em k níveis, desde que todos
os índices dependam do tamanho do problema.
Nesse caso, a complexidade da estrutura aninhada
será da ordem de n ^ k.
116
117. www.professoresalgoritmos.com
for (IndExt=1; IndExt<=n ; IndExt++)
for (IndMed=IndExt; IndMed<=n ; IndMed++)
for (IndInt=1; IndInt<=IndMed; IndInt++)
trecho com O(1)
o laço mediano é executado n+n-1+n-2+...
+2+1=(n²+n)/2 vezes; o laço mais
interno será executado no máximo n vezes; logo,
tem-se O((n²+n)*n), ou seja: O(n³)
117
118. www.professoresalgoritmos.com
• Repetições
• Repetição multiplicativa: é aquela em que cada
iteração atualiza o controle mediante uma
multiplicação ou divisão.
limite=1;
while (limite<=n)
{
trecho com O(1)
limite = limite*2;
}
o número de iterações
depende de n; limite vai
dobrando a cada iteração;
depois de k iterações,
limite = 2^k e k = log2
limite; como o valor
máximo de limite é n,
então o trecho é O(log2n)
= O(log n)
OBS: Na verdade O(log n) independe da base do logaritmo, pois
logan = logab*logbn = c*logbn.
118
119. www.professoresalgoritmos.com
int limite;
for (limite=n; limite!=0; limite /=2)
trecho com O(1)
o número de iterações depende de n; limite vai-se subdividindo a cada
iteração; depois de k=log2n iterações, encerra; então o trecho é O(log n)
• Os dois exemplos anteriores também podem ser
generalizados, adotando-se um fator genérico de
multiplicação fator. Nesse caso, o número de
iterações será dado por k = logfatorlimite = O(logf(n)),
se o limite é função de n.
119
120. www.professoresalgoritmos.com
int limite=n;
while (limite!=0)
{
for (i=1; i<=n; i++)
trecho com O(1)
limite = limite/2;
}
o número de iterações
depende de n; limite vai-se
subdividindo a cada iteração;
o laço interno é O(n), o
externo O (log n);
logo, o trecho é O (n log n)
Exemplo:
120
121. www.professoresalgoritmos.com
• Chamada de função: Pode ser resolvida considerando-
se que a função também tem um algoritmo com sua
própria complexidade. Esta é usada como base para
cálculo da complexidade do algoritmo invocador. Por
exemplo: se a invocação estiver num ramo de uma
alternativa, sua complexidade será usada na
determinação da máxima complexidade entre os dois
ramos; se estiver no interior de um laço, será
considerada no cálculo da complexidade da seqüência
repetida, etc.
• A questão se complica ao se tratar de uma chamada
recursiva.
121
122. www.professoresalgoritmos.com
• Embora não haja um método único para esta
avaliação, em geral a complexidade de um algoritmo
recursivo será função de componentes como: a
complexidade da base e do núcleo da solução e a
profundidade da recursão. Por este termo entende-se
o número de vezes que o procedimento é invocado
recursivamente. Este numero, usualmente, depende
do tamanho do problema e da taxa de redução do
tamanho do problema a cada invocação. E é na sua
determinação que reside a dificuldade da análise de
algoritmos recursivos.
122
123. www.professoresalgoritmos.com
• Exemplo:
int fatorial (int n)
{
if (n==0)
return 1; // Base
else
return n*fatorial(n- 1); //Núcleo
}
• A redução do problema se faz de uma em uma unidade, a cada
reinvocação do procedimento, a partir de n, até alcançar n = 0.
Logo, a profundidade da recursão é igual a n. O núcleo da solução
(que é repetido a cada reinvocação) tem complexidade O(1), pois
se resume a uma multiplicação. A base tem complexidade O(1),
pois envolve apenas uma atribuição simples. Nesse caso, conclui-se
que o algoritmo tem um tempo T(n) = n*1+1 = O(n). 123
124. www.professoresalgoritmos.com
Exemplo
• Considere um algoritmo recursivo, nesse caso
é necessário obter uma equação de
recorrência (maneira de definir uma função
por uma expressão envolvendo a mesma
função)
• O exemplo a seguir inspeciona n elementos de
um conjunto e permite descartar 2/3 dos
elementos e então fazer uma chamada
recursiva sobre os n/3 elementos restantes
124
125. www.professoresalgoritmos.com
Exemplo 1- Algoritmo recursivo
void pesquisa(int n)
{
if(n<=1)
{
cout<<"Inspeciona o elemento e termina";
}
else
{
cout<<"nPara cada um dos n elementos -
inspecione o elemento";
pesquisa(n/3);
}
}
125
126. www.professoresalgoritmos.com
Exemplo 1- Algoritmo recursivo
void pesquisa(int n)
{
if(n<=1)
{
cout<<"Inspeciona o elemento e termina";
}
else
{
cout<<"nPara cada um dos n elementos -
inspecione o elemento";
pesquisa(n/3);
}
}
Para esse
exemplo, a
complexidade
será O(n),
Complexidade
linear.
126
127. www.professoresalgoritmos.com
Exemplo
• Considere o algoritmo para ordenar n
elementos de um vetor v cujo princípio é o
seguinte:
1. Selecione o menor elemento do vetor
2. Troque esse elemento com o primeiro elemento
do v[0]
3. A seguir, repita essas duas operações com os n-1
elementos restantes, depois com os n-2
elemento, até que reste apenas um elemento
127
131. www.professoresalgoritmos.com
Exemplo 2- Programa para ordenar
Devemos começar a
análise pelo anel
interno. Nesse anel
temos um comando
de decisão que, por
sua vez, possui
apenas um
comando de
atribuição.
131
133. www.professoresalgoritmos.com
Exemplo 2- Programa para ordenar
Não sabemos se o
corpo do comando
de decisão será
executado ou não:
nessas situações
devemos considerar
o pior caso, isto é,
assumir que a linha
10 sempre será
executada.
133
134. www.professoresalgoritmos.com
Exemplo 2- Programa para ordenar
O tempo para
incrementar o índice
do anel e avaliar sua
condição de
terminação também é
O(1), e o tempo
combinado para
executar uma vez o
anel composto pelas
linhas de 6 a 11 é
O(max(1,1,1)) = O(1),
conforme a regra da
soma para notação O.
134
136. www.professoresalgoritmos.com
Exemplo 2- Programa para ordenar
O corpo do anel mais
externo contém, além
do anel interno, os
comandos de
atribuição nas linhas
5, 13, 14 e 15. Logo, o
tempo de execução
das linhas de 5 a 15 é
O(max(1,(n-i),1,1,1)) =
O(n-i).
136
137. www.professoresalgoritmos.com
Exemplo 2- Programa para ordenar
A linha 3 é executada n-
1 vezes, e o tempo total
para executar o
programa está limitado
ao produto de uma
constante pelo
somatório de (n – i) a
saber:
137
139. www.professoresalgoritmos.com
Complexidade de Tempo
• Melhor caso: corresponde ao menor tempo
de execução sobre todas as possíveis entradas
de tamanho n
• Caso médio (ou caso esperado): corresponde
à média dos tempos de execução de todas as
entradas de tamanho n
• Pior caso: corresponde ao maior tempo de
execução sobre todas as possíveis entradas de
tamanho n
139
140. www.professoresalgoritmos.com
Exemplo3
Considere o problema de encontrar o maior e o menor
elementos de um vetor de inteiros v[0..n-1], n>=1
void calculaMaxMin1(int vet[], int n)
{
int max = vet[0], min = vet[0];
for(int i=1; i<n; i++)
{
if(vet[i]>max)
max = vet[i];
if(vet[i]<min)
min = vet[i];
}
}
140
141. www.professoresalgoritmos.com
Exemplo3
Para o exemplo anterior, temos que f é uma função de
complexidade tal que f(n) é o número de comparações entre
os elementos de vet, se vet contiver n elementos, temos
que:
f(n) = 2(n-1), para n > 0
Esse programa pode ser facilmente melhorado.
Basta observar que a comparação vet[i] < min
somente é necessária quando o resultado da
comparação vet[i] > max é falso.
141
142. www.professoresalgoritmos.com
Exemplo4 – Nova versão
void calculaMaxMin2(int vet[], int n)
{
int max = vet[0], min = vet[0];
for(int i=1; i<n; i++)
{
if(vet[i]>max)
max = vet[i];
else if(vet[i]<min)
min = vet[i];
}
}
142
143. www.professoresalgoritmos.com
Exemplo4 – Nova versão
void calculaMaxMin2(int vet[], int n)
{
int max = vet[0], min = vet[0];
for(int i=1; i<n; i++)
{
if(vet[i]>max)
max = vet[i];
else if(vet[i]<min)
min = vet[i];
}
}
Melhor caso: f(n) = n-1
Pior caso: f(n) = 2(n-1)
Caso médio: f(n) = (3n-3)/2
Melhor caso: o
vetor está
ordenado
crescente e no
pior caso está
ordenado
decrescente
143
144. www.professoresalgoritmos.com
Referência Bibliográfica
• ZIVIANI, Nivio. Projeto de Algoritmos com
implementações em Java e C++. São Paulo:
Thomson Learning, 2007.
• CORMEN, Thomas. Algoritmos: teoria e prática.
Campus, 2002.
• Notas de aula Profª Raquel Marcia Müller
(http://www.comp.uems.br/Members/rmmuller/
pg_aedii)
144
147. www.professoresalgoritmos.com
TAD - Exemplo
• Se criarmos um tipo para representar um ponto
no espaço, um cliente desse tipo usa-o de forma
abstrata, com base apenas nas funcionalidades
oferecidas pelo tipo
• A forma com que ele foi efetivamente
implementado (armazenando cada coordenada
num campo ou agrupando todas num vetor)
passa a ser um detalhe de implementação, que
não deve afetar o uso do tipo nos mais diversos
contextos
147
148. www.professoresalgoritmos.com
Modularização
• Vantagens:
– desacoplamos a implementação do uso
– facilitamos a manutenção
– aumentamos o potencial de reutilização do tipo
criado
– a implementação do tipo pode ser alterada sem
afetar seu uso em outros contextos.
• Modularização: divisão de um programa em
vários arquivos-fontes.
148
149. www.professoresalgoritmos.com
Modularização
• Um módulo agrupa vários tipos e funções com
funcionalidades relacionadas, caracterizando
assim uma finalidade bem definida.
• Se um módulo definir um novo tipo de dado e
o conjunto de operações para manipular
dados desse tipo, dizemos que o módulo
representa um tipo abstrato de dados (TAD)
149
151. www.professoresalgoritmos.com
Exemplo 1 – TAD Ponto
• Operações:
– Cria: operação que cria um ponto com coordenadas x
e y
– Atribui: operação que atribui novos valores às
coordenadas de um ponto
– Distancia: operação que calcula a distância entre dois
pontos
– Libera: operação que libera a memória alocada por
um ponto
• Interface: arquivo ponto.h
151
152. www.professoresalgoritmos.com
Exemplo 2 – TAD Circulo
• Operações:
– Cria: operação que cria um circulo com centro
(x,y) e raio r
– Area: operação que calcula a area do circulo
– Interior: operação que verifica se um dado ponto
está dentro do circulo
– Libera: operação que libera a memória alocada
por um circulo
• Interface: arquivo circulo.h
152
153. www.professoresalgoritmos.com
Referência Bibliográfica
• ZIVIANI, Nivio. Projeto de Algoritmos com
implementações em Java e C++. São Paulo:
Thomson Learning, 2007. Capítulo 1.
• CORMEN, Thomas. Algoritmos: teoria e prática.
Campus, 2002.
• Celes, Waldemar; Cerqueira, Renato e Rangel,
José Lucas. Introdução a Estruturas de Dados.
Rio de Janeiro: Elsevier, 2004 – 5ª impressão.
Capítulo 9.
153
155. www.professoresalgoritmos.com
Introdução
• O vetor não é estrutura muito flexível, pois
precisamos dimensioná-lo com um número
máximo de elementos
– Complexidade das funções para inserir e remover
usando vetor em um tempo linear é O(n)
• Solução: utilizar estruturas de dados que cresçam
conforme precisamos armazenar novos
elementos
– E diminuam a medida que retiramos elementos
armazenados
155
156. www.professoresalgoritmos.com
Introdução
• Essas estruturas são chamadas dinâmicas e
armazenam cada um dos seus elementos por
alocação dinâmica
– Complexidade para inserir e remover: O (1)
• A primeira estrutura a ser estudada é a lista
encadeada
• As listas encadeadas são amplamente
utilizadas para implementar diversas outras
estruturas de dados com semânticas próprias
156
159. www.professoresalgoritmos.com
Lista Linear Encadeada
• Para cada novo elemento inserido na estrutura
=> alocamos um espaço de memória para
armazená-lo
• Assim, o espaço total de memória gasto pela
estrutura é proporcional ao número de
elementos armazenados
• Não podemos garantir que os elementos
armazenados na lista ocuparão um espaço
contíguo de memória
– Portanto, não temos acesso direto aos elementos da
lista
159
160. www.professoresalgoritmos.com
Lista Linear Encadeada
• Exemplos de listas:
Lista Telefônica
Lista de clientes de uma agência bancária
Lista de setores de disco a serem acessados por
um sistema operacional
Lista de pacotes a serem transmitidos em um nó
de uma rede de computação de pacotes
160
161. www.professoresalgoritmos.com
Lista Linear Encadeada
• Para percorrer todos os elementos da lista,
devemos explicitamente guardar o seu
encadeamento
Isso é feito armazenando-se junto
com a informação de cada
elemento, um ponteiro para o
próximo elemento da lista
161
163. www.professoresalgoritmos.com
Lista Linear Encadeada
• Estrutura:
– Consiste em uma sequência encadeada de
elementos, em geral chamados nós (nodos) da
lista
– Um nó da lista é representado por um estrutura
que contém dois campos: a informação
armazenada e o ponteiro para o próximo
elemento da lista
163
164. www.professoresalgoritmos.com
Lista Linear Encadeada
• A lista é representada por um ponteiro para o
primeiro elemento (ou nó)
• Do primeiro elemento, podemos alcançar o
segundo, seguindo o encadeamento, e assim
por diante
• O último elemento da lista possui um ponteiro
para inválido, com valor NULL e sinaliza que
não existe um próximo elemento
164
167. www.professoresalgoritmos.com
1- Função Inserir na Lista
• Uma vez criada a lista vazia, podemos inserir
nela novos elementos
• Para cada elemento inserido, devemos alocar
dinamicamente a memória necessária para
armazenar o elemento e encadeá-lo na lista
existente
• Parâmetros para a função: o ponteiro para a
lista e o valor/ informação do novo elemento
167
168. www.professoresalgoritmos.com
1- Função Inserir na Lista
• A função inserir na lista pode:
– Inserir um novo elemento no fim da lista, fazendo
que o último elemento aponte para NULL,
– Inserir no início da lista fazendo com que o prim
(primeiro ponteiro) aponte para esse novo
elemento.
168
169. www.professoresalgoritmos.com
2- Função Imprime os elementos
• Essa função percorre todos os elementos da
Lista e imprime os valores dos elementos
armazenados na lista
• Usamos uma variável auxiliar que é um
ponteiro (aux) que aponta para cada uma das
estruturas até chegar no NULL
169
170. www.professoresalgoritmos.com
3- Função Verifica se a Lista está vazia
• Essa função pode ser útil e utilizada em outras
funções
• A função recebe a lista e retorna 1 se estiver
vazia ou 0 se não estiver vazia
• Uma lista está vazia se seu valor é NULL
170
172. www.professoresalgoritmos.com
4- Função Remover um elemento
• Parâmetros: lista e o valor do elemento que
desejamos remover da lista
• Função mais complexa
• Se o elemento a ser retirado for o primeiro da
lista: devemos fazer
172
174. www.professoresalgoritmos.com
Listas Circulares
• Algumas aplicações necessitam representar
conjuntos cíclicos
• Estrutura: o último elemento tem como
próximo o primeiro elemento da lista, o que
forma um ciclo
• Nesse caso nem faz sentido em falar em
primeiro ou último elemento já que é um ciclo
– dessa forma, a lista pode ser representada por
um ponteiro para um elemento inicial qualquer
174
176. www.professoresalgoritmos.com
Função Imprime elementos
Lista Circular
• Para percorrer os elementos de uma lista
circular é necessário visitar todos os
elementos a partir de um ponteiro do
elemento inicial até alcançar novamente esse
mesmo elemento
176
179. www.professoresalgoritmos.com
Listas Duplamente Encadeada
• A lista encadeada vista anteriormente,
também chamada Lista Simplesmente
Encadeada (LSE), caracteriza-se por formar um
encadeamento simples entre os elementos:
–Cada elemento armazena um ponteiro para
o próximo elemento da lista
179
180. www.professoresalgoritmos.com
Listas Duplamente Encadeada
• Problemas da LSE:
– não conseguimos percorrer eficientemente os
elementos em ordem inversa (do final para o
início da lista)
– O encademento simples também dificulta a
retirada de um elemento da lista, pois não temos
um ponteiro para o elemento anterior ao ser
retirado
180
181. www.professoresalgoritmos.com
Listas Duplamente Encadeada
• Solução: Listas duplamente encadeadas
• Nessas listas, cada elemento tem um ponteiro
para o próximo e um ponteiro para o
elemento anterior
• Assim, dado um elemento, podemos acessar
os dois elementos adjacentes: o próximo e o
anterior
181
185. www.professoresalgoritmos.com
Função Remover Elemento da
Lista Duplamente Encadeada
• A função fica mais complicada, pois é
necessário acertar o encadeamento duplo
– Em contrapartida, podemos retirar um elemento
da lista se conhecermos apenas o ponteiro para
esse elemento
185
186. www.professoresalgoritmos.com
Função Remover Elemento da
Lista Duplamente Encadeada
• Se p representa o ponteiro do elemento que
desejamos retirar, para acertar o
encadeamento devemos conceitualmente
fazer:
p-> ant-> prox = p-> prox;
p-> prox -> ant = p-> ant;
Isto é, o anterior passa a apontar para o próximo, e
o próximo passa a apontar para o anterior 186
187. www.professoresalgoritmos.com
Função Remover Elemento da
Lista Duplamente Encadeada
• Se p aponta para um elemento no meio da
lista: as duas atribuições são suficientes para
acertar o encadeamento
• Se p aponta para um elemento no extremo da
lista:
– Se p for o último elemento, o elemento anterior
deverá apontar para NULL quando p for removido
– Se p for o primeiro elemento, o ponteiro para o
primeiro deverá apontar para o próximo elemento
187
190. www.professoresalgoritmos.com
Exercícios
Sabendo-se que as variáveis “prim” e “ult” são,
respectivamente, ponteiros para o início e o final de
uma lista simplesmente encadeada com 5 elementos, o
procedimento “abcd” é utilizado para:
[A] incluir um elemento no final da lista.
[B] excluir o último elemento da lista.
[C] incluir um elemento no início da lista.
[D] excluir o primeiro elemento da lista.
190
191. www.professoresalgoritmos.com
Exercícios
4- Marque (certo) ou (errado):
a) (CESPE - 2008 - TRT - 5ª Região (BA) - Técnico Judiciário -
Tecnologia da Informação )
A principal característica de uma lista encadeada é o fato de
o último elemento da lista apontar para o elemento
imediatamente anterior.
b) (CESPE - 2009 - ANAC - Técnico Administrativo -
Informática)Em uma lista circular duplamente encadeada,
cada nó aponta para dois outros nós da lista, um anterior e
um posterior.
191
192. www.professoresalgoritmos.com
Exercícios
(Poscomp-2011)
( ) Uma lista permite que as inserções possam ser feitas em
qualquer lugar (posição), mas as remoções, não.
( ) Em uma lista circular com encadeamento simples, o
primeiro elemento aponta para o segundo e para o último.
( ) Para remover um elemento de uma lista duplamente
encadeada, deve-se alterar o encadeamento dos elementos
anterior e próximo ao elemento removido.
192
193. www.professoresalgoritmos.com
Referência Bibliográfica
• ZIVIANI, Nivio. Projeto de Algoritmos com
implementações em Java e C++. São Paulo:
Thomson Learning, 2007.
• CORMEN, Thomas. Algoritmos: teoria e prática.
Campus, 2002.
• Celes, Waldemar; Cerqueira, Renato e Rangel,
José Lucas. Introdução a Estruturas de Dados.
Rio de Janeiro: Elsevier, 2004 – 5ª impressão.
Capítulo 10.
193
196. www.professoresalgoritmos.com
Introdução
• Uma das Estrutura de dados mais simples é a
PILHA
– Por isso, é a mais utilizada em programação
Principal ideia: todo acesso a seus
elementos é feito a partir do topo
196
197. www.professoresalgoritmos.com
Introdução
• Quando um novo elemento é introduzido na
pilha, ele passa a ser o elemento do topo
• O único elemento que pode ser removido da
pilha é o do topo
• Os elementos da pilha só podem ser retirados
na ordem inversa à ordem em que foram
introduzidos: o primeiro que sai é o último
que entrou (LIFO – Last in, first out)
197
204. www.professoresalgoritmos.com
Exemplo - Pilha
• O exemplo mais próximo é a própria pilha de
execução da linguagem C
– As variáveis locais das funções são
dispostas em uma pilha, e uma função só
tem acesso às variáveis da função que está
no topo
• Não é possível acessar as variáveis da função
locais às outras funções
204
209. www.professoresalgoritmos.com
int main()
{
Pilha* pi = cria_pilha();
push_pilha(pi,2);
push_pilha(pi,4);
push_pilha(pi,1);
imprime_pilha(pi);
getch();
system("cls");
pop_pilha(pi);
imprime_pilha(pi);
getch();
}
209
210. www.professoresalgoritmos.com
Exercícios
1- Faça uma função que retorne a quantidade de
elementos (tamanho) de uma pilha.
2- Faça uma função para concatenar duas pilhas, essa
função deve receber as pilhas como parâmetro
(observe a imagem).
2.1
4.5
1.0
P1 – topo -> 7.2
3.1
9.8
P2 – topo ->
7.2
3.1
9.8
2.1
4.5
1.0
P1 – topo ->
concatena
210
211. www.professoresalgoritmos.com
Referência Bibliográfica
• ZIVIANI, Nivio. Projeto de Algoritmos com
implementações em Java e C++. São Paulo:
Thomson Learning, 2007.
• CORMEN, Thomas. Algoritmos: teoria e prática.
Campus, 2002.
• Celes, Waldemar; Cerqueira, Renato e Rangel,
José Lucas. Introdução a Estruturas de Dados.
Rio de Janeiro: Elsevier, 2004 – 5ª impressão.
Capítulo 11.
211
214. www.professoresalgoritmos.com
Introdução
• Outra Estrutura de dados bastante usada na
computação é a FILA
• O que a diferencia da pilha é a ordem de saída
dos elementos: enquanto na pilha “o último
que entra é o primeiro que sai”, na fila “o
primeiro que entra é o primeiro que sai”
214
216. www.professoresalgoritmos.com
Introdução
• Analogia natural com a fila do dia-a-dia: quem
entra primeiro na fila é o primeiro a se
atendido (ex. fila de Banco, fila do CAA, fila do
Mc Donald, etc)
• Os elementos da fila só podem ser retirados
na ordem em que foram introduzidos: o
primeiro que entra é o primeiro que sai
(FIFO – First in, First out)
216
218. www.professoresalgoritmos.com
Exemplo - Fila
• Um exemplo de utilização em computação é a
implementação de uma fila de impressão:
– Impressora é compartilhada por várias máquinas:
adotar uma estratégia para determinar o
documento será impresso primeiro
• Estratégia mais simples: tratar todas as requisições
com a mesma prioridade e imprimir os
documentos na ordem em que forem submetidos
(o primeiro submetido é o primeiro a ser impresso)
218
226. www.professoresalgoritmos.com
Exercícios
1- Faça uma função que retorna a quantidade de
elementos existem na fila.
2- Faça uma função que verifica se existe um
determinado número(valor) inserido na fila.
226
227. www.professoresalgoritmos.com
Referência Bibliográfica
• ZIVIANI, Nivio. Projeto de Algoritmos com
implementações em Java e C++. São Paulo:
Thomson Learning, 2007.
• CORMEN, Thomas. Algoritmos: teoria e prática.
Campus, 2002.
• Celes, Waldemar; Cerqueira, Renato e Rangel,
José Lucas. Introdução a Estruturas de Dados.
Rio de Janeiro: Elsevier, 2004 – 5ª impressão.
Capítulo 12.
227
229. www.professoresalgoritmos.com
Introdução
• Uma função é dita recursiva se definida em
termos dela mesma
• Ou seja, uma função é recursiva quando
dentro dela está presente uma instrução de
chamada a ela própria
229
231. www.professoresalgoritmos.com
Exemplo – Fatorial Recursivo
int main()
{
int num;
do{
cout<<"nDigite um numero ou negativo para terminar: ";
cin>>num;
if(num>0)
{
cout<<"nO fatorial de "<<num<<" e: "<<fatorial(num);
}
}while(num>0);
cout<<"nFim do programa";
getch();
}
231
232. www.professoresalgoritmos.com
Introdução
• O código gerado por uma função recursiva
exige a utilização de mais memória, o que
torna a execução mais lenta
• Não é difícil criar funções recursivas, o difícil é
reconhecer as situações nas quais a recursão é
apropriada
• Três pontos devem ser lembrados quando
queremos escrever uma função recursiva
232
233. www.professoresalgoritmos.com
Criando uma função recursiva
• 1º Passo: definir o problema em termos
recursivos
• Isso significa definir o problema usando ele
mesmo na definição
• Ex.: O fatorial de um número pode ser
definido por meio da seguinte expressão:
n! = n * (n-1)!
233
234. www.professoresalgoritmos.com
Criando uma função recursiva
• 2º Passo: encontrar a condição básica. Toda
função recursiva deve ter uma condição de
término chamada de condição básica
• A função fatorial(), quando chamada, verifica
se num é zero
– Se a condição for satisfeita, interrompe a
recursão
234
235. www.professoresalgoritmos.com
Criando uma função recursiva
• 3º Passo: cada vez que a função é chamada
recursivamente deve estar mais próxima de
satisfazer a condição básica
• Isso garante que o programa não girará em
uma sequência infinita de chamadas
• No exemplo, a cada chamada, o valor de num
estará mais próximo de zero
235
236. www.professoresalgoritmos.com
Como trabalha uma função recursiva?
• Para entender o funcionamento de uma
função recursiva, vamos imaginar que a
chamada recursiva é a chamada a outra
função que tenha o mesmo código da função
original
236
238. www.professoresalgoritmos.com
Como trabalha uma função recursiva?
• Supondo que o número digitado tenha sido 3:
int fatorial(int 3)
{
...
else
{
return(3 * fatorial(2));
}
}
int fat1(int 2)
{
...
else
{
return(2 * fatorial(1));
}
}
238
239. www.professoresalgoritmos.com
Como trabalha uma função recursiva?
• Supondo que o número digitado tenha sido 3:
int fatorial(int 3)
{
...
else
{
return(3 * fatorial(2));
}
}
int fat1(int 2)
{
...
else
{
return(2 * fatorial(1));
}
}
int fat2(int 1)
{
...
else
{
return(1 * fatorial(0));
}
}
239
240. www.professoresalgoritmos.com
Como trabalha uma função recursiva?
• Supondo que o número digitado tenha sido 3:
int fatorial(int 3)
{
...
else
{
return(3 * fatorial(2));
}
}
int fat1(int 2)
{
...
else
{
return(2 * fatorial(1));
}
}
int fat2(int 1)
{
...
else
{
return(1 * fatorial(0));
}
}
int fat3(int 0)
{
if(n == 0)
{
return (1);
}
...
}
240
241. www.professoresalgoritmos.com
Como trabalha uma função recursiva?
• Supondo que o número digitado tenha sido 3:
int fatorial(int 3)
{
...
else
{
return(3 * fatorial(2));
}
}
int fat1(int 2)
{
...
else
{
return(2 * fatorial(1));
}
}
int fat2(int 1)
{
...
else
{
return(1 * fatorial(0));
}
}
int fat3(int 0)
{
if(n == 0)
{
return (1);
}
...
}
241
242. www.professoresalgoritmos.com
Como trabalha uma função recursiva?
• O que ocorre na memória é quase a mesma
coisa, exceto pelo fato de que não há
repetição do código da função
• Observe que várias chamadas estão ativas ao
mesmo tempo
• Enquanto a última chamada não terminar, a
penúltima não termina e assim por diante
– Isso faz as variáveis de cada chamada serem todas
mantidas na memória, o que requer mais
memória
242
244. www.professoresalgoritmos.com
Característica da função recursiva
• As funções recursivas devem ter:
– Ponto de Parada: resolvido sem utilização de
recursividade, sendo este ponto geralmente um
limite superior ou inferior da regra geral.
– Regra Geral: o método geral da recursividade
reduz a resolução do problema através da
invocação recursiva de casos mais pequenos,
sendo estes casos menores resolvidos através da
resolução de casos ainda menores, e assim
sucessivamente, até atingir o ponto de parada
que finaliza o método.
244
250. www.professoresalgoritmos.com
Exemplo – Impressão de um seqüência de números
void print_numero(int num)
{
if (num > 0)
{
print_numero(num-1);
cout << num << " ";
}
}
void print_numero_inv(int num)
{
if (num > 0)
{
cout << num << " ";
print_numero_inv(num-1);
}
}
int main()
{
int numero;
cout << "Digite o numero inicial: ";
cin >> numero;
print_numero(numero);
cout << endl;
print_numero_inv(numero);
cout << endl;
system("pause");
}
250
251. www.professoresalgoritmos.com
Exemplo – Resto da divisão de um número por outro
(método sem recursão)
#include <iostream.h>
int resto(int x, int y)
{
while(x >= y)
{
x = x –y;
}
return( x );
}
int main()
{
int num, den;
cout << "Digite o numerador: ";
cin >> num;
cout << "Digite o denominador: ";
cin >> den;
cout << resto(num, den);
cout << endl;
system("pause");
}
251
252. www.professoresalgoritmos.com
Exemplo – Resto da divisão de um número por outro
(método recursivo)
#include <iostream.h>
int resto(int x, int y)
{
if (x < y)
return(x);
return( resto(x - y, y) );
}
int main()
{
int num, den;
cout << "Digite o numerador: ";
cin >> num;
cout << "Digite o denominador: ";
cin >> den;
cout << resto(num, den);
cout << endl;
system("pause");
}
252
253. www.professoresalgoritmos.com
Exercícios
1- Escreva uma função recursiva denominada
potencia() que aceite dois parâmetros inteiros
positivos i e j. A função retorna i elevado a
potência j. Por exemplo: potencia(2,3) é igual a
8. Use a seguinte definição:
i elevado à potência j é igual a i elevado à
potência j-1 vezes i
253
254. www.professoresalgoritmos.com
Exercícios
2- Escreva uma função recursiva de nome soma()
que receba um número inteiro positivo n como
argumento e retorne a soma dos n primeiro
números inteiros.
Por exemplo, se a função receber n= 5, deve
retornar 15, pois 15 = 1+2+3+4+5
254
255. www.professoresalgoritmos.com
Referência Bibliográfica
• ZIVIANI, Nivio. Projeto de Algoritmos com
implementações em Java e C++. São Paulo:
Thomson Learning, 2007.
• CORMEN, Thomas. Algoritmos: teoria e prática.
Campus, 2002.
• Celes, Waldemar; Cerqueira, Renato e Rangel,
José Lucas. Introdução a Estruturas de Dados.
Rio de Janeiro: Elsevier, 2004 – 5ª impressão.
Capítulo 11.
255
258. www.professoresalgoritmos.com
Introdução
• Estruturas de dados chamadas lineares como
vetores e listas não são adequadas para
representar dados que devem ser dispostos e
maneira hierárquica
– Exemplo: arquivos (documentos) que criamos em
um computador são armazenados dentro de uma
estrutura hierárquica de diretórios (pastas)
– Existe um diretório base dentro do qual podemos
armazenar diversos subdiretórios e arquivos
258
259. www.professoresalgoritmos.com
Introdução
• Árvores: são estruturas de dados adequadas
para a representação de hierarquias
– A forma mais natural de definir uma estrutura de
árvore é usando a recursividade
• Recursividade: habilidade de uma função
chamar a si mesma
259
260. www.professoresalgoritmos.com
Recursividade
• Uma função poderá também ser considerada
recursiva se chamar outras funções que, em
algum momento, chamem a primeira função,
tornando esse conjunto de funções um processo
recursivo.
• Cada vez que uma função é chamada de forma
recursiva, é guardada uma cópia dos seus
parâmetros de forma a não perder os valores dos
parâmetros das chamadas anteriores.
260
261. Árvores
Uma árvore é composta por:
• um nó Raiz, denominado r, que contém zero
ou mais sub árvores
• nós folhas ou extremos, que não possuem
filhos
261
263. www.professoresalgoritmos.com
Árvores
• É tradicional desenhar as estruturas de
árvores com a raiz para cima e as folhas para
baixo
• Não fica explicita a direção dos ponteiros
– Fica subentendido que os ponteiros apontam
sempre do pai para os filhos
– Os tipos de árvores existentes são diferenciados
pelo número de filhos por nó e as informações
armazenadas em cada nó
263
264. Árvores Binárias
• Exemplo de utilização:
avaliação de expressões
• Como trabalhamos com
operadores que
esperam um ou dois
operandos, os nós da
árvore para representar
uma expressão têm no
máximo dois filhos
264
265. Árvores Binárias
• nós folhas representam os
operandos
• nós internos representam
operadores
• No exemplo, a expressão
representada é a:
(3+6) * (4-1) + 5
265
266. Árvores Binárias
• Em uma árvore binária, cada
nó tem zero, um ou dois filhos.
• Recursivamente, podemos
definir uma árvore binária
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)
raiz
sae sad
Vazia
* A definição recursiva será usada na construção de algoritmos e na
verificação (informal) da correção e do seu desempenho
266
267. www.professoresalgoritmos.com
Árvores Binárias
a
b c
d e f
Os nós a, b, c, d, e e f formam uma árvore
binária:
- Sub árvore à esquerda formada por b e
d
- Sub árvore à direita formada por c, e e f
- A raiz da árvore representada pelo nó a
- As raízes das sub árvores representadas
pelos nós b e c
- Folhas representadas pelos nós d, e e f
- Além disso, cada nó folha representa
uma árvore, com duas sub árvores vazias.
267
268. www.professoresalgoritmos.com
Árvores Binárias
a
b c
d e f
Podemos usar a seguinte notação textual:
- A árvore vazia é representada por < >
- e a árvore não vazia, por <raiz sae sad>
Para o nosso exemplo:
<a<b< ><d< >< >>><c<e< >< >><f< >< >>>>
268
269. www.professoresalgoritmos.com
Representação
• De modo semelhante ao que fizemos para as
demais estruturas, podemos definir um tipo
para representar uma árvore binária
struct arv
{
char info;
arv* esq;
arv* dir;
};
269
270. www.professoresalgoritmos.com
Representação
• Da mesma forma que uma lista encadeada é
representada por um ponteiro para o nó para
o primeiro nó, a estrutura da árvore é
representada por um ponteiro para o nó raiz
• Dado o ponteiro para o nó raiz tem-se acesso
aos demais nós
270
272. www.professoresalgoritmos.com
Operações básicas
• Cria árvore não-vazia:
– Para construir árvores não-vazias, podemos ter
uma operação que cria um nó raiz dadas a
informação e as duas sub árvores, a da esquerda e
a da direita
– Essa operação tem como retorno o endereço do
nó raiz criado
272
273. www.professoresalgoritmos.com
Operações básicas
• Imprime árvore:
– Consiste em exibir todo o conteúdo da árvore
– Essa função deve percorrer recursivamente a
árvore, visitando todos os nós e imprimindo sua
informação
– Como uma árvore binária ou é vazia ou é
composta pela raiz e por duas sub árvores
273
274. www.professoresalgoritmos.com
Operações básicas
• Imprime árvore:
– Portanto, para imprimir a informação de todos os
nós da árvore devemos primeiro testar se ela é
vazia
– se não for, imprimimos a informação associada à
raiz e chamamos (recursivamente) a função para
imprimir as sub árvores
274
276. www.professoresalgoritmos.com
Referência Bibliográfica
• ZIVIANI, Nivio. Projeto de Algoritmos com
implementações em Java e C++. São Paulo:
Thomson Learning, 2007.
• CORMEN, Thomas. Algoritmos: teoria e prática.
Campus, 2002.
• Celes, Waldemar; Cerqueira, Renato e Rangel,
José Lucas. Introdução a Estruturas de Dados.
Rio de Janeiro: Elsevier, 2004 – 5ª impressão.
Capítulo 13.
276
278. www.professoresalgoritmos.com
Introdução
• Dois argumentos favoráveis às árvores:
1. as árvores são bem apropriadas para representar
a estrutura hierárquica de um certo domínio
2. o processo de busca é muito mais rápido
usando árvores do que listas encadeadas
• No entanto, o 2º argumento, nem sempre se
mantém
278
281. www.professoresalgoritmos.com
Introdução
• Uma árvore é dita balanceada quando as suas
sub-árvores à esquerda e à direita possuem a
mesma altura.
• E todos os nós vazios estão no mesmo nível,
ou seja, a árvore está completa.
• A árvore que não está balanceada, define-se
como degenerada
281
283. www.professoresalgoritmos.com
Introdução
• Como em uma árvore binária cada nó pode
ter dois filhos, o número de nós em um certo
nível é o dobro do número de ascendentes
que residem no nível prévio
Altura Nível Nós em um nível
1 0 2 ^ 0 = 1
2 1 2 ^ 1 = 2
3 2 2 ^ 2 = 4
4 3 2 ^ 3 = 8
283
286. www.professoresalgoritmos.com
Balanceamento
• Balanceamento Dinâmico: AVL
– Árvore AVL em homenagem aos matemáticos russos
(Adelson-Velskii e Landism -1962)
– Uma árvore AVL é uma árvore binária de pesquisa
onde a diferença em altura entre as subárvores
esquerda e direita é no máximo 1 (positivo ou
negativo).
• A essa diferença chamamos de “fator de balanceamento”
de n(FatBal (n)).
• Essa informação deverá constar em cada nó de uma árvore
balanceada
286
287. www.professoresalgoritmos.com
Árvore AVL
• Árvore AVL (ou árvore balanceada pela altura)
• Assim, para cada nodo podemos definir um
fator de balanceamento (FB) , que vem a ser
um número inteiro igual a:
• O Fator de uma folha é sempre Zero (0)
FB(nodo p) = altura(subárvore direita p) -
altura(subárvore esquerda p)
287
288. www.professoresalgoritmos.com
Exemplos de Árvores AVL
• Os números nos nodos representam o FB para cada
nodo.
• Para uma árvore ser AVL os fatores de balanço
devem ser necessariamente -1, 0, ou 1.
288
290. www.professoresalgoritmos.com
Árvore AVL
• Se o fator de balanceamento de qualquer nó
em uma árvore AVL se tornar menor do que -1
ou maior do que 1
– a árvore tem que ser balanceada
– Um arvore AVL pode ser tornar desbalanceada em
quatro situações, mas somente duas delas
necessitam ser analisadas
• as outras duas são simetricas
290
291. www.professoresalgoritmos.com
Balanceamento de Árvore AVL
• Inicialmente inserimos um novo nodo na
árvore.
– A inserção deste novo nodo pode ou não violar a
propriedade de balanceamento.
• Caso a inserção do novo nodo não viole a
propriedade de balanceamento
– Podemos então continuar inserindo novos nodos.
291
292. www.professoresalgoritmos.com
Balanceamento de Árvore AVL
• Caso contrário precisamos nos preocupar em
restaurar o balanço da árvore.
– A restauração deste balanço é efetuada através
do que denominamos ROTAÇÕES na árvore.
– “Rotações” => movimentações dos nós, pode ser
feito a medida que um nó é inserido
292
293. www.professoresalgoritmos.com
Balanceamento de Árvore AVL
• Primeiro caso: resultado de inserir um nó na
subárvore da direita do filho à direita (ver
próximo slide)
– Inserindo um nó em algum lugar da subarvore da
direita de Q, perturba o balanceamento da árvore P
– Para resolver: girar o nó Q ao redor de seu
ascendente P, de modo que o fator de balanceamento
tanto de P como de Q se torna zero, o que é ainda
melhor do que no princípio
293
295. www.professoresalgoritmos.com
Balanceamento de Árvore AVL
• Segundo caso: resultado de inserir um nó na
subárvore da esquerda do filho à direita (ver
próximos slides)
– Para trazer a árvore de volta ao balanceamento,
uma dupla rotação é realizada
– O balanço da árvore P é restaurado girando-se R
ao redor do nó Q e então girando-se R novament,
dessa vez ao redor do nó P
295
299. www.professoresalgoritmos.com
Dicas: Árvore AVL
1. Para identificarmos quando uma rotação é
simples ou dupla observamos os sinais de
FatBal:
– se o sinal for igual, a rotação é simples.
– se o sinal for diferente a rotação é dupla.
2. Se FB + rotação para esquerda
3. Se FB - rotação para direita
299
300. www.professoresalgoritmos.com
Exercícios
1- Considere a inserção dos seguintes valores
(nesta ordem) em uma árvore AVL:
5,3,8,2,4,7,10,1,6,9,11. Para essas inserções
nenhuma rotação é necessária. Desenhe a
árvore AVL resultante e determine o fator de
balanceamento de cada nó.
300
302. www.professoresalgoritmos.com
Referência Bibliográfica
• ZIVIANI, Nivio. Projeto de Algoritmos com
implementações em Java e C++. São Paulo:
Thomson Learning, 2007.
• DROZDEK, Adam. Estruturas de dados e
algoritmos em c++. São Paulo: Cengage
Learning, 2009. Tradução: Luiz Sérgio de
Castro Paiva. Capítulo: 6.
302
305. www.professoresalgoritmos.com
Árvore binária de busca
• O algoritmo de busca binária apresentado tem
bom desempenho computacional
• e deve ser usado quando temos os dados
ordenados armazenados em um vetor
305
306. www.professoresalgoritmos.com
Árvore binária de busca
• Mas se precisarmos inserir e remover elementos
da estrutura e ao mesmo tempo dar suporte a
funções de busca eficientes, a estrutura de vetor
– (e, consequentemente, a busca binária) não se mostra
adequada
• Para inserir um novo elemento em um vetor
ordenado, temos de rearrumar os elementos no
vetor para abrir espaço para inserção do novo
elemento
306
307. www.professoresalgoritmos.com
Árvore binária de busca
• Uma situação semelhante ocorre quando
removemos um elemento do vetor
• Sendo assim, precisamos de uma estrutura
dinâmica que dê suporte a operações de
busca
• No caso, podemos usar a estrutura estudada:
Árvore Binária
307
308. www.professoresalgoritmos.com
Árvore binária de busca
• As árvores binárias aqui consideradas têm
uma propriedade fundamental:
– o valor associado a raiz é sempre maior do que os
valores associados a qualquer nós das subárvores
• Essa propriedade garante que quando
percorremos a árvore em ordem simétrica
(sae – raiz – sad), os valores são encontrados
em ordem crescente
308
310. www.professoresalgoritmos.com
Árvore binária de busca
• Uma variação possível permite a repetição de
valores na árvore:
– o valor associado à raiz é sempre maior do que o valor
associado a qualquer nó da sae
– e é sempre menor ou igual ao valor associado a
qualquer nó sad
• Nesse caso, como a repetição de valores é
permitida, quando a árvore é percorrida em
ordem simétrica, os valores são encontrados em
ordem não decrescente
310
311. www.professoresalgoritmos.com
Árvore binária de busca
• Ao usar a propriedade de ordem simétrica, a
busca de um valor em uma árvore pode ser
feita de forma eficiente
• Para procurar um valor numa árvore,
comparamos o valor que buscamos ao valor
associado à raiz
– Em caso de igualdade, o valor foi encontrado
– Se o valor for menor, a busca continua em sae
– Se o valor for maior, a busca continua em sad
311