Você está na página 1de 93

Técnicas de

Programação
Prof. Edilson
Rodrigues
Contatos
ejrodrigues@prof.unisa.br

98477-2468

@edilson_fozzy
Ementa

Descrição e aplicação de conceitos avançados de programação aplicadas


no desenvolvimento de software e resolução de problemas
computacionais com conexão com banco de dados, baseado em padrões
de projeto. Exploração de técnicas avançadas em programação.
Compreensão e implementação de algoritmos de busca: linear e binária.
Compreensão e implementação de algoritmos de ordenação: intuitivos e
avançados. Compreensão e implementação dos conceitos de tabelas de
hash.
Conteúdo Programático

1. Algoritmos de busca
2. Algoritmos de ordenação
3. Algoritmos de ordenação eficientes
4. Estruturas complexas
5. Padrões de projeto
Objetivos

• Utilizar e conhecer os principais algoritmos para resolução de


problemas importantes na computação
• Compreender a necessidade do uso de um algoritmo.
Implementar os diversos tipos de algoritmos. Conceituar
padrões de projeto. Definir outros tipos de estruturas de dados.
Ser capaz de entender a complexidade de sistemas
computacionais. Conhecer e implementar os algoritmos de
busca. Conhecer e implementar os algoritmos de ordenação.
Conhecer as formas de armazenamento eficiente em memória.
Usar padrões para construir interfaces mais complexas
Ferramentas

• Linguagem de Programação
• Java, C#
Bibliografia
Complexidade
de Algoritmos
Medida do Tempo de Execução de um
Programa
• O projeto de algoritmos é fortemente influenciado pelo estudo
de seus comportamentos;
• Depois que um problema é analisado e decisões de projeto são
finalizadas, é necessário estudar as várias opções de
algoritmos a serem utilizados, considerando os aspectos de
tempo de execução e espaço ocupado;
• Muitos desses algoritmos são encontrados em áreas como
pesquisa operacional, otimização, teoria dos grafos, estatística,
probabilidades, entre outras.
Tipos de Problemas na Análise de
Algoritmos
• Análise de um algoritmo particular:
• Qual é o custo de usar um dado algoritmo para resolver
um problema específico?
• Características que devem ser investigadas:
• análise do número de vezes que cada parte do
algoritmo deve ser executada,
• estudo da quantidade de memória necessária.
Tipos de Problemas na Análise de
Algoritmos
• Análise de uma classe de algoritmos:
• Qual é o algoritmo de menor custo possível para resolver
um problema particular?
• Toda uma família de algoritmos é investigada;
• Procura-se identificar um que seja o melhor possível;
• Coloca-se limites para a complexidade computacional dos
algoritmos pertencentes à classe;
Custo de um Algoritmo

• Determinando o menor custo possível para resolver problemas


de uma dada classe, temos a medida da dificuldade inerente
para resolver o problema;
• Quando o custo de um algoritmo é igual ao menor custo
possível, o algoritmo é ótimo para a medida de custo
considerada;
• Podem existir vários algoritmos para resolver o mesmo
problema;
• Se a mesma medida de custo é aplicada a diferentes
algoritmos, então é possível compará-los e escolher o mais
adequado.
Medida do Custo pela Execução do
Programa
• Pode ser feito medindo diretamente o tempo de execução de
um programa em um computador real;
• Tais medidas são bastante inadequadas e os resultados jamais
devem ser generalizados:
• os resultados são dependentes do compilador que pode
favorecer algumas construções em detrimento de outras;
• os resultados dependem do hardware;
• quando grandes quantidades de memória são utilizadas,
as medidas de tempo podem depender deste aspecto.
Medida do Custo pela Execução do
Programa
• Apesar disso, há argumentos a favor de se obterem medidas
reais de tempo.
• Ex.: quando há vários algoritmos distintos para resolver um
mesmo tipo de problema, todos com um custo de
execução dentro de uma mesma ordem de grandeza.
• Assim, são considerados tanto os custos reais das
operações como os custos não aparentes, tais como
alocação de memória, indexação, carga, dentre outros.
Medida do Custo pela Execução do
Programa
• Usa um modelo matemático baseado em um computador
idealizado;
• Deve ser especificado o conjunto de operações e seus custos
de execuções;
• É mais usual ignorar o custo de algumas das operações e
considerar apenas as operações mais significativas;
• Ex.: algoritmos de ordenação. Consideramos o número de
comparações entre os elementos do conjunto a ser ordenado e
ignoramos as operações aritméticas, de atribuição e
manipulações de índices, caso existam.
Exemplo: Maior Elemento

