Você está na página 1de 11

ESTRUTURAS COMPLEXAS

No processo de busca em matrizes, é comum que sejam utilizados algoritmos


de seleção, inserção e bolhas, porém eles não apresentam uma boa eficiência quando
precisam percorrer uma alta quantidade de elementos. Felizmente, existem outras
estruturas complexas que podem ser usadas para busca e organização de dados
como a tabela por difusão e a árvore binária.
Uma árvore binária pode representar qualquer tipo de algoritmo de comparação
e responder questionamentos a respeito de quantas relações são necessárias para
uma ordenação de n elementos e qual a melhor estimativa para matrizes aleatórias.
Já as tabelas por difusão são usadas na representação de estruturas de dados como
vetores associativos e conjuntos.

Árvore binária

Uma árvore binária é uma estrutura de dados que tem por característica possuir
um elemento raiz e dois subelementos, a subárvore da esquerda e a da direita. Ela
pode ser representada, também, por arcos de S(sim) e N(não) em cada uma dos nós
raízes (DEITEL; DEITEL, 2010).
Quando um nó não terminal de árvore possui questionamento ou condições de
resposta direta (Sim ou Não) e é possível fazer ordenação das folhas por meio da
aplicação de um algoritmo, podemos dizer que se trata de uma árvore de decisão
(PUGA; RISSETI, 2009).
As árvores de decisão são muito aplicadas no cotidiano do usuário. Aplicativos
como ChatBot fazem uso da tabela de decisão para determinar qual orientação deve
ser dada ao usuário que apresenta determinado problema/solicitação.
Essa estrutura também é aplicada ao aprendizado de máquinas para a
classificação das informações; para visualizar regras de negócios que especificam
grupos de indivíduos; ou no processo de classificação das informações (taxonomia).
Uma característica das árvores binárias é o uso da técnica de recursividade
(DEITEL; DEITEL, 2010). Por causa desse aspecto, elas são muito usadas na
computação, principalmente para fazer buscas. É também por causa da recursividade
que o algoritmo de busca por inserção pode ser implementado na estrutura de uma
árvore binária.
A profundidade de uma árvore é determinada pela distância de um de seus nós
até a raiz. Os nós que têm a mesma profundidade pertencem ao mesmo nível. A
distância do menor nível de nó até a raiz resulta na altura da árvore.
Uma árvore que não contem nó ou que apresenta dois nós em todos os seus
níveis é ‘estritamente binária’, pois possui um balanceamento em toda a sua estrutura.
Quando todos os nós de uma árvore têm paridade de sub nós, dizemos que essa
árvore é completa.
É possível fazer a varredura de uma árvore binária por meio de diversas
técnicas de busca (PUGA; VISSETI, 2009). Dentre elas, podemos destacar três:
1) Esquerda-raiz-direta (ERD) ou em-ordem: A varredura é feita pela
esquerda para, em seguida, retornar para a raiz e, por fim, recomeçar pelo lado
direito.
2) Raiz-esquerda-direita (RED) ou pré-ordem: A varredura tem início na
raiz, passa para a esquerda e recomeça pela direita.
3) Esquerda-direita-raiz (EDR) ou pós-ordem: A varredura começa no
lado esquerdo, passa para a direita e retorna à raiz.
Árvores binárias balanceadas possuem, aproximadamente, a mesma altura,
considerando a igualdade dos nós do lado esquerdo e direito. Pode-se considerar que
uma árvore binária com balanceamento com n nós, possui uma altura próxima de log
n.
Classificação
As árvores binárias podem ser classificadas em:
• Binária: quando cada nó possui nenhum, um ou dois nós filhos
e cada filho tem somente um pai;
• Binária cheia: quando cada nó possui dois filhos;
• Binária perfeita: quando as folhas estão todas no mesmo nível;
• Binária completa: quando os nós se encontram o mais à
esquerda possível e seus níveis são todos preenchidos, com exceção
do último;
• Binária equilibrada: quando os subníveis da árvore possui no
máximo um nível de profundidade de diferença;
• Degenerada: todos os nós têm somente um nó filho;
• Binária ordenada: quando cada sub árvore (esquerda/direita) de
cada nó, contém somente valores menores/maiores do que nó
analisado.

