Você está na página 1de 11

Algoritmo de busca binária recursiva

A primeira linguagem desenvolvida para a pesquisa de inteligência artificial foi


uma linguagem recursiva projetada para o processamento de lista. Por esta razão foi
chamada de LIS.
Os algoritmos de busca binária podem ser representados com conceitos de
recursividade (DEITEL; DEITEL, 2010). A Busca binária recursiva codificada
corretamente é muito simples e seus códigos com menos linhas do comando resultam
em menos ‘bugs’ (problemas). Desta forma, eles favorecem o melhor uso do I-cache
(memória de acesso rápido que ajuda no desempenho do processador) da sua CPU
resultando em um bom desempenho.

Análise do código

O exemplo ilustrado na figura, em script de código de programação Java,


representa a estrutura de uma busca binária recursiva de um elemento dentro do array
de números representado anteriormente na figura.

Figura – Algoritmo de busca binária recursiva


Nesse script o resultado será a posição do elemento encontrado dentro do array
números.
Você pode perceber que a estrutura do código é semelhante à relação lógica
do script de busca binária não recursiva. Nota-se, entretanto, que eles diferem devido
à ausência da aplicação do comando de repetição e à inserção do conceito de
recursividade por meio das linhas 13 e dezesseis quando o método busca binária é
executado com os devidos parâmetros. As declarações das estruturas simples (key)
e do array (números) são realizadas nas linhas 21 e 22. O método de busca é
chamado na linha 23.

Complexidade

Tanto na busca binária como na variação com recursividade, o tempo de


repetições é dado pelo termo O(log n). O valor de log n cresce muito devagar, pois
transforma multiplicações em adições.
Por exemplo, em potência dois (21, 22, 23, 2n), ele determina o valor do elemento
n acrescido de valor um, ou seja, quando um vetor de 8 posições tiver 4 repetições de
comparações (23+1), um vetor de 100 (26+1) posições terá 7 posições de
comparações. No caso do exemplo da figura, o array números possui nove elementos,
ou seja, o número próximo de 9 de base 2 é 8 (2 3). Isto resulta em (23 + 1) ou 4
repetições.

Algoritmo de busca com salto

Similar à busca binária, o algoritmo de pesquisa com salto é aplicado para


matrizes ordenadas (DEITEL; DEITEL, 2010). O conceito básico é fazer a verificação
de menos elementos que a pesquisa linear, ou seja, essa técnica de busca cria um
bloco e faz a localização do elemento buscado dentro dele. Caso o item não esteja no
bloco, o algoritmo o desloca por inteiro.
A extensão do bloco é baseada no tamanho da lista. Caso a sua extensão seja
n, o tamanho do bloco será √n, começando da esquerda para direita. Após a
localização do bloco certo, o algoritmo procura o item usando a técnica de pesquisa
linear. No quesito desempenho, a pesquisa de salto se localiza entre a linear e a
binária.

Figura– Tamanho de lista

Nesse caso, o tamanho da lista é 8, sendo assim, o tamanho do bloco será √ 8


= 3, arredondados. Isso permite que o bloco seja deslocado de 3 em 3 elementos da
esquerda para a direita e, dentro do bloco, faz-se uma busca linear.

Figura– Tamanho do bloco e busca

Complexidade

Pode-se dizer que a busca binária tem um desempenho melhor que a de salto,
entretanto esta última tem a vantagem de buscar a chave dentro de uma amostragem
muito menor (apenas um salto) enquanto a binária precisa buscar pela informação em
O(log n) elementos.
A pesquisa de salto tem um tempo de complexidade menor que a busca linear
(O (n)) e maior que a binária (O (log n)). O tamanho ideal de um bloco a ser saltado é
(√n). Isso faz com que a complexidade de tempo de busca de salto seja O (√n). No
pior caso, não salta n/k e se o último valor verificado é maior do que o desejado, faz-
se no máximo K1 comparações. Isso significa n/k + K – 1 comparações.

Análise do código

O exemplo ilustrado na figura 1.14 representa, em script de código de


programação Java, a estrutura de uma busca de salto dentro de um array de x
posições. Suponhamos uma busca por um item de valor 143 dentro de uma estrutura
de dados ordenados: S = {8, 19, 23, 50, 75,103, 121, 143, 201}.
Uma vez que a estrutura contém nove elementos e que m = √ n, sabemos que
o valor de salto é 3. Desta forma, tem-se que S[2] = 23 e que este valor é menor que
143, portanto o algoritmo salta para o próximo bloco. Nesse novo bloco temos S[5] =
103, valor inferior a 143, por esta razão, o algoritmo salta mais uma vez. Por fim, S[8]
= 201, sendo esse valor maior que 143 a chave pode existir nesse subconjunto. A
partir daí é feita uma busca sequencial.
Figura– Algoritmo de busca de salto