Considere o algoritmo para encontrar o maior elemento de um


vetor de inteiros A[1..n], n >= 1.

int maior(Vetor A) {
int i, Temp;
Temp = A[0];
for (i = 1; i < n; i++)
if (Temp < A[i])
Temp = A[i];
return Temp;
}
Exemplo: Maior Elemento

Considere o algoritmo para encontrar o maior elemento de um


vetor de inteiros A[1..n], n >= 1.

int maior(Vetor A) {
int i, Temp;
Temp = A[0];
for (i = 1; i < n; i++)
if (Temp < A[i]) n–1
Temp = A[i]; comparações
return Temp;
}
Tamanho da Entrada de Dados

• A medida do custo de execução de um algoritmo depende


principalmente do tamanho da entrada dos dados.
• É comum considerar o tempo de execução de um programa
como uma função do tamanho da entrada.
Tamanho da Entrada de Dados

• No caso da função Max do programa do exemplo, o custo é


uniforme sobre todos os problemas de tamanho n.
• Já para um algoritmo de ordenação isso não ocorre: se os
dados de entrada já estiverem quase ordenados, então o
algoritmo pode ter que trabalhar menos.
Melhor Caso, Pior Caso
e Caso Médio
• Melhor caso: menor tempo de execução sobre todas as
entradas de tamanho n.
• Pior caso: maior tempo de execução sobre todas as entradas
de tamanho n.
• Caso médio (ou caso esperado): média dos tempos de
execução de todas as entradas de tamanho n.
Comportamento Assintótico de Funções

• O parâmetro n fornece uma medida da dificuldade para se


resolver o problema;
• Para valores suficientemente pequenos de n, qualquer
algoritmo custa pouco para ser executado, mesmo os
ineficientes;
• A escolha do algoritmo não é um problema crítico para
problemas de tamanho pequeno;
• Logo, a análise de algoritmos é realizada para valores grandes
de n;
Comportamento Assintótico de Funções

• Estuda-se o comportamento assintótico das funções de custo


(comportamento de suas funções de custo para valores
grandes de n);
• O comportamento assintótico de f(n) representa o limite do
comportamento do custo quando n cresce.
Notação O

• Utilizada para representar o comportamento assintótico de uma


função no pior caso
• Exemplo:
• 𝑓𝑓 𝑛𝑛 = 2 𝑛𝑛 + 1 + 7
• 𝑓𝑓 𝑛𝑛 = 𝑂𝑂(𝑛𝑛)
• Dois algoritmos com mesmo comportamento assintótico: neste
caso não é possível determinar qual o melhor, pois eles diferem
apenas por constantes
Comparação de programas

• Podemos avaliar programas comparando as funções de


complexidade, negligenciando as constantes de
proporcionalidade;
• Um programa com tempo de execução O(n) é melhor que outro
com tempo O(n2);
• Porém, as constantes de proporcionalidade podem alterar esta
consideração;
Comparação de programas

• Exemplo: um programa leva 100n unidades de tempo para ser


executado e outro leva 2n2. Qual dos dois programas é melhor?
• depende do tamanho do problema;
• Para n < 50, o programa com tempo 2n2 é melhor do que o
que possui tempo 100n;
• Para problemas com entrada de dados pequena é
preferível usar o programa cujo tempo de execução é
O(n2);
• Entretanto, quando n cresce, o programa com tempo de
execução O(n2) leva muito mais tempo que o programa
O(n).
Principais classes de problemas