Figura – Modelo simples de árvore binária

Detalhes da figura:
• Arco direcionado: ligação entre um nó pai e um nó filho;
• Nó raiz: é o primeiro elemento da árvore, portanto não possuí nó
pai;
• Folha: Nó que não tem filhos;
• Profundidade do nó: distancia da raiz até o nó em questão;
• Nível: conjunto de nós a uma profundidade. O menor nível é o nó
raiz;
• Tamanho do nó: número de nós descendentes + 1(o próprio nó);
• Nós irmãos (siblings): quando os nós tem o mesmo pai;
• Nó pai: quando possui nós descendentes;
• Nó filho: nó descendente de um nó pai;
• Grau de entrada (in-degree): número de arcos que chegam em
um nó;
• Grau de saída (out-degree): número de arcos que partem de um
nó.
O entendimento de estrutura de dados tipo árvore é tão fundamental que
diversas áreas computação aplicam esse conceito de hierarquia em suas definições.
Esse é o caso de Document Objetc Model (DOM) (DEITEL; DEITEL, 2008).
O World Wide Web consortium (w3c) é um consórcio internacional que
determina todos os padrões de desenvolvimento na Web e especifica que, sempre
que uma página for carregada, todos os elementos que estão na linguagem de
marcação HTML serão reconhecidos pelo navegador de maneira hierárquica.
Semelhante a uma estrutura de árvore, os elementos da página web são
posicionados hierarquicamente em níveis e podem ser facilmente encontrados por
linguagens como JavaScript por meio de Document Object Model (DOM) (LEMAY,
2002).
Ao se tornar capaz de manipular e controlar esses elementos, passa a ser
possível desenvolver e páginas interativas e abertas para feedback que tornam a
navegação intuitiva e prazerosa para o usuário. Veja, a seguir, a árvore de elementos
de uma página HTML.
HTML

HEAD BODY

DIV DIV

IMG LI A LI

Figura – Árvore DOM

É importante compreender que conceito de árvore pode ser implementado em


outras aplicações como ChatBots de perguntas frequentes e ajuda de orientação.

Tabelas de símbolos
Tipos abstratos de dados (TAD) são estruturas que comportam um conjunto de
elementos. Alguns dos tipos mais comuns de TADs incluem vetores, listas e árvores.
Um tipo abstrato possui um valor e uma operação (V e O). Essas estruturas
fazem uso de interfaces, pois, por meio delas, pode-se realizar diversas operações
específicas.
Em computação, uma interface é um objeto que contém a assinatura de um
método, mas não a sua implementação. Em outras palavras, diz o que cada operação
deve fazer, mas não como ela deverá ser implementada (LIMA, 2016).
Figura – Interface

Essas operações a serem implementadas na interface são chamadas de


applications programming interface (API) (LIMA, 2016). Esse recurso é altamente
utilizado em computação e é comumente chamado de driver quando usado para
hardware ou plugin quando usado para software.
Estrutura de dados como a pilha, fila e tabela de símbolos representam um tipo
de dado genérico (PUGA; VISSETI, 2009). As operações de remoção e inserção de
dados nessas estruturas são chamadas de APIs. O usuário cliente usa as operações
da API para não ter acesso aos detalhes da operação (implementação) da interface.
Desta forma, na linguagem da computação, diz-se que a interface funciona como um
contrato entre o usuário e a implementação na ordem usuário-interface-
implementação.
Uma tabela de símbolos é uma estrutura que possui duas colunas. Uma delas
faz referência à chave do item (key) e a outra ao valor inserido na tabela (value). Cada
linha é composta de um par (key e value) e é chamada de item. Desta forma, cada
item está associado a uma chave e a um valor. Uma agenda telefônica, por exemplo,
pode ser considerada uma tabela de símbolos.

