MAC2166 - Introdução à Computação

01/06/2017 - Aula 18 - Algoritmos Eficientes para a Busca de Elementos em Listas

Problema 18.1

a) Escreva uma função que, dados como parâmetro uma lista L e um número real x, devolve a posição em que x ocorre na lista ou None caso x não esteja na lista.

b) Usando a função do interior, escreva um programa que lê uma sequência de números reais e depois imprime a sequência eliminando os elementos repetidos.

Solução:

In [2]:
def acha(lista, x):
    ''' (list, float) -> int
        Retorna a posição em que x ocorre na lista, ou None caso contrário.
    '''
    i = 0
    while i < len(lista) and lista[i] != x:
        i += 1
        
    if i == len(lista):   # chegou ao final da lista sem encontrar x
        return None
    else:
        return i
    
def main():
    n = int(input("Digite o número de elementos da sequência de números reais: "))
    
    seq = []
    for i in range(1,n+1):
        numero = float(input("Digite o %dº número da sequência: " %i))
        
        if acha(seq, numero) == None:    # Se o número lido ainda não está na sequência,
            seq.append(numero)          # inclui o número na sequência
    
    print("A sequência sem repetições fica: ", seq)

##################################
main()
    
Digite o número de elementos da sequência de números reais: 6
Digite o 1º número da sequência: 8.75
Digite o 2º número da sequência: 2.5
Digite o 3º número da sequência: 1.2
Digite o 4º número da sequência: 2.5
Digite o 5º número da sequência: 8.75
Digite o 6º número da sequência: 3.14
A sequência sem repetições fica:  [8.75, 2.5, 1.2, 3.14]

Busca Sequencial

O algoritmo usado para implementar a função acha(lista,x) acima é chamado de Busca Sequencial. Observe que, nesse algoritmo, toda a sequência precisa ser varrida quando o x procurado não está em lista. Mas é possível deixar esse algoritmo mais eficiente quando os elementos da lista na qual se quer buscar um valor estão ordenados. O código abaixo mostra uma nova versão da função -- a acha2(lista_ordenada,x); nessa nova versão, a função pode detectar que um elemento não está na lista ordenada mesmo antes de chegar ao seu final:

In [7]:
def acha2(lista_ordenada, x):
    ''' (list, float) -> int
        A função recebe uma lista ordenada e um valor x e retorna a posição em que x 
        ocorre na lista ordenada, ou None caso contrário.
    '''
    i = 0
    while i < len(lista_ordenada) and lista_ordenada[i] < x:  
        # avança i enquanto ainda não foi encontrado em i um valor igual ou maior que x
        i += 1
        
    if i < len(lista_ordenada) and lista_ordenada[i] == x:   # encontrou x na posição i da lista
        return i
    else:
        return None
    
##################################
# Testando a função 
seq_ordenada = [1.2, 2.5, 3.14, 8.75]
x_buscado = 3.14
print("A posição de", x_buscado, "na sequência", seq_ordenada, "é:", acha2(seq_ordenada,x_buscado))
    
A posição de 3.14 na sequência [1.2, 2.5, 3.14, 8.75] é: 2

Busca Binária

Quando a sequência está ordenada, a busca pode ser ainda mais eficiente do que a implementada na função acha2 acima. Para isso, usamos uma estratégia parecida com a forma como buscamos palavras em um dicionário ou nomes em um catálogo de telefones ou endereços em papel. Nesse caso, ao invés de testar um elemento de cada vez sequencialmente, podemos aplicar o seguinte algoritmo:

  1. Considere o elemento M, no meio da lista.
  2. Caso x for igual a M, então a busca termina pois encontramos o valor procurado.
  3. Caso M for maior que x, então x deve estar na primeira metade da sequência. A busca deve continuar apenas na primeira metade. Mas se o comprimento dessa metade for nulo, a busca deve termina e o valor não foi encontrado.
  4. Caso M for menor que x, então x deve estar na segunda metade da sequência. A busca deve continuar apenas na segunda metade. Mas se o comprimento dessa metade for nulo, então a busca termina e o valor não foi encontrado.