• f(n) = O(1)
• Algoritmos de complexidade O(1) são ditos de
complexidade constante;
• Uso do algoritmo independe de n;
• As instruções do algoritmo são executadas um número fixo
de vezes;
Principais classes de problemas

• f(n) = O(log n)
• Um algoritmo de complexidade O(log n) é dito ter
complexidade logarítmica;
• Típico em algoritmos que transformam um problema em
outros menores;
• Pode-se considerar o tempo de execução como menor que
uma constante grande;
• Quando n é mil, log2 n ≈ 10, quando n é 1 milhão, log2 n ≈
20;
• Para dobrar o valor de log n temos de considerar o
quadrado de n;
• A base do logaritmo muda pouco estes valores: quando n é
1 milhão, o log2n é 20 e o log10n é 6.
Principais classes de problemas

• f(n) = O(n)
• Um algoritmo de complexidade O(n) é dito ter
complexidade linear;
• Em geral, um pequeno trabalho é realizado sobre cada
elemento de entrada;
• É a melhor situação possível para um algoritmo que tem
de processar/produzir n elementos de entrada/saída;
• Cada vez que n dobra de tamanho, o tempo de execução
dobra;
Principais classes de problemas

• f(n) = O(n log n)


• Típico em algoritmos que quebram um problema em outros
menores, resolvem cada um deles independentemente e
ajuntando as soluções depois.
• Quando n é 1 milhão, n log2 n é cerca de 20 milhões.
• Quando n é 2 milhões, n log2 n é cerca de 42 milhões,
pouco mais do que o dobro.
Principais classes de problemas

• f(n) = O(n2)
• Um algoritmo de complexidade O(n2) é dito ter
complexidade quadrática;
• Ocorrem quando os itens de dados são processados aos
pares, muitas vezes em um anel dentro de outro;
• Quando n é mil, o número de operações é da ordem de 1
milhão.
• Sempre que n dobra, o tempo de execução é multiplicado
por 4;
• Úteis para resolver problemas de tamanhos relativamente
pequenos;
Principais classes de problemas

• f(n) = O(n3)
• Um algoritmo de complexidade O(n3) é dito ter
complexidade cúbica;
• Úteis apenas para resolver pequenos problemas;
• Quando n é 100, o número de operações é da ordem de 1
milhão;
• Sempre que n dobra, o tempo de execução fica
multiplicado por 8;
Principais classes de problemas

• f(n) = O(2n)
• Um algoritmo de complexidade O(2n) é dito ter
complexidade exponencial;
• Geralmente não são úteis sob o ponto de vista prático;
• Ocorrem na solução de problemas quando se usa força
bruta para resolvê-los;
• Quando n é 20, o tempo de execução é cerca de 1 milhão.
Quando n dobra, o tempo fica elevado ao quadrado;
Principais classes de problemas

• f(n) = O(n!)
• Um algoritmo de complexidade O(n!) é dito ter
complexidade exponencial, apesar de O(n!) ter
comportamento muito pior do que O(2n);
• Geralmente ocorrem quando se usa força bruta na solução
do problema;
• n = 20 → 20! = 2432902008176640000, um número com
19 dígitos;
• n = 40 → um número com 48 dígitos;
Comparação de funções de complexidade
Algoritmos Polinomiais

• Algoritmos polinomiais são geralmente obtidos mediante


entendimento mais profundo da estrutura do problema;
• Um problema é considerado:
• intratável: se não existe um algoritmo polinomial para
resolvê-lo;
• bem resolvido: quando existe um algoritmo polinomial para
resolvê-lo;
Algoritmos Polinomiais X Algoritmos
Exponenciais
• A distinção entre algoritmos polinomiais eficientes e algoritmos
exponenciais ineficientes possui várias exceções;
• Exemplo: um algoritmo com função de complexidade f(n) = 2n é
mais rápido que um algoritmo g(n) = n5 para valores de n
menores ou iguais a 20;
• Também existem algoritmos exponenciais que são muito úteis
na prática;
• Exemplo: o algoritmo Simplex para programação linear possui
complexidade de tempo exponencial para o pior caso mas
executa muito rápido na prática;
• Tais exemplos não ocorrem com frequência na prática, e muitos
algoritmos exponenciais conhecidos não são muito úteis;
Exemplo de Algoritmo Exponencial

