Algoritmo de busca binária - Binary Search

Neste post vou falar um pouco sobre o algoritmo de pesquisa binária, e como ele é muito poderoso em comparação com o algoritmo de pesquisa linear.

Resumidamente, o algoritmo de pesquisa binária é um algoritmo eficiente que divide a lista repetidamente pela metade até encontrar o item desejado. Seu funcionamento é determinado pela busca da metade, analisando se o valor da metade está muito longe ou perto do elemento que queremos encontrar.

Diferentemente da pesquisa simples ou pesquisa linear onde analisamos elementos por elementos em uma lista de array, o algoritmo de pesquisa binária é um algoritmo mais performático, levando menos tempo que o algoritmo de busca linear.

animação do funcionamento de busca binária

Como o algoritmo funciona na lógica?

Imagine o seguinte cenário. Voce deseja procurar em um dicionario a palavra "programação", como a palavra começa com a letra "p" você claramente iria iniciar a busca pela metade do dicionario, analisaria se a busca está muito proximo ou longe da letra que você está buscando e descartaria o restante, até chegar na letra "P".

Vejamos outro exemplo

Imagine que você tem uma lista de números de 1 a 100 e deseja encontrar o número 40 nessa lista. Você poderia percorrer essa lista analisando todos os elementos e verificar se o valor corresponde a 40. Mas esse tipo de algoritmo leva muito tempo para processar. Para resolver isso, vamos usar o algoritmo de pesquisa binária. Ele começará a pesquisa pela metade da lista, verificando se o valor desejado está muito próximo ou longe, eliminando assim os valores mais baixos e mais altos. Por exemplo, para encontrar o número 40, ele pode começar pela metade do array, que seria o número 50. Cinquenta está muito acima de 40. Portanto, ele pode eliminar os valores acima de cinquenta e analisar os valores abaixo de 50. Agora, imaginamos que o algoritmo chuta um número, digamos 30. Trinta é um valor abaixo de 40, então os valores abaixo de 30 não serão analisados. Logo, 40 está entre 30 e 50. Com isso, o algoritmo terá que buscar o número 40 entre 30 e 50, sempre dividindo a busca pela metade.

Funcionamento do Algoritmo

A função binarySearch vai procurar o item em um array ordenado. Se o valor que queiramos encontrar estiver no array sera retornado a sua posição, se o valor não estiver no array a função retornarar None.

  • criando uma lista de 100 elementos de 1-100
   listOfNumbers = list(range(1,101)) # [1,2,4,...,99,100]

Os elementos da lista devem está ordenado para que o algoritmo funcione. Se a lista tiver seus elementos desordenado, o algoritmo não vai funcionar.

  • pegar o valor mais baixo e o valor mais alto
   low  = 0 # 0 
   high = len(listOfNumbers) - 1 # 99
  • pegar o valor do meio e salvar na variavel o elemento do meio kick (chute)
   middle = (low + high) / 2
   kick   = array[middle] 
  • analisar se o valor do chute corresponde ao item que queiramos encontrar.
   if kick == item:
      return middle

nessa parte, se o valor do chute for igual o valor do item que queremos encontra,será retornado sua posição.

  • o chute for maior que o valor que queiramos encontrar, vamos atualizar o valor de high para uma nova posição.
   if kick > item:
      high = middle - 1 
  • O mesmo ocorre se o valor for menor que o valor que queremos encontrar.
   if kick < item:
      low = middle + 1 
  • se a função não encontrar o item na lista, a função ira retornar None.

Código completo

listOfNumbers = list(range(1,101)) # [1,2,4,...,99,100]

# algoritmo de pesquisa binária
def binarySearch(array, item):
   low  = 0
   high = len(array) - 1
   while low <= high:
      middle = int((high + low) / 2)
      kick   = array[middle]

      if kick == item:
         return middle
      elif kick > item:
         high = middle - 1
      else:
         low  = middle + 1
   return None
print(binarySearch(listOfNumbers, 40))  # retorno = 39

print(binarySearch(listOfNumbers, -40)) # retorno = None

Observe que, na busca do número 40, a função retorna sua posição 39. Se você analisar a lista, verá que o elemento da 39ª posição será exatamente o número 40.

Agora, observe que, na busca do elemento -40, a função retorna None, pois o elemento não existe na lista

algoritmo de pesquisa linear

listOfNumbers = list(range(1,101)) # [1,2,4,...,99,100]

def linearSearch(array, item):
   for index in range(0, len(array)):
      if array[index] == item:
         return index
   return None 
print(linearSearch(arrayOfNumbers, 40))  # retorno = 39

print(linearSearch(arrayOfNumbers, -40))) # retorno = None

Comparando o número de operações usando Big O notation.

Em resumo, o que é Big O notation, é basicamente um meio de medimos a eficiencia de um algoritmo. Nesse post não me aprofundarei sobre o assunto, mas quem sabe em um post futuro.

No exemplo do algoritmo de pesquisa linear onde temos uma lista de 100 elementos, dizemos que o número de processos/tentativas é O(n) onde n é a quantidade de elementos na lista. Essa notação nos diz que o algoritmo vai precisar de 100 tentativas para encontrar o elemento que queremos. Se for uma lista de 4 bilhões de elentos, precisaria de 4 bilhões de tentantivas. Isso nos demonstra o quão demorado é esse método de pesquisa linear.

No caso da pesquisa binária a quantidade de tentativas que o algoritmo vai precisar vai ser bem diferente da quantidade de tentativa da pesquisa linear. O número de tentativa vai ser O(log2 n) onde n é a quantidade de elementos da lista. No caso da lista de 100 elementos, o número de tentativa que o algoritmos vai precissa é de 7 para poder encontrar o elemento.

Uma observação: A notação big O notation não é usada para medir o tempo de execulsão do algoritmo, mas a quantidade de operações que o algoritmo vai precissar. Através desse método podemos saber o quão eficiente é uma algoritmo.

conclusão

Neste post, aprendemos sobre um algoritmo superpoderoso e como aplicá-lo na prática, além de compará-lo com o algoritmo de pesquisa linear. Este post teve uma referência do livro Entendendo Algoritmos: Um Guia Ilustrado Para Programadores e Outros Curiosos, no qual estou me baseando. Qualquer dúvida, sinta-se à vontade para comentar; responderei com o maior prazer. Agora é a sua vez de implementar esse algoritmo. Deixe aqui nos comentários a sua versão. Vou gostar muito de ver o seu código.

Para aqueles que têm interesse em algoritmos, fiz recentemente uma publicação sobre um site que permite visualizar estruturas de dados e algoritmos em animações.

O site que possibilita a visualização do funcionamento de vários algoritmos é: https://visualgo.net/en

Se você se interessar pela minha publicação, basta acessar o seguinte link: https://www.tabnews.com.br/SamuelMayer/visualizando-estruturas-de-dados-e-algoritmos-em-uma-animacao

Cara que legal, enquanto eu ia lendo isso eu tava pensando "isso ta tão bem explicado quanto o livro que to lendo" e ai no final tu referenciou o mesmo livro, Entendendo Algoritmos: Um Guia Ilustrado Para Programadores e Outros Curiosos já tenho 5 anos de programação então algumas coisas nesse livro eu já tinha muito bem fixado, mas tem muitas coisas que a gente esquece e é bom relembrar, além de que de fato é um livro bem "leve" de ler.

Eu fiquei muito feliz em ter lido teu comentario. Eu estava até com medo de ter explicado mau o algoritmo, pq o motivo do post é poder documentar meus estudos que estou fazendo. Teu comentario me motivou muito em continuar com o esse projeto.