Observe que nesse algoritmo, a cada vez que executamos os passos 3 ou 4, metade da sequência é eliminada da busca. É por isso que o algoritmo é chamado de Busca Binária.

Para "enxergar" melhor quão mais eficiente a Busca Binária é em relação à Busca Sequencial, considere o seguinte exemplo: procuramos um dado x em uma sequência ordenada com 512 elementos, mas x não está nesta sequência. Antes de indicar que x não está na sequência, o algoritmo de Busca Sequencial precisa testar todos os 512 elementos da sequência. No caso da Busca Binária, o primeiro teste elimina 256 elementos, o segundo 128, o terceiro 64, e depois 32, 16, 8, 4, 2, até que a lista contenha apenas 1 elemento. Dessa forma, ao invés de 512, apenas 9 elementos (ou log(len(sequencia))) precisam ser testados.

Problema 18.2

Escreva uma função que implemente o algoritmo da Busca Binária. A sua função deve receber como parâmetro uma lista ordenada e um número real x e devolver a posição em que x ocorre na lista ou None caso x não esteja na lista.

Solução 1 (sem recursão)

In [13]:
def busca_binaria(seq, x):
    ''' (list, float) -> bool
        retorna a posição em que x ocorre na lista ordenada,
        ou None caso contrário, usando o algoritmo de busca binária.
    '''
    n = len(seq)
    inicio = 0
    fim = n-1
    while inicio <= fim:
        meio = (inicio + fim) // 2
        
        if x == seq[meio]:        # achou x 
          return meio
        elif x < seq[meio]:       # x pode estar na primeira metade da sequência
            fim = meio - 1
        else:
            inicio = meio + 1   # x pode estar na segunda metade da sequência

    return None   


##########################################
# Alguns testes da função busca_binaria
seq = [4, 10, 80, 90, 91, 99, 100, 101]
testes = [80, 50]

print("A sequência usada para testes é: ", seq)
for t in testes:
    pos = busca_binaria(seq, t)
    if pos is None:
        print("Nao achei ", t)
    else:
        print("Achei %d na posição %d" %(t, pos))
    
A sequência usada para testes é:  [4, 10, 80, 90, 91, 99, 100, 101]
Achei 80 na posição 2
Nao achei  50

Solução 2 (com recursão)

In [17]:
def busca_binaria_recursiva(seq, x, inicio, fim):
    ''' (list, float, inicio, fim) -> bool
        retorna a posição em que x ocorre na sublista entre as posições inicio e fim 
        (ou seja, em {seq[inicio], seq[inicio+1],...,seq[fim-1],seq[fim]} )
        ou None caso contrário, usando o algoritmo de busca binária.
    '''
    meio = (inicio + fim) // 2
    
    if inicio > fim:         # caso base (subsequencia vazia)
        return None
    
    elif x == seq[meio]:     # achou x 
      return meio
    
    elif x < seq[meio]:      # x pode estar na primeira metade da sequência    
        return busca_binaria_recursiva(seq, x, inicio, meio - 1)
    
    else:                    # x pode estar na segunda metade da sequência
        return busca_binaria_recursiva(seq, x, meio + 1, fim)


def busca_binaria2(seq,x):
    ''' (list, float) -> bool
        retorna a posição em que x ocorre na lista ordenada,
        ou None caso contrário, usando o algoritmo de busca binária.
    '''
    n = len(seq)
    inicio = 0
    fim = n - 1
    return busca_binaria_recursiva(seq, x, inicio, fim)
    
    

##########################################
# Alguns testes da função busca_binaria
seq = [4, 10, 80, 90, 91, 99, 100, 101]
testes = [80, 50]

print("A sequência usada para testes é: ", seq)
for t in testes:
    pos = busca_binaria2(seq, t)
    if pos is None:
        print("Nao achei ", t)
    else:
        print("Achei %d na posição %d" %(t, pos))
    
A sequência usada para testes é:  [4, 10, 80, 90, 91, 99, 100, 101]
Achei 80 na posição 2
Nao achei  50