Logo Passei Direto
Buscar
Material
páginas com resultados encontrados.
páginas com resultados encontrados.

Escolha uma das opções e acesse esse e outros materiais sem bloqueio. 🤩

Cadastre-se ou realize login

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

Escolha uma das opções e acesse esse e outros materiais sem bloqueio. 🤩

Cadastre-se ou realize login

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

Escolha uma das opções e acesse esse e outros materiais sem bloqueio. 🤩

Cadastre-se ou realize login

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

Escolha uma das opções e acesse esse e outros materiais sem bloqueio. 🤩

Cadastre-se ou realize login

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

Escolha uma das opções e acesse esse e outros materiais sem bloqueio. 🤩

Cadastre-se ou realize login

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

Escolha uma das opções e acesse esse e outros materiais sem bloqueio. 🤩

Cadastre-se ou realize login

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

Prévia do material em texto

2ºAula
TABELAS HASH
Objetivos de aprendizagem
Ao término desta aula, vocês serão capazes de: 
• entender o que são tabelas Hash;
• compreender o que é a função de dispersão;
• saber como tratar colisões.
Olá,
Nesta aula, vamos abordar mais uma estrutura fundamental 
para a Computação: as Tabelas Hash. Essas tabelas fornecem 
uma forma de armazenamento que permite uma busca rápida 
dos dados, desde que a pessoa conheça a chave. 
Leiam atentamente esta aula e façam os exercícios. Se 
enfrentarem alguma dúvida, estaremos a sua disposição na área 
do aluno.
Bons estudos!
15
Estrutura de Dados II 14
Seções de estudo
1. Entendendo o problema e o que são as Tabelas 
Hash
2. Resolvendo Colisões
1 - Entendendo o problema e o que 
são as Tabelas Hash
Na disciplina de Estruturas de Dados I, vocês viram 
algoritmos para resolver dois problemas, que são considerados 
elementares na Computação: a Ordenação e Busca.
Em relação a busca, vocês viram que podemos resolver 
de duas formas. Quando temos um conjunto linear de dados 
não-ordenados, usualmente fazemos a busca de maneira 
sequencial, percorrendo elemento por elemento. Já quando 
temos um conjunto de elementos ordenados, podemos usar 
a busca binária para conseguirmos uma resposta mais rápida. 
Porém, em certas situações, nem sempre a busca binária 
pode ser a melhor solução para buscar um dado. Sendo assim, 
vamos falar sobre, um novo conceito, o dicionário de dados. 
Podemos denominá-lo como um conjunto dinâmico de 
dados, onde em cada elemento há um subconjunto de dados 
- ou seja, um registro - no qual um desses dados satélites é 
uma chave, para que o acesso aos dados seja realizado.
Em um dicionário de dados, podemos definir as 
seguintes operações:
• Busca: dado um dicionário de dados S e um 
item K, consiste em retornar um ponteiro para o 
elemento X do conjunto, onde X.chave = K.
• Insere: dado um dicionário S e um ponteiro para 
o registro X, o método consiste em adicionar o 
elemento X ao dicionário.
• Remove: dado um dicionário S e um ponteiro para 
o registro X, o método consiste em remover um 
item X ao dicionário.
Definido esse conceito, vamos apresentar o problema 
que justifica a criação dessa estrutura de dados. Esse problema 
ocorre quando temos muitos dados para ser armazenados e 
precisamos armazená-los, de forma que a busca seja rápida. 
Vamos ver alguns contextos que se encaixam nesse cenário.
• Contexto nº 1: suponhamos que você trabalha 
em uma operadora de telefonia. Neste momento, 
a operadora está cuidando de 1 milhão de telefones, 
onde em cada telefone há um cliente associado, 
que é identificado pelos seus dados pessoais (RG, 
CPF, Endereço, Cidade etc). Nesta operadora, o 
atendimento é iniciado quando o cliente informa o 
número de telefone associado. Assim, é necessário 
que a empresa implemente um sistema eficaz de 
busca de clientes pelo número de telefone associado;
Figura 1 - Amostra de dados aplicada ao Contexto Nº 1, mostrando os dados 
relacionados a um número de telefone.Fonte: Acervo Pessoal.
• Contexto nº 2: o Banco Nacional das Couves está 
implantando um novo sistema para armazenar 
os dados dos clientes. Os clientes são indexados 
pelo número da conta associada. Além disso, são 
armazenados os dados da conta neste registro 
(como saldo, histórico de transferências etc). Esse 
banco usa a mesma sequência de numeração para 
todas as contas, onde esse número pode chegar 
de 000.000 a 799.999. Da mesma forma que o 
contexto anterior, o cliente informa o número da 
sua conta para iniciar seu atendimento. Assim, 
é fundamental um sistema de busca eficaz pelo 
número da conta.
Figura 1 - Amostra de dados aplicada ao Contexto Nº 2, mostrando os dados 
relacionados a um número de conta.Fonte: Acervo Pessoal.
Os dois contextos apresentados são diferentes, 
mas são semelhantes entre si. Há uma necessidade pelo 
armazenamento de um grande número de dados, sendo 
que são indexados por uma chave.
Se analisarmos as estruturas de dados que 
apresentamos, veremos que podemos resolver esse 
problema. Mas, algumas situações podem ocorrer:
• Se armazenarmos em um vetor, podemos 
armazenar cada registro em um item do vetor. 
E assim, bastará indicar a respectiva chave para 
acessar imediatamente o elemento. Porém, 
independente, se tivermos 10, 1000 ou 1 milhão 
de clientes, temos que alocar a quantidade de itens 
a ser armazenados de forma prévia. Isso é ruim, 
pois estamos - em alguns casos - desperdiçando 
memória. No caso do banco, teremos que alocar 
800 mil posições, mesmo que tenhamos apenas 
100 clientes cadastrados.
• Se armazenarmos em uma lista ligada, poderíamos 
ter o armazenamento de forma incremental, 
salvando dados à medida que os dados estão 
sendo criados. O problema é que na lista ligada, 
temos que fazer a busca sequencial, o que pode 
causar uma degradação no desempenho. Imagine 
se buscarmos o cliente João das Flores, que 
está na última posição da lista de clientes da 
operadora telefônica. Teríamos que percorrer 
999.999 registros antes de chegarmos ao registro 
desejado.
 