Tabela– Agenda telefônica

978685645 João Carlos


987654567 Maria do Rosário
34566543 Carlos Afonso
76546334 Eduardo Monteiro
Podemos citar como outro tipo de tabela de símbolos a estrutura de um arquivo
de servidor de domínios (DNS) (tabela 2). Esses servidores contém uma tabela que
mapeia os números de protocolo de internet (IP) para nome do domínio.

Tabela– Tabela de DNS


200.160.198.33 www.uol.com.br
23.219.192.121 www.estadao.com.br
191.19.252.239 www.nucleopedagogico.br
79.170.40.4 www.oxford.uk

As tabelas de símbolos utilizam operações por meio de interfaces para inserir


(put) e para resgatar (get) informações dentro dela. Sempre com referência aos itens
e seus pares (chave, valor).

Funções hashing

Toda tabela possui paralelamente um conjunto de chaves que mapeia a


posição dos seus elementos. Um algoritmo que mapeia dados de diversos tamanhos
para dados de tamanho determinado é chamada de função de espalhamento (hash).

Figura – Mapeamento de dados


No século XXI, o mundo virtual tem se tornado cada vez mais presente na vida
das pessoas. A todo o momento, novas aplicações web ou móveis são desenvolvidas
para facilitar o dia a dia do usuário. Estamos presenciando o surgimento de novas
maneiras de fazer coisas cotidianas como, por exemplo, pedir comida por aplicativo
ao invés de por telefone.
Na internet do século XXI, variedade, volume e velocidade das informações são
significativos e seu crescimento é exponencial. A isso, soma-se a necessidade que os
usuários têm de compartilhar e interagir, principalmente nas redes sociais.
Para satisfazer essa urgência, os dados devem ser armazenados em estruturas
que guardem as informações (dados) de forma que, em um momento futuro, elas
possam ser acessadas sem lidar com qualquer tipo de alteração em seu conteúdo.
Essas estruturas são chamadas de banco de dados (LEAL, 2015).
A função hash, quando aplicada à banco de dados, acelera a consulta por meio
da identificação de registros (linhas) duplicados nas tabelas. Na criptografia, por
exemplo, as funções hash transformam strings enormes em dados de tamanho fixo.
Existem diferentes tipos de função hash que podem ser de diferentes tamanhos
(bytes). Dentre elas, podemos destacar:
• Message-Digest Algorithm 5: tem o tamanho de 128 bits e é
usada em criptografia unidirecional especificada na Request For
Command (RFC) 1321. Ele pode ser aplicado para a verificação da
integridade de dados;
• Secure Hash Algorithm-2: Pode ter diversos tamanhos como 224,
256, 384 ou 512 (bits) e é usado na criptografia para atestar a
integridade das informações. Um exemplo clássico de seu uso é a
verificação da chave criptográfica de um arquivo pós download
(TERADA, 2008).
Uma assinatura digital pode ser transformada por uma função hash para, em
seguida, o valor resultante ser enviado ao receptor para garantir a integridade da
assinatura. Posteriormente, o receptor faz uso da mesma função para gerar um novo
valor e comparar com o recebido. Ambos precisam ser iguais. Caso contrário, ocorreu
alguma modificação no arquivo original. Pode-se dizer que uma função hash funciona
de maneira semelhante às funções de criptografia.
O ácido desoxirribonucleico (DNA) armazena informações sobre a genética dos
seres vivos, portanto uma função hash pode ser usada para localizar sequências
similares dentro dele.
A seguir, veremos os tipos de função hash:

Divisão
A função de divisão é a maneira mais fácil de se usar o hash. Nela, usa-se o
módulo de divisão (h(k) = k mod TSize), sendo TSize o tamanho da tabela (h) e k o
número de itens. Essa técnica é escolhida sempre que o conjunto de chaves é
desconhecido.