• Um caixeiro viajante deseja visitar n cidades de tal forma que


sua viagem inicie e termine em uma mesma cidade, e cada
cidade deve ser visitada uma única vez;
• Supondo que sempre há uma estrada entre duas cidades
quaisquer, o problema é encontrar a menor rota para a viagem;
• A figura ilustra o exemplo para quatro cidades c1; c2; c3; c4, em
que os números nos arcos indicam a distância entre duas
cidades;
Exemplo de Algoritmo Exponencial

O percurso <c1; c3; c4; c2; c1> é uma solução para o problema,
cujo percurso total tem distância 24.
Exemplo de Algoritmo Exponencial

• Um algoritmo simples seria verificar todas as rotas e escolher a


menor delas;
• Há (n – 1)! rotas possíveis e a distância total percorrida em
cada rota envolve n adições, logo o número total de adições é
n!;
• No exemplo anterior teríamos 24 adições;
• Suponha agora 50 cidades: o número de adições seria 50! ≈
1064;
• Em um computador que executa 109 adições por segundo, o
tempo total para resolver o problema com 50 cidades seria
maior do que 1045 séculos só para executar as adições;
Exemplo de Algoritmo Exponencial

O problema do caixeiro viajante aparece com frequência em


problemas relacionados com transporte, mas também aplicações
importantes relacionadas com otimização de caminho percorrido.
Buscas (ou
Pesquisa)
Introdução

• Importância em estudar busca


• Busca é uma tarefa muito comum?

• Vários métodos e estruturas de dados podem ser empregados


para se fazer busca
• Quais estruturas de dados?

• Certos métodos de organização/ordenação de dados podem


tornar o processo de busca mais eficiente
Algoritmos de Busca
• O problema de busca é caracterizado pela procura de um
determinado elemento em um grupo de elementos do
mesmo tipo.
– Exemplos:
• Encontrar o registro de um cliente entre todos os registros dos
clientes de um banco, para que seja possível informar seu
saldo.
• Encontrar o registro de um aluno dentre todos os registros dos
alunos de uma universidade, para que seja possível gerar o
seu histórico escolar.

• Algoritmos de busca visam resolver o problema da busca


e são, portanto, bastante utilizados em computação.

• Necessita-se de algoritmos eficientes de busca.


– De forma simplificada, um algoritmo eficiente é aquele que realiza
sua tarefa em tempo computacional reduzido.
Algoritmos de Busca
• O tempo de execução de um algoritmo de busca depende
do tamanho do conjunto de elementos a serem
consultados.

– É mais rápido procurar um elemento em um grupo de 100 do que


em um grupo de 100.000.

• Além disso, a eficiência do processo de busca depende


do algoritmo adotado.

– Exploraremos o conceito de complexidade de algoritmos no


sentido de tentar avaliar a eficiência dos algoritmos estudados.
Algoritmos de Busca
• Sem perda de generalidade, o problema será atacado
considerando-se que:

1. Os elementos a serem percorridos são numéricos, distintos e


estão armazenados em um vetor;

2. O número de elementos define o tamanho do vetor;

3. O elemento procurado pode não estar no vetor (no conjunto de


elementos).
Algoritmos de Busca

numeros: contém o grupo de elementos

7 19 4 10 18 6 8 1 3 12
0 1 2 3 4 5 6 7 8 9

posição dos elementos tamanho do vetor: len(valores) = 10


(índices) (número de elementos: 9+1=10)

Exemplo 1) Elemento procurado: 18 Resposta: posição 4