Nesse script, o resultado será a posição do elemento encontrado dentro do


array values.
Nota-se, as declarações do objeto de consulta (linha 30); da estrutura de dados
(linha 29) e a chamada do método de busca de salto com a passagem de dois
parâmetros (linha 31).
Entre as linhas 9 e 11, são declaradas as variáveis de salto, o movimento de
salto para trás e o tamanho da estrutura de dados. Em seguida, abre-se um comando
de repetição para analisar o salto caso este seja menor que o valor procurado (linhas
12 a 17).
Posteriormente, é feito o uso de um comando de repetição para análise do valor
prévio, caso ele seja menor que o procurado (linhas 18 a 22). Por fim, é analisado se
o valor procurado é igual a valor prévio do array.
Assim como todos os algoritmos de pesquisa, a busca de salto é conveniente
para alguns tipos de tarefas. Em uma lista muito extensa, o uso do algoritmo linear
pode ser dispendioso com o agravante de que, se o número desejado for o último da
lista, será necessário percorrê-la por completo para achá-lo. No caso de algoritmos
de busca binária, caso o elemento buscado esteja no início da lista, avançar até o
meio pode não ser o ideal, pois será necessário dar um enorme salto para trás. Nestas
ocasiões, a busca de salto tem a vantagem de voltar atrás somente uma vez (em caso
de implementação básica).
O algoritmo de busca mais adequado para o seu sistema dependerá de uma
série de fatores como a quantidade de dados e a possibilidade da estrutura de arquivo
sofrer com inserções ou exclusões ao longo do tempo. Por esta razão, o estudo
preliminar das características da estrutura de arquivo a ser usado é essencial, pois é
somente desta maneira que você será capaz de determinar qual algoritmo será o mais
eficiente para as suas buscas.

ALGORITMOS DE ORDENAÇÃO

Imagine que você precisa procurar por um nome em uma agenda de telefones
sem qualquer tipo de ordenação. Esta certamente seria uma tarefa complexa e
demorada. Esse mesmo problema ocorre com as informações em computação. Por
esta razão, sistemas que estruturam dados mais complexos com arrays e listas
precisam armazenar seu conteúdo em ordem crescente ou decrescente para facilitar
as pesquisas de informações dentro dessas estruturas.
Tente imaginar uma tabela de um banco de dados de uma instituição financeira
renomada, quantos milhares de registros com diversas informações importantes estão
armazenados ali? Suponha que essas tabelas não fossem ordenadas, quanto tempo
um usuário levaria para fazer uma simples consulta de dados?
Os algoritmos de ordenação são métodos que organizam estruturas complexas
e seus conjuntos de dados em uma ordem crescente ou decrescente, desta forma,
melhorando o desempenho de consultas (NASCIMENTO; MOZZAQUATRO;
ANTONIAZZI, 2016).
Os métodos de ordenação podem ser:
• Internos: Onde os registros cabem na memória principal (in-place) e
podem ser acessados imediatamente;
• Externos: Onde os registros não cabem na memória principal (not in-
place) e só podem ser consultados por meio de grandes blocos ou por acesso
sequencial. (NASCIMENTO; MOZZAQUATRO; ANTONIAZZI, 2016).

Existem dois tipos de métodos ordenação:


1) Simples: Usado para estruturas menores, mais simples e de fácil
entendimento.
2) Complexo: Usado em estrutura de dados mais sofisticadas.

Em termos de complexidade, as informações fundamentais são:


• O número de comparações entre chaves C(n);
• O número de movimentações M(n) dos registros nas estruturas.
Sendo que n representa o número de registros.

Ordenação por bolha (bubble sort)


O algoritmo de ordenação por bolha opera de maneira simples. Ele percorre
toda a estrutura de dados (array) ou objeto a ser ordenado e faz uma comparação
entre os elementos adjacentes ou suas chaves verificando se estão na ordem correta.
Essa comparação pode ser realizada em ordem crescente ou decrescente
(NASCIMENTO; MOZZAQUATRO; ANTONIAZZI, 2016).
A cada iteração, o método de ordenação por bolha fica mais eficiente. Essas
iterações ocorrem por meio de um conjunto de comparações n-j, onde n é o número
de elementos e j é a contagem da iteração atual.
A respeito de complexidade, esse método tem seu pior caso em O(n²) e seu
melhor caso quando o algoritmo executa n operações relevantes, sendo n o número
de ordenações.
O método de ordenação por bolha é de complexidade quadrática, portanto não
é recomendado para arrays com um volume extenso de dados ou para situações que
exijam uma ordenação rápida.
Análise do algoritmo
A figura mostra um script em linguagem Java implementado com o algoritmo
bolha.

Figura– Implementação do algoritmo de ordenação por bolha