Enlaçamento
Nesse método, a chave é dividida em diversas partes que são combinadas ou
enlaçadas juntas e com a frequência transformada para criar o endereço alvo.
Podemos usar como exemplo um código de pessoa física (cpf) que possui quatro
partes. Os itens poderiam ser somados e o resultado dividido pelo tamanho do item
(11) e adicionado a uma tabela como referência. Existem dois tipos de enlaçamento.
O deslocado e o limite.

Extração
Nesse método, somente uma parte da chave é usada para calcular o endereço.
Por exemplo, em redes de computadores, o endereço físico de uma placa de rede é
composto por 12 dígitos em formato hexa. Considere uma tabela com propriedade e
número de série. Os seis primeiros determinam o fabricante e os seis últimos o número
de série da placa de rede.

Tabela hash
Também conhecida como tabela de dispersão, trata-se de um vetor (tabela) de
tamanho fixo. Nela, os elementos são alocados em posições determinadas por uma
função hash. A forma mais simples de sua representação é um vetor de registro. Ele
contém uma chave para cada linha dessa tabela, ou seja, a cada elemento do vetor.
Figura– Tabela com vetor simples

Por padrão, a chave é do tipo inteiro, entretanto ela pode ser associada a uma
de valores alfanuméricos. Esse segundo tipo de dados é chamado de chave
associativa. Ela deve ser um identificador para cada registro que conterá mais
informações sobre o item procurado. A ideia é fazer a varredura do vetor em busca do
item procurado de forma fácil e veloz. A tabela também pode ser usada com listas ao
invés de valores.

Figura – Tabela com vetor listas

Tabelas hash normalmente são implementadas em vetores associados ou


caches. Elas são muito usadas para indexar grandes bases de dados. Tem
complexidade em termo de O(1), desconsiderando as colisões. Seu desempenho
melhora conforme o vetor aumenta.
As tabelas hash fazem uso de funções de espalhamento para posicionar os
objetos dentro do vetor. Uma função hash precisa ser fácil de calcular e a distribuição
dos itens no vetor deve ser uniforme.
O tempo de inserção e remoção de elementos em um vetor é assegurado pela
tabela de dispersão em valores medianos constantes. Seu comportamento possui
característica como o emprego de funções de dispersão e da técnica de resolução ou
de colisões.
O tamanho da tabela vai impactar diretamente na qualidade da função de
dispersão, pois pode facilitar a ocorrência de colisões entre os elementos. Uma colisão
acontece quando a posição de dois elementos é indicada pela mesma chave.
O ideal de uma função hash é que os números resultantes sejam valores
únicos. Desta forma, as colisões seriam evitadas. Esse processo é importante porque
as colisões diminuem o desempenho do sistema.
A combinação da tabela hash com outras estruturas de dados como listas
encandeadas é, muitas vezes, necessária para evitar as colisões. Técnicas de
resolução de colisões podem ser implementadas internamente na própria tabela.

Técnicas de colisões

Sempre que dois dados distintos entram em uma função hash, espera-se que
as saídas sejam diferentes. Quando isso não ocorre, ou seja, as entradas são
diferentes, mas as saídas são iguais, tem-se uma colisão.
Em algumas situações, essas colisões são totalmente intoleráveis. Como
exemplo prático, podemos mencionar situações que envolvem segurança da
informação e criptografia.
Um outro exemplo de colisão pode estar relacionado à tabela American
Standard Code for Information Interchange (ASCII). Imagine uma função de dispersão
aplicada a nomes que retorna o valor da tabela ASCII da primeira letra de cada nome.
Todos os nomes que começam com a mesma letra serão referenciados com a mesma
chave da tabela. Desta forma, colisões ocorrem e existem técnicas para a sua
resolução:

Encadeamento Separado

Uma lista encandeada é usada para armazenar os conflitos de colisão. Essa


técnica é a mais simples. O processo de inserir um novo elemento requer, também,
uma inserção na lista encandeada, porém a remoção exige uma atualização dentro
da lista.

Você também pode gostar