Exemplo 2) Elemento procurado: 5 Resposta: não encontrado


Exemplo

# Subprogramas

...

# Programa Principal de Busca


numeros = [0]*10 # Cria o vetor numeros zerado, com tamanho n = 10
preencher(numeros)

# dado: o elemento a ser procurado


dado = int(input(“Escolha valor a ser procurado: ”))

# onde: o local no vetor onde dado foi encontrado ou -1 se não encontrado


onde = buscaElemento(numeros, dado)

escreverResposta(dado, onde)

48
Exemplo (continuação)
# Subprogramas
def preencher(valores):
for ind in range(len(valores)):
valores[ind] = int(input(“Elemento[”+str(ind)+“]=”))
return None
def buscaElemento(valores, procurado):
...
def escreverResposta(valor, pos):
if pos<0:
print(valor,“não foi encontrado”)
else:
print(valor, “foi encontrado na posição”, pos)
return
# Programa Principal de Busca
numeros = [0]*10
preencher(numeros)
dado = int(input(“Escolha valor a ser procurado: ”))
onde = buscaElemento(numeros, dado)
escreverResposta(dado, onde)
49
Busca Simples (utilizando for)

Todas as posições do vetor são comparadas com o valor


procurado.

valores: contém o grupo de elementos

7 19 4 10 18 6 8 1 3 12
0 1 2 3 4 5 6 7 8 9

procurado: 18
local: -1

50
Busca Simples (utilizando for)

Todas as posições do vetor são comparadas com o valor


procurado.

valores: contém o grupo de elementos

7 19 4 10 18 6 8 1 3 12
0 1 2 3 4 5 6 7 8 9

procurado: 18
local: -1
indice: 0

51
Busca Simples (utilizando for)

Todas as posições do vetor são comparadas com o valor


procurado.

valores: contém o grupo de elementos

7 19 4 10 18 6 8 1 3 12
0 1 2 3 4 5 6 7 8 9

procurado: 18
local: -1
indice: 1

52
Busca Simples (utilizando for)

Todas as posições do vetor são comparadas com o valor


procurado.

valores: contém o grupo de elementos

7 19 4 10 18 6 8 1 3 12
0 1 2 3 4 5 6 7 8 9

procurado: 18
local: -1
indice: 2

53
Busca Simples (utilizando for)

Todas as posições do vetor são comparadas com o valor


procurado.

valores: contém o grupo de elementos

7 19 4 10 18 6 8 1 3 12
0 1 2 3 4 5 6 7 8 9

procurado: 18
local: -1
indice: 3

54
Busca Simples (utilizando for)

Todas as posições do vetor são comparadas com o valor


procurado.

valores: contém o grupo de elementos

7 19 4 10 18 6 8 1 3 12
0 1 2 3 4 5 6 7 8 9

procurado: 18
local: 4
indice: 4

55
Busca Simples (utilizando for)

Todas as posições do vetor são comparadas com o valor


procurado.

valores: contém o grupo de elementos

7 19 4 10 18 6 8 1 3 12
0 1 2 3 4 5 6 7 8 9

procurado: 18
local: 4
indice: 5

56
Busca Simples (utilizando for)

Todas as posições do vetor são comparadas com o valor


procurado.

valores: contém o grupo de elementos

7 19 4 10 18 6 8 1 3 12
0 1 2 3 4 5 6 7 8 9

procurado: 18
local: 4
indice: 6

57
Busca Simples (utilizando for)

Todas as posições do vetor são comparadas com o valor


procurado.

valores: contém o grupo de elementos

7 19 4 10 18 6 8 1 3 12
0 1 2 3 4 5 6 7 8 9

procurado: 18
local: 4
indice: 7

58
Busca Simples (utilizando for)

Todas as posições do vetor são comparadas com o valor


procurado.

valores: contém o grupo de elementos

7 19 4 10 18 6 8 1 3 12
0 1 2 3 4 5 6 7 8 9

procurado: 18
local: 4
indice: 8