Assim, chegamos a nossa solução: usar uma estrutura 
de dados que ofereça uma flexibilidade de armazenamento 
e recuperar um dado com poucas operações. Podemos 
criar um vetor de n posições, onde em cada elemento é 
armazenado um registro.
Para que possamos armazenar os dados, usamos uma 
regra: através da chave, computamos uma função para 
definir qual será a posição que armazenaremos o item. Por 
exemplo, definimos que o resto da divisão da chave pelo 
16
15
número de elementos do vetor será o lugar para onde irá 
o dado.
Figura 3 - Exemplo de Tabelas Hash. Fonte: Acervo Pessoal.
Você não deve ter percebido, mas acabamos de 
mostrar o conceito das tabelas de dispersão ou tabelas 
hash. Formalmente, podemos definir como uma estrutura 
aplica uma função aritmética f(k) em uma chave k, e através 
dessa função, temos o endereço de armazenamento do 
dado na tabela - nome a qual damos esse vetor.
Essa estrutura foi concebida com a ideia do acesso 
randômico à memória, onde os dados podem ser 
armazenados em lugares diferentes na memória. Assim, ao 
termos uma chave computada por meio de uma fórmula, 
podemos acessar rapidamente o dado que desejamos.
Mas, que nome dar a esta função que calcula qual é a 
posição? Veremos a seguir.
1.1 - Função de dispersão
Para a função f(k) que computa qual será a posição de 
armazenamento é dado o nome de função de dispersão. 
Autores da área sempre recomendam que uma boa 
função de dispersão deve distribuir de forma uniforme os 
elementos pelos endereços e ser facilmente computado, 
ou seja, a função não deve demorar para computar a 
chave, caso contrário, perdemos os ganhos que obtivemos 
com essa estrutura de dados.
Existem várias formas de calcular o endereço de 
armazenamento. Vejam:
• Método da divisão: é o nome do método 
que usamos na nossa explicação. Consiste em 
dividir a chave pelo tamanho da tabela e, assim, 
aproveitarmos o resto da divisão. Então, se 
queremos armazenar o elemento com a chave 
80 em uma tabela com 11 posições, fazemos o 
seguinte cálculo:
f(80) = 80 mod 11 = 3
O resto da divisão - neste caso, 3, - será o endereço 
onde o elemento X será armazenado. De acordo com 
Cormen et. al. (2012) para que a função tenha um bom 
desempenho, o tamanho da tabela deve ser um número 
primo e não próximo de uma potência de 2. 
Figura 3 - Exemplo do método da divisão. Fonte: Acervo Pessoal.
• Método da dobra: consiste em fazer “dobras” 
sucessivas nos dígitos da chave, somando os 
números que serão sobrepostos, sem considerar 
o “vai-um”. Essa operação prossegue até que seja 
obtido o número de dígitos desejado para a chave.
Figura 4 - Exemplo do método da dobra. Fonte: NONATO, 2003.
• Método da multiplicação: existem diversas 
variações deste método. A mais conhecida é o 
método do meio quadrado, que consiste na seguinte 
regra: multiplica-se a chave por ela mesma, e o 
resultado é convertido em uma palavra de n bits. 
Dessa palavra, aproveitamosos números centrais, 
que passam a ser o endereço do elemento X. 
Szwarcfiter (1994) relata que se separarmos 10 
bits desse cálculo, teremos 1024 endereços base. 
A seguir veremos um exemplo:
f(34) = 34*34 = 1156 = 0000 1156 = 0011
Apesar de termos bons algoritmos de cálculo de 
17
Estrutura de Dados II 16
endereços de armazenamento, as vezes é inevitável que dois 
elementos sejam designados (mapeados) para o mesmo 
endereço. Quando isso ocorre, temos uma colisão. Por 
exemplo, se usarmos uma tabela com 11 posições, e usarmos 
o método da divisão, as chaves 33 e 99 serão designados para 
o mesmo endereço 0. 
Figura 5 - Situação de uma colisão em uma tabela hash.Fonte: Acervo Pessoal.
Mas, existem soluções interessantes para resolver essa 
situação. Veremos isso na seção a seguir.
2 - Resolvendo Colisões
Existem várias soluções que podemos adotar para 
resolver o problema das colisões. Vejamos a partir de agora.
2.1 - Encadeamento Fechado
Para essa situação, usamos uma lista encadeada em cada 
uma das posições da tabela. Assim, a posição pode armazenar 
vários elementos que forem mapeados pela mesma posição, 
sem a preocupação de termos um limite. Ou seja, todos os 
elementos que forem mapeados para o mesmo endereço vão 
ser inseridos em uma mesma lista.
Figura 6 - Demonstração do encadeamento fechado. Fonte: Acervo Pessoal.
A vantagem é que o número de elementos a ser 
mapeados na busca é reduzido, dependendo da qualidade 
da função de dispersão. Porém, temos como desvantagens 
o uso de um campo extra para os ponteiros de ligação e a 
necessidade de adotarmos um tratamento especial para as 
chaves, pois teremos um elemento inserido diretamente na 
tabela e elementos inseridos na lista encadeada. 
Uma solução que podemos adotar para contornar 
isso é transformar a tabela de endereços em uma tabela 
de ponteiros, em que cada ponteiro aponta para a lista 
correspondente. Assim, armazenamos todos os elementos 
diretamente nas listas.
correspondente ao seu endereço. Fonte: Acervo Pessoal.
Vale lembrar que a eficácia dessa forma de armazenamento 
depende da eficácia da função de dispersão. Se muitos elementos 
forem mapeados para o mesmo endereço, poderemos ter uma 
lista longa, e assim, podemos ter problemas de desempenho.
mesmo endereço. Assim, podemos ter um problema de desempenho. Fonte: 
Acervo Pessoal.
2.2 - Hashing Linear
Este método consiste em armazenar os dados que são 
mapeados em um mesmo lugar em posições posteriores na 
tabela. Assim, ao invés de armazenarmos em uma tabela 
separada, colocamos todos os dados em uma mesma tabela, 
usando os espaços vagos. Quando um dado for mapeado em 
uma chave ocupada, o sistema procurará a primeira posição 
vazia da tabela para preencher este dado.
Figura 9 - Demonstração do método de Hashing Linear.Fonte: Acervo Pessoal.
18
17
 A vantagem dessa abordagem é a simplicidade de 