O script apresentado na figura executa a ordenação de um array de 10


elementos inteiros desordenados que são apresentados na linha 28. Na linha 29 é
definido o método de ordenação arrayRetornoOrdenado que é capaz de retornar um
array ordenado para alimentar a variável do tipo array. Na linha 30, o objeto é
mostrado no console ordenado.
É importante lembrar que existe a necessidade de converter o array para string
senão haverá, somente, a visualização de um número do objeto criado em memória,
algo parecido com ‘[I@1e643faf’. Isso pode ser feito por meio do método
Arrays.toString().
O método bubbleSort recebe como parâmetro um array desordenado (linha
sete). Na linha nove é criada uma variável de troca para a verificação do final das
comparações. Um comando que garante a repetição da operação até que todos os
elementos estejam ordenados é iniciado na linha 13.
Observa-se que, entre as linhas 17 e 20, os valores e chaves são comparados
e, caso necessário, alterados de posição de forma a criar uma ordenação crescente
dos elementos. Na linha 24, o array é retornado em ordem.
Esse algoritmo tem a vantagem de ser fácil de compreensão e conhecido. Além
disso, a troca dos elementos de lugar é feita sem a necessidade de armazenamento
temporário, fazendo com que seu uso de memória seja mínimo. Apresenta, entretanto,
como principal desvantagem a baixa eficiência em organizar listas de dados muito
extensas.

Complexidade
Lembre-se que a complexidade desse tipo de algoritmo é do tipo n² etapas de
processamento para cada número n de elementos que serão ordenados. Desta forma,
se um array possui cinco elementos, o número de passos do processo será 25.
Quando lidamos com listas muito longas, a quantidade de etapas faz com que
esse método se torne impraticável no mundo real. Por esta razão, esse algoritmo só
é usado no mundo acadêmico para gerar conhecimento de lógica de ordenação.

Ordenação por inserção


Esse algoritmo de ordenação também é simples, pois examina um objeto em
uma estrutura de dados (array ou lista) e faz a comparação das suas chaves
construindo uma matriz final com a inserção de um elemento por vez (DEITEL;
DEITEL, 2010). Esse algoritmo também possui uma relação quadrática, portanto só
deve ser usado em estruturas pequenas de dados. Apesar disso, dentre os algoritmos
de ordenação simples, esse é o que apresenta maior eficiência.
É possível compreender o algoritmo de inserção por meio de uma comparação
com um jogo de cartas. Geralmente, quando um indivíduo está jogando, ele segura
um conjunto ordenado de cartas em sua mão. Ao retirar uma nova do monte, vai
compará-la com as outras que já está segurando e a posicionará em sua mão de uma
maneira que conserve a organização preestabelecida.
Essa mesma ideia pode ser traduzida para a lógica das máquinas por meio do
algoritmo de ordenação por inserção. O algoritmo percorre todo array, começando
pelo índice um, fazendo as comparações necessárias até posicionar o elemento no
local correto, ou seja, para cada iteração x do algoritmo, o elemento A[x] troca de
posição com cada elemento maior que ele a sua esquerda.
A grande vantagem do algoritmo de ordenação por inserção é que, sempre que
um novo elemento for adicionado ao array, ele já estará ordenado ou quase ordenado,
devido à natureza linear do processo. A principal desvantagem desse sistema é o
custo da movimentação dos elementos dentro do vetor.

Figura– Algoritmo por inserção

Complexidade
No melhor dos casos, a complexidade desse algoritmo é dada pelo termo O(n)
quando o array está completamente ordenado. Na pior das situações, tem-se o termo
O(n²), ou seja, um array com 20 elementos cria uma complexidade de 20², portanto
400 iterações.

Análise do algoritmo
A figura mostra um script em linguagem Java implementado com o algoritmo
de inserção.
Figura– Implementação do algoritmo e ordenação por inserção

O script executa a ordenação de um array de dez elementos inteiros


desordenados que foram declarados na linha 26. Na linha 27 é determinado o método
de retorno do array ordenado. Na linha 28, é feita a impressão do array em forma de
string, usando o método Arrays.toString().
O método de ordenação tem início na linha nove com a declaração de duas
variáveis:
1) Variável J: recebe os valores das alterações da chave.
2) Variável value: recebe o elemento temporariamente para ser trocado de
posição.

Logo em seguida, ocorre o cascateamento de dois comandos de repetição. O


primeiro determina a leitura de todo o array, elemento por elemento. O segundo faz a
troca quando a chave for maior que zero e menor que a variável value. Por fim, o array
ordenado ao final do loop é retornado a linha 27 e impresso na linha 28.
Uma vantagem do algoritmo de ordenação por inserção é que nenhum
movimento e realizado sem necessidade, pois a ordenação só ocorre quando algo
está fora do lugar.

Você também pode gostar