59
Busca Simples (utilizando for)

Todas as posições do vetor são comparadas com o valor


procurado.

valores: contém o grupo de elementos

7 19 4 10 18 6 8 1 3 12
0 1 2 3 4 5 6 7 8 9

procurado: 18
local: 4
indice: 9

60
Busca Simples (utilizando for)

Todas as posições do vetor são comparadas com o valor


procurado.

valores: contém o grupo de elementos

7 19 4 10 18 6 8 1 3 12
0 1 2 3 4 5 6 7 8 9

procurado: 18
local: 4 Resultado
indice: 9

61
# Subprogramas
def preencher(valores):
...

def buscaElemento(valores, procurado):


local = -1
for indice in range(len(valores)):
if valores[indice]==procurado:
local = indice
return local

def escreverResposta(valor, pos):


...

# Programa Principal de Busca


numeros = [0]*10
preencher(numeros)
dado = int(input(“Escolha valor a ser procurado: ”))
onde = buscaElemento(numeros, dado)
escreverResposta(dado, onde)
62
Busca Simples (utilizando for)
• Todas as posições do vetor são comparadas com o valor
procurado, mesmo quando este é encontrado antes do
fim do vetor.

• Nitidamente, esta é uma busca ineficiente.

• O algoritmo avalia necessariamente n elementos, onde n


é o tamanho do vetor. Portanto, sua complexidade é da
ordem de n, representado por O(n).

63
Busca Simples (utilizando for com saída rápida)
• Utilizando-se sub-programação, não se considera uma
má prática de programação se realizar um return dentro
de uma repetição.

• O subprograma buscaElemento pode ter sua eficiência


melhorada quando encontra o elemento no vetor, não
necessitando repetir todos os índices previstos no for:

def buscaElemento(valores, procurado):


for indice in range(len(valores)):
if valores[indice]==procurado:
return indice # retorna ao encontrar o local
return -1 # retorna -1 se terminar sem encontrar

64
Busca Simples (utilizando while)

Neste algoritmo, o processo de busca é interrompido


quando o elemento procurado é encontrado.

valores: contém o grupo de elementos

7 19 4 10 18 6 8 1 3 12
0 1 2 3 4 5 6 7 8 9

procurado: 4
local: -1
indice: 0

65
Busca Simples (utilizando while)

Neste algoritmo, o processo de busca é interrompido


quando o elemento procurado é encontrado.

valores: contém o grupo de elementos

7 19 4 10 18 6 8 1 3 12
0 1 2 3 4 5 6 7 8 9

procurado: 4
local: -1
indice: 1

66
Busca Simples (utilizando while)

Neste algoritmo, o processo de busca é interrompido


quando o elemento procurado é encontrado.

valores: contém o grupo de elementos

7 19 4 10 18 6 8 1 3 12
0 1 2 3 4 5 6 7 8 9

procurado: 4
local: -1
indice: 2

67
Busca Simples (utilizando while)

Neste algoritmo, o processo de busca é interrompido


quando o elemento procurado é encontrado.

valores: contém o grupo de elementos

7 19 4 10 18 6 8 1 3 12
0 1 2 3 4 5 6 7 8 9

procurado: 4
local: 2
indice: 2

68
Busca Simples (utilizando while)

Neste algoritmo, o processo de busca é interrompido


quando o elemento procurado é encontrado.

valores: contém o grupo de elementos

7 19 4 10 18 6 8 1 3 12
0 1 2 3 4 5 6 7 8 9

procurado: 4
local: 2 Resultado
indice: 10

69
# Subprogramas
def preencher(valores):
...
def buscaElemento(valores, procurado): # primeira solução
local = -1
indice = 0
while indice<len(valores):
if valores[indice]!=procurado:
indice = indice + 1
else:
local = indice
indice = len(valores)
return local
def escreverResposta(valor, pos):
...
# Programa Principal de Busca
numeros = [0]*10
preencher(numeros)
dado = int(input(“Escolha valor a ser procurado: ”))
onde = buscaElemento(numeros, dado)
escreverResposta(dado, onde)
70
# Subprogramas
def preencher(valores):
...