implementação. O problema é quando muitos dados são 
mapeados em uma mesma posição, fazendo que haja um 
agrupamento de dados em uma mesma área, e, da mesma 
forma que ocorre na abordagem anterior, pode ser mais 
demorado encontrar determinado dado.
Seção 2.3 - Hashing Duplo
 É considerado uma variação da abordagem anterior. 
Quando um dado for mapeado para uma posição ocupada 
na tabela, outra função de dispersão é chamada para calcular 
o incremento. Nesse exemplo, a chave 27 é mapeada na 
posição 3, já ocupada. Para resolver o problema calculamos 
um número extra para ser calculado junto com o resultado 
anterior, para que, assim, obtenhamos um novo endereço.
Figura 10 - Demonstração do método de Hashing Duplo.Fonte: Acervo Pessoal.
 A vantagem dessa abordagem é o espelhamento 
melhor das chaves. O problema desse método é que os 
elementos podem estar muito distantes um do outro, violando 
o princípio da localidade.
Atenção: As f rmulas aqui e pressas s estão para efeitos de 
demonstração. Você pode criar, através das premissas dos métodos 
mostrados nesta aula, a forma de calcular o endereço ou tratar 
colisões para a tabela em que você está trabalhando.
Uma boa função de dispersão, que evita muitas colisões, associada a 
uma forma de tratamento de colisões e ciente, pode fazer com que a 
sua tabela de dispersão seja rápida no processo de busca dos dados.
E assim, encerramos a nossa aula. Na próxima aula, 
vamos ver outra estrutura de dados: As árvores. Até mais!
Retomando a aula
Chegamos, assim, ao nal da segunda aula. Espera-se 
que agora tenha cado mais claro o entendimento de 
vocês sobre as tabelas Hash. Vamos, então, recordar?
1 - Entendendo o problema e o que são as Tabelas Hash
Nesta seção, vimos que as Tabelas Hash são estruturas 
que armazenam dados em uma tabela com n posições. A 
posição que o elemento será armazenado é definido por uma 
função de dispersão, que calcula esse endereço com base na 
chave. Existem vários tipos de funções de dispersão. Nesta 
aula, vimos também as três formas de criar a função de 
dispersão: Método da Divisão, Método da Dobra e Método 
da Multiplicação.
2 - Resolvendo Colisões
Nesta seção, vimos que uma colisão ocorre quando dois 
ou mais registros são mapeados para uma mesma posição. 
Para resolver esse problema, estudamos três soluções: 
Encadeamento Fechado (usamos listas encadeadas para 
armazenar os elementos que são designados para o mesmo 
endereço); Hashing Linear (consiste em armazenar os 
dados que são mapeados em um mesmo lugar em posições 
posteriores na tabela); e Hashing Duplo (quando usamos outra 
função para calcular uma nova posição a ser armazenada).
CORMEN, Thomas H.; et. al.. Algoritmos: teoria e 
prática. Rio de Janeiro: Campus, 2012.
KNUTH, Donald Ervin. The art of computer programming. 
3. ed. Amsterdam: Addison-Wesley, 1998.
SZWARCFITER, Jayme Luiz; MARKENZON, 
Lilian. Estruturas de dados e seus algoritmos. 2. ed. Rio de Janeiro: 
LTC, 1994.
Vale a pena ler
Vale a pena
FEOFILOFF, Paulo. Hashing. São Paulo: USP, 2017. 
Disponível em: <https://www.ime.usp.br/~pf/estruturas-
de-dados/aulas/st-hash.html>. Acesso em: 07 ago. 2018.
NONATO, Luis Gustavo. Método da Dobra. 
USP, 2003. Disponível em: <http://www.lcad.icmc.usp.
br/~nonato/ED/Hashing/node36-b.html>. Acesso em: 
08 ago. 2018.
RICARTE, Ivan L. M. Tabelas Hash. UNICAMP, 
2003. Disponível em: <http://www.dca.fee.unicamp.br/
cursos/EA876/apostila/HTML/node26.html>. Acesso 
em: 07 ago. 2018.
Vale a pena acessar
19
Estrutura de Dados II 18
DARELA, Vitor; ORLEÃES, Rafael. Tabelas 
Hash. Disponível em: <https://www.youtube.com/
watch?v=m891Sdamb3U>. Acesso em: 05 ago. 2018. 
PARALUPPI, Paulo. Tabelas Hash - Estrutura de 
Dados - Unicamp. Disponível em: <https://www.youtube.
com/watch?v=Non0I_OSt9o>. Acesso em: 03 ago. 2018. 
Vale a pena assistir
Minhas anotações
20

Mais conteúdos dessa disciplina