def buscaElemento(valores, procurado): # segunda solução


indice = 0
while indice<len(valores):
if valores[indice]!=procurado:
indice = indice + 1
else:
return indice # retorna ao encontrar local
return -1 # retorna -1 se terminar sem encontrar

def escreverResposta(valor, pos):


...
# Programa Principal de Busca
numeros = [0]*10
preencher(numeros)
dado = int(input(“Escolha valor a ser procurado: ”))
onde = buscaElemento(numeros, dado)
escreverResposta(dado, onde)
71
Busca Simples (utilizando while)
• Em média, este algoritmo executa n/2 vezes o corpo da
repetição while.

• Em algumas vezes, o dado será encontrado logo na


primeira posição, em outras, na última posição.

• No pior caso, o algoritmo avalia n elementos. Portanto,


sua complexidade também é da ordem de n,
representado por O(n).

72
Busca com Sentinela (utilizando while)
• O algoritmo anterior poderia executar menos
comparações, se não houvesse a necessidade de evitar
ultrapassar o fim do vetor, caso o elemento procurado
não se encontre no conjunto.
• Propõe-se então alocar uma posição a mais no vetor e
inserir forçadamente o elemento procurado nesta
posição.
• Desta forma, necessariamente o elemento procurado
será encontrado. Se for encontrado na posição n+1,
significa que o elemento não pertence ao conjunto
original.
• Apesar de executar menos comparações, o algoritmo
avalia, no pior caso, n+1 elementos. Portanto, sua
complexidade também é da ordem de n, representado
por O(n).
73
# Subprogramas
def preencher(valores):
...
def buscaElemento(valores, procurado):
indice = 0
while valores[indice]!=procurado:
indice = indice + 1
if indice==len(valores)-1:
local = -1 # local do sentinela, informa que não achou
else:
local = indice
return local

def escreverResposta(valor, pos):


...
# Programa Principal de Busca
numeros = [0]*10
preencher(numeros)
dado = int(input(“Escolha valor a ser procurado: ”))
numeros.append(dado) # coloca o procurado no final: o sentinela
onde = buscaElemento(numeros, dado)
escreverResposta(dado, onde)
74
Busca Binária
• Os algoritmos apresentados até o momento foram
projetados sem levar em consideração se os elementos
do vetor encontram-se ordenados ou não.

• Caso os elementos se encontrem ordenados, algoritmos


mais eficientes podem ser implementados.

• No algoritmo chamado busca binária, cada passo divide o


espaço de busca em dois grupos até encontrar o
elemento sendo procurado.

75
Busca Binária

Os n elementos encontram-se ordenados no vetor.

valores

1 3 4 5 7 8 9 10 12 13 14 15 17 18 19 20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

inicio fim

procurado: 14

76
Busca Binária
procurado: 14

14 > 10
valores

1 3 4 5 7 8 9 10 12 13 14 15 17 18 19 20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

inicio meio fim

Número de elementos comparados: 1


77
Busca Binária
procurado: 14

valores

1 3 4 5 7 8 9 10 12 13 14 15 17 18 19 20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

meio fim

inicio

Número de elementos comparados: 1


78
Busca Binária
procurado: 14

14 < 15
valores

1 3 4 5 7 8 9 10 12 13 14 15 17 18 19 20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

inicio meio fim

Número de elementos comparados: 2


79
Busca Binária
procurado: 14

valores

1 3 4 5 7 8 9 10 12 13 14 15 17 18 19 20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

inicio fim

Número de elementos comparados: 2


80
Busca Binária
procurado: 14

14 > 13
valores

1 3 4 5 7 8 9 10 12 13 14 15 17 18 19 20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

inicio fim

meio

Número de elementos comparados: 3


81
Busca Binária
procurado: 14

valores

1 3 4 5 7 8 9 10 12 13 14 15 17 18 19 20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

inicio fim

Número de elementos comparados: 3


82
Busca Binária
procurado: 14

14 = 14
valores

1 3 4 5 7 8 9 10 12 13 14 15 17 18 19 20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

inicio fim

meio

Número de elementos comparados: 4


83
Busca Binária
procurado: 14

14 = 14
valores

1 3 4 5 7 8 9 10 12 13 14 15 17 18 19 20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

inicio fim

Resposta: local = 10 meio

Número de elementos comparados: 4


84
Busca Binária
def buscaElemento(valores, procurado):
inicio = 0
fim = len(valores)-1
meio = (inicio + fim) // 2
while (inicio<fim) and (procurado!=valores[meio]):
if procurado>valores[meio]:
inicio = meio + 1
else:
fim = meio -1
meio = (inicio + fim) // 2
if procurado!=valores[meio]:
local = -1
else:
local = meio
return local
85
Busca Binária
• Na busca binária, a cada passo, divide-se o espaço de
busca em dois até que o elemento procurado seja
encontrado.
• Considerando-se um vetor de 16 posições, no máximo 4
divisões podem ser feitas. E portanto, no máximo 4
elementos do vetor serão comparados com o elemento
procurado.
• Observe que se o vetor for de 32 posições (vetor
duplicado), no máximo 5 elementos do vetor serão
acessados (apenas um a mais). Ou ainda, se o vetor tiver
32.000 posições, apenas 15 avaliações serão
necessárias.
• Na prática, se o vetor tiver n posições, a busca binária
avaliará, no pior caso, log2(n) elementos. Portanto, sua
complexidade é da ordem de log(n), representado por
O(log(n)). 86
Busca do Menor e Maior Elementos
• Este problema é caracterizado pela procura do menor e
do maior elementos em um grupo de elementos do
mesmo tipo.

• Sem perda de generalidade, o problema será atacado


considerando-se que:

– Os elementos a serem percorridos são numéricos e estão


armazenados em um vetor;

– Estes elementos podem não ser distintos;

– O número de elementos define o tamanho do vetor.

87
Busca do Menor e Maior Elementos

valores: contém o grupo de elementos

4 19 4 10 18 6 19 1 3 18
0 1 2 3 4 5 6 7 8 9

posição dos elementos tamanho do vetor: 9+1 = 10


(indices) (número de elementos: n)

Resposta: Menor Elemento: 1


Maior Elemento: 19

88
Busca do Menor e Maior Elementos

# Subprogramas
def preencher(valores):
for ind in range(len(valores)):
valores[ind] = int(input(“Elemento[”+str(ind)+“]=”))
return
def buscarMenorMaiorElementos(valores):
...

def escrever(infos):
print(“O menor elemento =”, infos[0], “e o maior elemento =”, infos[1])
return None

# Programa Principal de Busca do Menor e do Maior Elementos


numeros = [0]*10
preencher(numeros)
extremos = buscarMenorMaiorElementos(numeros)
escrever(extremos)

89
Busca do Menor e Maior Elementos (continuação)
# Subprogramas
def preencher(valores):
...
def buscarMenorMaiorElementos(valores):
menor = valores[0]
maior = valores[0]
for indice in range(1,len(valores)):
if menor > valores[indice]:
menor = valores[indice]
elif maior < valores[indice]:
maior = valores[indice]
return [menor, maior]
def escrever(infos):
...
# Programa Principal de Busca do Menor e do Maior Elementos
numeros = [0]*10
preencher(numeros)
extremos = buscarMenorMaiorElementos(numeros)
escrever(extremos)
90
Busca do Menor e Maior Elementos
• O algoritmo apresentado encontra o menor e o maior
elementos em um vetor de n elementos.

• Dado que cada um dos n elementos é avaliado


necessariamente uma vez, a sua complexidade é da
ordem de n, representado por O(n).

91
PERGUNTAS?
BOM
DESCANSO
A TODOS!

Você também pode gostar