Estrutura de
Dados
Tabela de Espalhamento
Tratamento de Colisões
Colisão de Endereçamento Separado
profa. Divani Barbosa
Aula 12 parte 2
Link para slides animados: https://youtu.be/dZraaLrgCGw
Tabela Hash
2
ou Tabela de Espalhamento (Dispersão)
Permite realizar buscas em um conjunto de elementos
armazenados em uma tabela (um arranjo que pode
ser um vetor) de maneira não ordenada.
Mecanismo de Inserção e
Recuperação de Elementos
Necessário um par {chave, elemento}
null
B
null
A
null
E
null
null
D
C
null
B
D
E
C
A
Conjunto
dos
elementos
3
null
B
null
A
null
E
null
null
D
C
null
B
D
E
C
A
Elemento A
Nome: Ana
matricula: 20
Elemento B
Nome: Davi
matricula: 22
Elemento C
Nome: Luca
matricula: 35
⋮
Conjunto
dos
elementos
Todos elementos
do mesmo tipo
Mecanismo de Inserção e
Recuperação de Elementos
Necessário um par {chave, elemento}
null
B
null
A
null
E
null
null
D
C
null
B
D
E
C
A
Elemento A
Nome: Ana
matricula: 20
chave
Elemento B
Nome: Davi
matricula: 22
Elemento C
Nome: Luca
matricula: 35
Conjunto
dos
elementos
0
1
2
.
.
.
tam_max-1
Posição calculada
pela função hash:
⋮
Mecanismo de Inserção e
Recuperação de Elementos
Necessário um par {chave, elemento}
6
 As chaves são imutáveis, portanto, os
elementos do conjunto não podem ser
modificados.
 Tabela Hash não possui função de
alteração das chaves
 No entanto, eles podem ser inseridos e
removidos.
Propriedades
Função HASH
A função Hash é responsável pela aplicação de
uma transformação aritmética sobre a chave.
Essa transformação deve gerar
um valor de varie
de 0 a tam_max-1
índice elemento
0 null
1 B
2 null
3 A
4 null
⋮
E
null
null
D
C
tam_max-1 null
hash(chave)
Transformação com operador resto
funcaohash (chave) = chave % tam_max;
 Aplicável para chaves inteiras.
 Para produzir melhor resultado o tam_max deve ser um
numero primo (menor risco de colisão)
Sedgewick sugere escolher
tam_max da seguinte maneira:
 Escolha uma potência de 2
que esteja próxima do valor
apropriado para os seus dados.
 Adote para tam_max o
número primo que esteja logo
abaixo da potência escolhida.
x 2x tam_max
3 8 7
5 32 31
7 128 127
9 512 509
11 2048 2039
13 8192 8191
15 32768 32749
18 262144 262139
Nesse caso a função seria:
private int funcaohash(int chave) {
return ( Math.abs(chave) % tam_max );
}
Exemplo para o caso da chave ser uma string:
private int funcaohash(String chave) {
int h = chave.charAt(0);
for(int i=0; i<chave.length(); i++)
h = (h * 251 + chave.charAt(i)) % tam_max;
return Math.abs(h);
}
Cada caractere é um número entre 0 e 255. Portanto, uma cadeia pode ser
interpretada por um número.
Método de Horner:
Colisões
 Um problema que pode ocorrer é o caso de duas chaves
distintas gerarem o mesmo índice.
 Este problema é chamado de colisão
 Por maior que seja a tabela, sempre haverá o risco de colisão
 As colisões podem ser tratadas de duas maneiras...
null
A
null
D
null
null
B
null
null
B
D
C
A
0
1
2
3
.
.
.
TAM_MAX-1
Índice Conteúdo
0 null
1 A
2 null
3 D
4 null
⋮
null
B
null
TAM_MAX-1 null
hash(chave)
Solução 1 para colisões
“LINEAR PROBING” ou “Endereçamento Aberto”
 Quando ocorre uma colisão, procura-se a
próxima posição vaga da tabela.
Método ideal para
quando o numero de
elementos a ser usado
pode ser estimado.
Índice Conteúdo
0 null
1 A
2 C
3 D
4 null
⋮
null
B
null
TAM_MAX-1 null
hash(chave)
Solução 1 para colisões
“LINEAR PROBING” ou “Endereçamento Aberto”
 Quando ocorre uma colisão, procura-se a
próxima posição vaga da tabela.
Solução 2 para colisões
“SEPARATE CHAINING”
Cada entrada da tabela conter uma lista encadeada.
 Todos os elementos com chaves que endereçam
para um mesmo índice da tabela são guardados
em uma mesma lista linear
Índice
Nó
Lista SE
0 null
1
2 null
3
4 null
⋮
null
null
TAM_MAX-1 null
hash(chave)
A
D
B
Quando numero de
elementos a ser
usado NÃO pode ser
estimado.
null
null
null
null
Solução 2 para colisões
“SEPARATE CHAINING”
Cada entrada da tabela conter uma lista encadeada.
 Todos os elementos com chaves que endereçam
para um mesmo índice da tabela são guardados
em uma mesma lista linear
Índice
Nó
Lista SE
0 null
1
2 null
3
4 null
⋮
null
null
TAM_MAX-1 null
hash(chave)
A
D
B
C null
null
null
Como a tabela Hash (o vetor Hash)
nesse caso será do tipo Nó Lista.
Usaremos a classe Nó para
armazenar os elementos. Igual na
Lista Simplesmente Encadeada.
Fonte: http://www.ime.usp.br/~pf/algoritmos/aulas/lista.html
class NO {
public double item;
public NO prox;
}
Classe Nó:
espaço para armazenamento da
informação (valor chave)
Um espaço para armazenar uma
referência da localização na
memória onde o próximo
elemento se encontra.
Cada Nó possui um elo (endereço) para o próximo elemento.
primeiro ultimo
null
Classe LISTA:
Campos Construtor
private NO primeiro;
private NO ultimo;
public LISTA() {
primeiro = null;
ultimo = null;
}
Métodos
vazio: Retorna verdadeiro se lista vazia.
insere_lista: Insere itens no final da lista.
pesquisa_lista: Retorna verdadeiro se item
encontrado.
imprime_lista: Imprime lista toda.
apaga_lista Remove item indicado.
Aula 9 – parte 1
public void imprime_lista() {
No atual = primeiro;
while (atual != null) {
System.out.print(atual.item + " -> ");
atual = atual.prox; // caminhando
}
}
Caminhando do
inicio para o
fim da lista
imprime_lista
Entrada: Nenhuma
Saída: Não há
No
PROX = NULL
item = 2,3
No
PROX= NULL
item = 4,5
No
PROX = null
item = 0,5
primeiro
atual
No
PROX = NULL
item = 2,3
No
PROX= NULL
item = 4,5
No
PROX = null
item = 0,5
primeiro
atual
atual
public void imprime_lista() {
No atual = primeiro;
while (atual != null) {
System.out.print(atual.item + " -> ");
atual = atual.prox; // caminhando
}
}
imprime_lista
Entrada: Nenhuma
Saída: Não há
No
PROX = NULL
item = 2,3
No
PROX= NULL
item = 4,5
No
PROX = null
item = 0,5
primeiro
atual
atual
public void imprime_lista() {
No atual = primeiro;
while (atual != null) {
System.out.print(atual.item + " -> ");
atual = atual.prox; // caminhando
}
}
imprime_lista
Entrada: Nenhuma
Saída: Não há
No
PROX = NULL
item = 2,3
No
PROX= NULL
item = 4,5
No
PROX = null
item = 0,5
primeiro
atual = null
public void imprime_lista() {
No atual = primeiro;
while (atual != null) {
System.out.print(atual.item + " -> ");
atual = atual.prox; // caminhando
}
}
imprime_lista
Entrada: Nenhuma
Saída: Não há
public boolean pesquisa_lista(double chave) {
No atual = primeiro;
while (atual != null) { // chegou no final?
if (atual.item == chave) return true; // encontrou item
atual = atual.prox; // caminhando do inicio ao fim
}
return false; // não encontrou item
}
pesquisa_lista
Entrada: Valor chave
Saída: Verdadeiro ou Falso
No
PROX = NULL
item = 2,3
No
PROX= NULL
item = 4,5
No
PROX = null
item = 0,5
primeiro
atual Exemplo: chave = 4,5
public boolean pesquisa_lista(double chave) {
No atual = primeiro;
while (atual != null) { // chegou no final?
if (atual.item == chave) return true; // encontrou item
atual = atual.prox; // caminhando do inicio ao fim
}
return false; // não encontrou item
}
pesquisa_lista
Entrada: Valor chave
Saída: Verdadeiro ou Falso
No
PROX = NULL
item = 2,3
No
PROX= NULL
item = 4,5
No
PROX = null
item = 0,5
primeiro
atual
atual
atual.item == chave → return true
public boolean vazio() {
return (primeiro==null);
}
vazio
Entrada: Não há
Saída: Verdadeiro ou Falso
public boolean pesquisa_lista(double chave) {
No atual = primeiro;
while (atual != null) { // chegou no final?
if (atual.item == chave) return true; // encontrou item
atual = atual.prox; // caminhando do inicio ao fim
}
return false; // não encontrou item
}
pesquisa_lista
Entrada: Valor chave
Saída: Verdadeiro ou Falso
public void apaga(double valor) {
No atual, anterior;
atual = anterior = primeiro;
while (atual != null) { // enquanto no chegou no fim da lista
if (atual.item == valor) break;
anterior = atual;
atual = atual.prox; // caminhando
}
if (atual == null) return;
if (atual == primeiro) {
primeiro = primeiro.prox;
anterior = null;
}
else if (atual == ultimo) {
ultimo = anterior;
ultimo.prox = null;
}
else anterior.prox = atual.prox;
atual = null;
}
apaga_lista
Encontrou item a ser removido
sai do laço while com atual
posicionado nele e anterior
no item anterior ao atual
Entrada: Valor chave
Saída: Não há
public void apaga(double valor) {
No atual, anterior;
atual = anterior = primeiro;
while (atual != null) { // enquanto no chegou no fim da lista
if (atual.item == valor) break;
anterior = atual; // posiciona anterior no item anterior ao atual
atual = atual.prox; // caminhando
}
if (atual == null) return; // se não encontrou item sai
if (atual == primeiro) { // se item na primeira posição
primeiro = primeiro.prox;
anterior = null;
}
else if (atual == ultimo) { // se item na última posição
ultimo = anterior;
ultimo.prox = null;
}
else anterior.prox = atual.prox; // se item no meio
atual = null;
}
apaga_lista Entrada: Valor chave
Saída: Não há
public void apaga(double valor) {
No atual, anterior;
atual = anterior = primeiro;
while (atual != null) {
if (atual.item == valor) break;
anterior = atual;
atual = atual.prox;
}
if (atual == null) return; // se não encontrou item sai
if (atual == primeiro) {
primeiro = primeiro.prox;
anterior = null;
}
else if (atual == ultimo) {
ultimo = anterior;
ultimo.prox = null;
}
else anterior.prox = atual.prox;
atual = null;
}
Comandos de posicionamento
dos itens atual e anterior
Comandos responsáveis
por apagar itens
Comando libera item atual da memoria
apaga_lista Entrada: Valor chave
Saída: Não há
public void apaga(double valor) {
No atual, anterior;
atual = anterior = primeiro;
while (atual != null) {
if (atual.item == valor) break;
anterior = atual;
atual = atual.prox;
}
if (atual == null) return;
if (atual == primeiro) { // se primeiro item
primeiro = primeiro.prox;
anterior = null;
}
else if (atual == ultimo) { // se último item
ultimo = anterior;
ultimo.prox = null;
}
else anterior.prox = atual.prox; // se item do meio
atual = null;
} Comando usado nos três casos
apaga_lista Entrada: Valor chave
Saída: Não há
Se for apagar o primeiro item
if (atual == primeiro) { primeiro = primeiro.prox;
anterior = null;
}
atual = null;
No
PROX =
item = 2,3
No
PROX =
item = 4,5
No
PROX =
item = 0,5
No
PROX= null
item = 8,1
primeiro ultimo
atual
anterior
Se for apagar o primeiro item
No
PROX =
item = 2,3
No
PROX =
item = 4,5
No
PROX =
item = 0,5
No
PROX= null
item = 8,1
primeiro ultimoatual
anterior
if (atual == primeiro) { primeiro = primeiro.prox;
anterior = null;
}
atual = null;
Se for apagar o primeiro item
atual = null
anterior = null
No
PROX =
item = 4,5
No
PROX =
item = 0,5
No
PROX= null
item = 8,1
primeiro ultimo
if (atual == primeiro) { primeiro = primeiro.prox;
anterior = null;
}
atual = null;
Se for apagar o ultimo item
else if (atual == ultimo) { // se ultimo item
ultimo = anterior;
ultimo.prox = null;
}
atual = null;
No
PROX =
item = 2,3
No
PROX =
item = 4,5
No
PROX =
item = 0,5
No
PROX= null
item = 8,1
ultimo
atual
primeiro anterior
Se for apagar o ultimo item
No
PROX =
item = 2,3
No
PROX =
item = 4,5
No
PROX =
item = 0,5
No
PROX= null
item = 8,1
ultimo
atualprimeiro anterior
else if (atual == ultimo) { // se ultimo item
ultimo = anterior;
ultimo.prox = null;
}
atual = null;
Se for apagar o ultimo item
No
PROX =
item = 2,3
No
PROX =
item = 4,5
No
PROX = null
item = 0,5
No
PROX= null
item = 8,1
ultimo
atualprimeiro anterior
else if (atual == ultimo) { // se ultimo item
ultimo = anterior;
ultimo.prox = null;
}
atual = null;
Se for apagar o ultimo item
No
PROX =
item = 2,3
No
PROX =
item = 4,5
No
PROX = null
item = 0,5
ultimo
primeiro anterior
atual = null
else if (atual == ultimo) { // se ultimo item
ultimo = anterior;
ultimo.prox = null;
}
atual = null;
Se for apagar itens do meio
else anterior.prox = atual.prox;
atual = null;
No
PROX =
item = 2,3
No
PROX =
item = 4,5
No
PROX =
item = 0,5
No
PROX= null
item = 8,1
ultimoatualprimeiro anterior
Se for apagar itens do meio
No
PROX =
item = 2,3
No
PROX =
item = 4,5
No
PROX =
item = 0,5
No
PROX= null
item = 8,1
ultimoatualprimeiro anterior
else anterior.prox = atual.prox;
atual = null;
Se for apagar itens do meio
No
PROX =
item = 2,3
No
PROX =
item = 4,5
No
PROX =
item = 0,5
No
PROX= null
item = 8,1
ultimoatualprimeiro anterior
else anterior.prox = atual.prox;
atual = null;
Se for apagar itens do meio
No
PROX =
item = 2,3
No
PROX =
item = 4,5
No
PROX= null
item = 8,1
ultimoprimeiro anterior
atual = null
else anterior.prox = atual.prox;
atual = null;
public void insere_fim(double valor) {
No novo = new No(); // Alocando memoria para o Nó
novo.item = valor;
novo.prox = null;
if (vazio()) primeiro = novo;
else ultimo.prox = novo;
ultimo = novo;
}
insere_lista
No
PROX =
item = Uva
No
PROX =
item = Pera
No
PROX = null
item = Sal
No
PROX= null
item = Alho
primeiro ultimo novo
Entrada: Valor chave
Saída: Não há
No
PROX =
item = Uva
No
PROX =
item = Pera
No
PROX =
item = Sal
No
PROX= null
item = Alho
primeiro ultimo novo
public void insere_fim(double valor) {
No novo = new No(); // Alocando memoria para o Nó
novo.item = valor;
novo.prox = null;
if (vazio()) primeiro = novo;
else ultimo.prox = novo;
ultimo = novo;
}
insere_lista Entrada: Valor chave
Saída: Não há
No
PROX =
item = Uva
No
PROX =
item = Pera
No
PROX =
item = Sal
No
PROX= null
item = Alho
primeiro
ultimo
novo
public void insere_fim(double valor) {
No novo = new No(); // Alocando memoria para o Nó
novo.item = valor;
novo.prox = null;
if (vazio()) primeiro = novo;
else ultimo.prox = novo;
ultimo = novo;
}
insere_lista Entrada: Valor chave
Saída: Não há
Classe LISTA
class LISTA {
private NO primeiro, ultimo;
public LISTA() { primeiro=ultimo=null; } // Construtor
public boolean vazio() { return (primeiro==null); }
public void insere_lista(double valor) { // Insere no FIM
NO novo = new NO(); // Alocando memoria para o Nó
novo.item = valor;
novo.prox = null;
if (vazio()) primeiro = novo;
else ultimo.prox = novo;
ultimo = novo;
}
public boolean pesquisa_lista(double chave) {
NO atual = primeiro;
while (atual != null) { // caminhando para o fim da lista
if(atual.item == chave) return true;
atual = atual.prox;
}
return false;
}
// Continua ...
// Continuação...
public void imprime_lista() {
NO atual = primeiro;
while (atual != null) { // caminhando para o fim da lista
System.out.print(atual.item + " -> ");
atual = atual.prox;
}
}
public void apaga_lista(double valor) { // Apaga item recebido no parâmetro
NO atual, anterior;
atual = anterior = primeiro;
while (atual != null) {
if (atual.item == valor) break;
anterior = atual;
atual = atual.prox;
}
if (atual == null) return; // Item não encontrado
if (atual == primeiro) {
primeiro = primeiro.prox;
anterior = null;
} else if (atual == ultimo) {
ultimo = anterior;
ultimo.prox = null;
} else anterior.prox = atual.prox;
atual = null;
}
} // fim classe LISTA
Classe HashLista:
Campos Construtor
private LISTA[] tab;
private int tam_max;
public HashLista(int tam) {
tab = new LISTA[tam];
tam_max = tam;
for (int i=0; i<tam; i++)
tab[i] = null;
}Métodos
função_hash: retorna posição calculada.
insere: insere itens.
apaga: apaga valor passado no parâmetro.
imprime: imprime tabela completa com listas.
busca: retorna -1 caso não encontre ou
posição da chave buscada na tabela hash.
private int funcaohash(double chave) {
int v = (int) chave;
return ( Math.abs(v) % tam_max );
}
funcao_hash
Entrada: elemento chave
Saída: inteiro que será usado como posição da tabela hash
public void imprime() {
for (int i=0; i<tam_max; i++) {
System.out.print("n HASH[" + i + "] -> ");
if ( tab[i] != null ) tab[i].imprime_lista();
System.out.print("null");
}
}
imprime
Entrada: não há
Saída: não há
public void apaga(double chave) {
int pos = busca(chave);
if (pos != -1) tab[pos].apaga_lista(chave);
else System.out.println("Item nao encontrado");
}
apaga
Entrada: elemento chave
Saída: não há
Se a chave foi
encontrada, apaga ela da
lista
Se a chave
não foi
encontrada, o
item não pode
ser apagado
public int busca(double chave) {
int pos = funcaohash(chave);
if ( tab[pos].pesquisa_lista(chave) ) return pos;
return -1;
}
busca
Entrada: elemento chave
Saída: -1 se não encontrou ou a posição do elemento na tabela
public void insere(double item) {
int pos = funcaohash(item);
if ( tab[pos] != null ) {
if (tab[pos].pesquisa_lista(item)) {
System.out.println("Item " + item + " duplicado");
return;
}
}
else tab[pos] = new LISTA();
tab[pos].insere_lista(item);
}
insere
Entrada: elemento chave
Saída: não há
public void insere(double item) {
int pos = funcaohash(item);
if ( tab[pos] != null ) {
if (tab[pos].pesquisa_lista(item)) {
System.out.println("Item " + item + " duplicado");
return;
}
}
else tab[pos] = new LISTA();
tab[pos].insere_lista(item);
}
Se existe item igual na tabela, saída imediata
insere
Entrada: elemento chave
Saída: não há
public void insere(double item) {
int pos = funcaohash(item);
if ( tab[pos] != null ) {
if (tab[pos].pesquisa_lista(item)) {
System.out.println("Item " + item + " duplicado");
return;
}
}
else tab[pos] = new LISTA();
tab[pos].insere_lista(item);
}
insere
Entrada: elemento chave
Saída: não há
Se existe item igual na tabela, saída imediata
Classe HashLista
class HashLista {
private LISTA[] tab;
private int tam_max;
public HashLista(int tam) {
tab = new LISTA[tam];
tam_max = tam;
for(int i=0; i<tam; i++) tab[i] = null;
}
private int funcaohash(double chave) {
int v = (int) chave;
return ( Math.abs(v) % tam_max );
}
public void insere(double item) {
int pos = funcaohash(item);
if ( tab[pos] != null ) {
if (tab[pos].pesquisa_lista(item)) {
System.out.println(" Item " + item + " duplicado (já cadastrado)");
return;
}
} else tab[pos] = new LISTA();
tab[pos].insere_lista(item);
}
// Continua ...
// Continuação ...
public void apaga(double chave) {
int pos = busca(chave);
if (pos != -1) tab[pos].apaga_lista(chave);
else System.out.println("n Item nao encontrado");
}
public void imprime() {
for (int i=0; i<tam_max; i++) {
System.out.print("n HASH[" + i + "] -> ");
if ( tab[i]!=null ) tab[i].imprime_lista();
System.out.print("null");
}
}
public int busca(double chave) {
int pos = funcaohash(chave);
if ( tab[pos].pesquisa_lista(chave) ) return pos;
return -1;
}
} // fim classe HashLista
52
class HashListaApp {
public static void main(String[] args) {
HashLista tab = new HashLista(7);
Scanner le = new Scanner(System.in);
double item;
System.out.println("n**************************************************");
System.out.println("Tabela HASH colisão Lista (7 itens reais - double)");
System.out.println("**************************************************");
System.out.println(" Inserindo 10 elementos ");
for (int i=0; i<10; i++){
System.out.print(" Inserindo elemento " + (i+1) );
System.out.print(" - Forneca valor real: ");
item = le.nextDouble();
tab.insere(item);
}
System.out.print("n Imprimindo conteudo"); tab.imprime();
while(true) {
System.out.print("nn>> Forneca o item que deseja apagar (-1 sai): ");
item = le.nextDouble();
if (item == -1) break;
tab.apaga(item);
tab.imprime();
}
System.out.println("n");
} // fim programa principal
} // fim classe HashTable
Exemplo de uso da classe HashLista
Pacotes:
import java.util.*;
import java.lang.*;
► Quando o objetivo é ter eficiência nas operações
de pesquisa, inserção e remoção, desde que o
número de inserções e remoções não provoque
variações na quantidade máxima de elementos.
Quando usar Tabela Hash?
► Ela também é indicada quando não há a necessidade de
considerar a ordem das chaves e de saber a posição da
chave de pesquisa em relação as outras chaves.
► Não é indicada para acesso a itens mínimos e máximos.
► São tipicamente usadas em verificadores de ortografia e
como tabelas de símbolos em compiladores de linguagem de
computador, onde um programa tem que verificar milhares
de palavras ou símbolos em uma fração de segundo.
► É uma estrutura de dados do tipo dicionário, pois, não
permite chaves repetidas.
► O objetivo principal da Tabela Hash é o de a
partir de uma chave simples, fazer uma busca
rápida e obter o valor desejado.
Resumo...
Sendo assim, levando em consideração as técnicas de
pesquisa sequencial, binária e baseada em hashing
teremos...
Sequencial Binária Hashing
Vantagem Simplicidade Eficiência Eficiência
Desvantagem Custo elevado Arranjo deve
estar ordenado
Não recupera
em ordem
alfabética
Pior Caso O(n) O(log n) O(n)
Caso Médio O(n) O(log n) O(1)
Tabela de Estruturas de
Armazenamento
de Propósito Geral
Inserção Eliminação Pesquisa
Vetor O(1) O(n) O(n)
Vetor Ordenado O(n) O(n) O(log n)
Lista
Encadeada
O(1)
fim ou inicio
O(n)
meio
O(1)
fim ou inicio
O(n)
meio
O(n)
Lista Encadeada
Ordenada
O(n)
O(1)
fim ou inicio
O(n)
meio
O(n)
Tabela Hash O(n) O(n) O(n)
Resumo das velocidades das várias estruturas de
Armazenamento de Propósito Geral usando a notação Big O
PIOR CASO
Tabela de Estruturas de
Armazenamento
de Propósito Geral
Inserção Eliminação Pesquisa
Vetor O(1) O(n) O(n)
Vetor Ordenado O(n) O(n) O(log n)
Lista
Encadeada
O(1)
fim ou inicio
O(n)
meio
O(1)
fim ou inicio
O(n)
meio
O(n)
Lista Encadeada
Ordenada
O(n)
O(1)
fim ou inicio
O(n)
meio
O(n)
Tabela Hash O(1) O(1) O(1)
Resumo das velocidades das várias estruturas de
Armazenamento de Propósito Geral usando a notação Big O
CASO MÉDIO
Atividades
1. Crie um programa em java que implemente uma
Tabela Hash com tratamento de colisões dado por
uma lista encadeada ORDENADA.
Trabalho 2 - Exercício 5. Desenvolva um programa em java
que implemente uma Tabela Hash com tratamento de colisões
do tipo lista. O elemento a ser armazenado será uma agenda
telefônica com nome (item chave) e telefone. A agenda deve
apresentar funções na forma de menu:
1. Armazenar um registro (nome e telefone)
2. Obter o telefone a partir de um nome
3. Remover um registro (nome e telefone)
4. Listar todos os registros armazenados.
5. Sair do programa

Aula 17

  • 1.
    Estrutura de Dados Tabela deEspalhamento Tratamento de Colisões Colisão de Endereçamento Separado profa. Divani Barbosa Aula 12 parte 2 Link para slides animados: https://youtu.be/dZraaLrgCGw
  • 2.
    Tabela Hash 2 ou Tabelade Espalhamento (Dispersão) Permite realizar buscas em um conjunto de elementos armazenados em uma tabela (um arranjo que pode ser um vetor) de maneira não ordenada.
  • 3.
    Mecanismo de Inserçãoe Recuperação de Elementos Necessário um par {chave, elemento} null B null A null E null null D C null B D E C A Conjunto dos elementos 3
  • 4.
    null B null A null E null null D C null B D E C A Elemento A Nome: Ana matricula:20 Elemento B Nome: Davi matricula: 22 Elemento C Nome: Luca matricula: 35 ⋮ Conjunto dos elementos Todos elementos do mesmo tipo Mecanismo de Inserção e Recuperação de Elementos Necessário um par {chave, elemento}
  • 5.
    null B null A null E null null D C null B D E C A Elemento A Nome: Ana matricula:20 chave Elemento B Nome: Davi matricula: 22 Elemento C Nome: Luca matricula: 35 Conjunto dos elementos 0 1 2 . . . tam_max-1 Posição calculada pela função hash: ⋮ Mecanismo de Inserção e Recuperação de Elementos Necessário um par {chave, elemento}
  • 6.
    6  As chavessão imutáveis, portanto, os elementos do conjunto não podem ser modificados.  Tabela Hash não possui função de alteração das chaves  No entanto, eles podem ser inseridos e removidos. Propriedades
  • 7.
    Função HASH A funçãoHash é responsável pela aplicação de uma transformação aritmética sobre a chave. Essa transformação deve gerar um valor de varie de 0 a tam_max-1 índice elemento 0 null 1 B 2 null 3 A 4 null ⋮ E null null D C tam_max-1 null hash(chave)
  • 8.
    Transformação com operadorresto funcaohash (chave) = chave % tam_max;  Aplicável para chaves inteiras.  Para produzir melhor resultado o tam_max deve ser um numero primo (menor risco de colisão) Sedgewick sugere escolher tam_max da seguinte maneira:  Escolha uma potência de 2 que esteja próxima do valor apropriado para os seus dados.  Adote para tam_max o número primo que esteja logo abaixo da potência escolhida. x 2x tam_max 3 8 7 5 32 31 7 128 127 9 512 509 11 2048 2039 13 8192 8191 15 32768 32749 18 262144 262139
  • 9.
    Nesse caso afunção seria: private int funcaohash(int chave) { return ( Math.abs(chave) % tam_max ); } Exemplo para o caso da chave ser uma string: private int funcaohash(String chave) { int h = chave.charAt(0); for(int i=0; i<chave.length(); i++) h = (h * 251 + chave.charAt(i)) % tam_max; return Math.abs(h); } Cada caractere é um número entre 0 e 255. Portanto, uma cadeia pode ser interpretada por um número. Método de Horner:
  • 10.
    Colisões  Um problemaque pode ocorrer é o caso de duas chaves distintas gerarem o mesmo índice.  Este problema é chamado de colisão  Por maior que seja a tabela, sempre haverá o risco de colisão  As colisões podem ser tratadas de duas maneiras... null A null D null null B null null B D C A 0 1 2 3 . . . TAM_MAX-1
  • 11.
    Índice Conteúdo 0 null 1A 2 null 3 D 4 null ⋮ null B null TAM_MAX-1 null hash(chave) Solução 1 para colisões “LINEAR PROBING” ou “Endereçamento Aberto”  Quando ocorre uma colisão, procura-se a próxima posição vaga da tabela. Método ideal para quando o numero de elementos a ser usado pode ser estimado.
  • 12.
    Índice Conteúdo 0 null 1A 2 C 3 D 4 null ⋮ null B null TAM_MAX-1 null hash(chave) Solução 1 para colisões “LINEAR PROBING” ou “Endereçamento Aberto”  Quando ocorre uma colisão, procura-se a próxima posição vaga da tabela.
  • 13.
    Solução 2 paracolisões “SEPARATE CHAINING” Cada entrada da tabela conter uma lista encadeada.  Todos os elementos com chaves que endereçam para um mesmo índice da tabela são guardados em uma mesma lista linear Índice Nó Lista SE 0 null 1 2 null 3 4 null ⋮ null null TAM_MAX-1 null hash(chave) A D B Quando numero de elementos a ser usado NÃO pode ser estimado. null null null
  • 14.
    null Solução 2 paracolisões “SEPARATE CHAINING” Cada entrada da tabela conter uma lista encadeada.  Todos os elementos com chaves que endereçam para um mesmo índice da tabela são guardados em uma mesma lista linear Índice Nó Lista SE 0 null 1 2 null 3 4 null ⋮ null null TAM_MAX-1 null hash(chave) A D B C null null null
  • 15.
    Como a tabelaHash (o vetor Hash) nesse caso será do tipo Nó Lista. Usaremos a classe Nó para armazenar os elementos. Igual na Lista Simplesmente Encadeada. Fonte: http://www.ime.usp.br/~pf/algoritmos/aulas/lista.html class NO { public double item; public NO prox; } Classe Nó: espaço para armazenamento da informação (valor chave) Um espaço para armazenar uma referência da localização na memória onde o próximo elemento se encontra. Cada Nó possui um elo (endereço) para o próximo elemento. primeiro ultimo null
  • 16.
    Classe LISTA: Campos Construtor privateNO primeiro; private NO ultimo; public LISTA() { primeiro = null; ultimo = null; } Métodos vazio: Retorna verdadeiro se lista vazia. insere_lista: Insere itens no final da lista. pesquisa_lista: Retorna verdadeiro se item encontrado. imprime_lista: Imprime lista toda. apaga_lista Remove item indicado. Aula 9 – parte 1
  • 17.
    public void imprime_lista(){ No atual = primeiro; while (atual != null) { System.out.print(atual.item + " -> "); atual = atual.prox; // caminhando } } Caminhando do inicio para o fim da lista imprime_lista Entrada: Nenhuma Saída: Não há No PROX = NULL item = 2,3 No PROX= NULL item = 4,5 No PROX = null item = 0,5 primeiro atual
  • 18.
    No PROX = NULL item= 2,3 No PROX= NULL item = 4,5 No PROX = null item = 0,5 primeiro atual atual public void imprime_lista() { No atual = primeiro; while (atual != null) { System.out.print(atual.item + " -> "); atual = atual.prox; // caminhando } } imprime_lista Entrada: Nenhuma Saída: Não há
  • 19.
    No PROX = NULL item= 2,3 No PROX= NULL item = 4,5 No PROX = null item = 0,5 primeiro atual atual public void imprime_lista() { No atual = primeiro; while (atual != null) { System.out.print(atual.item + " -> "); atual = atual.prox; // caminhando } } imprime_lista Entrada: Nenhuma Saída: Não há
  • 20.
    No PROX = NULL item= 2,3 No PROX= NULL item = 4,5 No PROX = null item = 0,5 primeiro atual = null public void imprime_lista() { No atual = primeiro; while (atual != null) { System.out.print(atual.item + " -> "); atual = atual.prox; // caminhando } } imprime_lista Entrada: Nenhuma Saída: Não há
  • 21.
    public boolean pesquisa_lista(doublechave) { No atual = primeiro; while (atual != null) { // chegou no final? if (atual.item == chave) return true; // encontrou item atual = atual.prox; // caminhando do inicio ao fim } return false; // não encontrou item } pesquisa_lista Entrada: Valor chave Saída: Verdadeiro ou Falso No PROX = NULL item = 2,3 No PROX= NULL item = 4,5 No PROX = null item = 0,5 primeiro atual Exemplo: chave = 4,5
  • 22.
    public boolean pesquisa_lista(doublechave) { No atual = primeiro; while (atual != null) { // chegou no final? if (atual.item == chave) return true; // encontrou item atual = atual.prox; // caminhando do inicio ao fim } return false; // não encontrou item } pesquisa_lista Entrada: Valor chave Saída: Verdadeiro ou Falso No PROX = NULL item = 2,3 No PROX= NULL item = 4,5 No PROX = null item = 0,5 primeiro atual atual atual.item == chave → return true
  • 23.
    public boolean vazio(){ return (primeiro==null); } vazio Entrada: Não há Saída: Verdadeiro ou Falso public boolean pesquisa_lista(double chave) { No atual = primeiro; while (atual != null) { // chegou no final? if (atual.item == chave) return true; // encontrou item atual = atual.prox; // caminhando do inicio ao fim } return false; // não encontrou item } pesquisa_lista Entrada: Valor chave Saída: Verdadeiro ou Falso
  • 24.
    public void apaga(doublevalor) { No atual, anterior; atual = anterior = primeiro; while (atual != null) { // enquanto no chegou no fim da lista if (atual.item == valor) break; anterior = atual; atual = atual.prox; // caminhando } if (atual == null) return; if (atual == primeiro) { primeiro = primeiro.prox; anterior = null; } else if (atual == ultimo) { ultimo = anterior; ultimo.prox = null; } else anterior.prox = atual.prox; atual = null; } apaga_lista Encontrou item a ser removido sai do laço while com atual posicionado nele e anterior no item anterior ao atual Entrada: Valor chave Saída: Não há
  • 25.
    public void apaga(doublevalor) { No atual, anterior; atual = anterior = primeiro; while (atual != null) { // enquanto no chegou no fim da lista if (atual.item == valor) break; anterior = atual; // posiciona anterior no item anterior ao atual atual = atual.prox; // caminhando } if (atual == null) return; // se não encontrou item sai if (atual == primeiro) { // se item na primeira posição primeiro = primeiro.prox; anterior = null; } else if (atual == ultimo) { // se item na última posição ultimo = anterior; ultimo.prox = null; } else anterior.prox = atual.prox; // se item no meio atual = null; } apaga_lista Entrada: Valor chave Saída: Não há
  • 26.
    public void apaga(doublevalor) { No atual, anterior; atual = anterior = primeiro; while (atual != null) { if (atual.item == valor) break; anterior = atual; atual = atual.prox; } if (atual == null) return; // se não encontrou item sai if (atual == primeiro) { primeiro = primeiro.prox; anterior = null; } else if (atual == ultimo) { ultimo = anterior; ultimo.prox = null; } else anterior.prox = atual.prox; atual = null; } Comandos de posicionamento dos itens atual e anterior Comandos responsáveis por apagar itens Comando libera item atual da memoria apaga_lista Entrada: Valor chave Saída: Não há
  • 27.
    public void apaga(doublevalor) { No atual, anterior; atual = anterior = primeiro; while (atual != null) { if (atual.item == valor) break; anterior = atual; atual = atual.prox; } if (atual == null) return; if (atual == primeiro) { // se primeiro item primeiro = primeiro.prox; anterior = null; } else if (atual == ultimo) { // se último item ultimo = anterior; ultimo.prox = null; } else anterior.prox = atual.prox; // se item do meio atual = null; } Comando usado nos três casos apaga_lista Entrada: Valor chave Saída: Não há
  • 28.
    Se for apagaro primeiro item if (atual == primeiro) { primeiro = primeiro.prox; anterior = null; } atual = null; No PROX = item = 2,3 No PROX = item = 4,5 No PROX = item = 0,5 No PROX= null item = 8,1 primeiro ultimo atual anterior
  • 29.
    Se for apagaro primeiro item No PROX = item = 2,3 No PROX = item = 4,5 No PROX = item = 0,5 No PROX= null item = 8,1 primeiro ultimoatual anterior if (atual == primeiro) { primeiro = primeiro.prox; anterior = null; } atual = null;
  • 30.
    Se for apagaro primeiro item atual = null anterior = null No PROX = item = 4,5 No PROX = item = 0,5 No PROX= null item = 8,1 primeiro ultimo if (atual == primeiro) { primeiro = primeiro.prox; anterior = null; } atual = null;
  • 31.
    Se for apagaro ultimo item else if (atual == ultimo) { // se ultimo item ultimo = anterior; ultimo.prox = null; } atual = null; No PROX = item = 2,3 No PROX = item = 4,5 No PROX = item = 0,5 No PROX= null item = 8,1 ultimo atual primeiro anterior
  • 32.
    Se for apagaro ultimo item No PROX = item = 2,3 No PROX = item = 4,5 No PROX = item = 0,5 No PROX= null item = 8,1 ultimo atualprimeiro anterior else if (atual == ultimo) { // se ultimo item ultimo = anterior; ultimo.prox = null; } atual = null;
  • 33.
    Se for apagaro ultimo item No PROX = item = 2,3 No PROX = item = 4,5 No PROX = null item = 0,5 No PROX= null item = 8,1 ultimo atualprimeiro anterior else if (atual == ultimo) { // se ultimo item ultimo = anterior; ultimo.prox = null; } atual = null;
  • 34.
    Se for apagaro ultimo item No PROX = item = 2,3 No PROX = item = 4,5 No PROX = null item = 0,5 ultimo primeiro anterior atual = null else if (atual == ultimo) { // se ultimo item ultimo = anterior; ultimo.prox = null; } atual = null;
  • 35.
    Se for apagaritens do meio else anterior.prox = atual.prox; atual = null; No PROX = item = 2,3 No PROX = item = 4,5 No PROX = item = 0,5 No PROX= null item = 8,1 ultimoatualprimeiro anterior
  • 36.
    Se for apagaritens do meio No PROX = item = 2,3 No PROX = item = 4,5 No PROX = item = 0,5 No PROX= null item = 8,1 ultimoatualprimeiro anterior else anterior.prox = atual.prox; atual = null;
  • 37.
    Se for apagaritens do meio No PROX = item = 2,3 No PROX = item = 4,5 No PROX = item = 0,5 No PROX= null item = 8,1 ultimoatualprimeiro anterior else anterior.prox = atual.prox; atual = null;
  • 38.
    Se for apagaritens do meio No PROX = item = 2,3 No PROX = item = 4,5 No PROX= null item = 8,1 ultimoprimeiro anterior atual = null else anterior.prox = atual.prox; atual = null;
  • 39.
    public void insere_fim(doublevalor) { No novo = new No(); // Alocando memoria para o Nó novo.item = valor; novo.prox = null; if (vazio()) primeiro = novo; else ultimo.prox = novo; ultimo = novo; } insere_lista No PROX = item = Uva No PROX = item = Pera No PROX = null item = Sal No PROX= null item = Alho primeiro ultimo novo Entrada: Valor chave Saída: Não há
  • 40.
    No PROX = item =Uva No PROX = item = Pera No PROX = item = Sal No PROX= null item = Alho primeiro ultimo novo public void insere_fim(double valor) { No novo = new No(); // Alocando memoria para o Nó novo.item = valor; novo.prox = null; if (vazio()) primeiro = novo; else ultimo.prox = novo; ultimo = novo; } insere_lista Entrada: Valor chave Saída: Não há
  • 41.
    No PROX = item =Uva No PROX = item = Pera No PROX = item = Sal No PROX= null item = Alho primeiro ultimo novo public void insere_fim(double valor) { No novo = new No(); // Alocando memoria para o Nó novo.item = valor; novo.prox = null; if (vazio()) primeiro = novo; else ultimo.prox = novo; ultimo = novo; } insere_lista Entrada: Valor chave Saída: Não há
  • 42.
    Classe LISTA class LISTA{ private NO primeiro, ultimo; public LISTA() { primeiro=ultimo=null; } // Construtor public boolean vazio() { return (primeiro==null); } public void insere_lista(double valor) { // Insere no FIM NO novo = new NO(); // Alocando memoria para o Nó novo.item = valor; novo.prox = null; if (vazio()) primeiro = novo; else ultimo.prox = novo; ultimo = novo; } public boolean pesquisa_lista(double chave) { NO atual = primeiro; while (atual != null) { // caminhando para o fim da lista if(atual.item == chave) return true; atual = atual.prox; } return false; } // Continua ...
  • 43.
    // Continuação... public voidimprime_lista() { NO atual = primeiro; while (atual != null) { // caminhando para o fim da lista System.out.print(atual.item + " -> "); atual = atual.prox; } } public void apaga_lista(double valor) { // Apaga item recebido no parâmetro NO atual, anterior; atual = anterior = primeiro; while (atual != null) { if (atual.item == valor) break; anterior = atual; atual = atual.prox; } if (atual == null) return; // Item não encontrado if (atual == primeiro) { primeiro = primeiro.prox; anterior = null; } else if (atual == ultimo) { ultimo = anterior; ultimo.prox = null; } else anterior.prox = atual.prox; atual = null; } } // fim classe LISTA
  • 44.
    Classe HashLista: Campos Construtor privateLISTA[] tab; private int tam_max; public HashLista(int tam) { tab = new LISTA[tam]; tam_max = tam; for (int i=0; i<tam; i++) tab[i] = null; }Métodos função_hash: retorna posição calculada. insere: insere itens. apaga: apaga valor passado no parâmetro. imprime: imprime tabela completa com listas. busca: retorna -1 caso não encontre ou posição da chave buscada na tabela hash.
  • 45.
    private int funcaohash(doublechave) { int v = (int) chave; return ( Math.abs(v) % tam_max ); } funcao_hash Entrada: elemento chave Saída: inteiro que será usado como posição da tabela hash public void imprime() { for (int i=0; i<tam_max; i++) { System.out.print("n HASH[" + i + "] -> "); if ( tab[i] != null ) tab[i].imprime_lista(); System.out.print("null"); } } imprime Entrada: não há Saída: não há
  • 46.
    public void apaga(doublechave) { int pos = busca(chave); if (pos != -1) tab[pos].apaga_lista(chave); else System.out.println("Item nao encontrado"); } apaga Entrada: elemento chave Saída: não há Se a chave foi encontrada, apaga ela da lista Se a chave não foi encontrada, o item não pode ser apagado public int busca(double chave) { int pos = funcaohash(chave); if ( tab[pos].pesquisa_lista(chave) ) return pos; return -1; } busca Entrada: elemento chave Saída: -1 se não encontrou ou a posição do elemento na tabela
  • 47.
    public void insere(doubleitem) { int pos = funcaohash(item); if ( tab[pos] != null ) { if (tab[pos].pesquisa_lista(item)) { System.out.println("Item " + item + " duplicado"); return; } } else tab[pos] = new LISTA(); tab[pos].insere_lista(item); } insere Entrada: elemento chave Saída: não há
  • 48.
    public void insere(doubleitem) { int pos = funcaohash(item); if ( tab[pos] != null ) { if (tab[pos].pesquisa_lista(item)) { System.out.println("Item " + item + " duplicado"); return; } } else tab[pos] = new LISTA(); tab[pos].insere_lista(item); } Se existe item igual na tabela, saída imediata insere Entrada: elemento chave Saída: não há
  • 49.
    public void insere(doubleitem) { int pos = funcaohash(item); if ( tab[pos] != null ) { if (tab[pos].pesquisa_lista(item)) { System.out.println("Item " + item + " duplicado"); return; } } else tab[pos] = new LISTA(); tab[pos].insere_lista(item); } insere Entrada: elemento chave Saída: não há Se existe item igual na tabela, saída imediata
  • 50.
    Classe HashLista class HashLista{ private LISTA[] tab; private int tam_max; public HashLista(int tam) { tab = new LISTA[tam]; tam_max = tam; for(int i=0; i<tam; i++) tab[i] = null; } private int funcaohash(double chave) { int v = (int) chave; return ( Math.abs(v) % tam_max ); } public void insere(double item) { int pos = funcaohash(item); if ( tab[pos] != null ) { if (tab[pos].pesquisa_lista(item)) { System.out.println(" Item " + item + " duplicado (já cadastrado)"); return; } } else tab[pos] = new LISTA(); tab[pos].insere_lista(item); } // Continua ...
  • 51.
    // Continuação ... publicvoid apaga(double chave) { int pos = busca(chave); if (pos != -1) tab[pos].apaga_lista(chave); else System.out.println("n Item nao encontrado"); } public void imprime() { for (int i=0; i<tam_max; i++) { System.out.print("n HASH[" + i + "] -> "); if ( tab[i]!=null ) tab[i].imprime_lista(); System.out.print("null"); } } public int busca(double chave) { int pos = funcaohash(chave); if ( tab[pos].pesquisa_lista(chave) ) return pos; return -1; } } // fim classe HashLista
  • 52.
    52 class HashListaApp { publicstatic void main(String[] args) { HashLista tab = new HashLista(7); Scanner le = new Scanner(System.in); double item; System.out.println("n**************************************************"); System.out.println("Tabela HASH colisão Lista (7 itens reais - double)"); System.out.println("**************************************************"); System.out.println(" Inserindo 10 elementos "); for (int i=0; i<10; i++){ System.out.print(" Inserindo elemento " + (i+1) ); System.out.print(" - Forneca valor real: "); item = le.nextDouble(); tab.insere(item); } System.out.print("n Imprimindo conteudo"); tab.imprime(); while(true) { System.out.print("nn>> Forneca o item que deseja apagar (-1 sai): "); item = le.nextDouble(); if (item == -1) break; tab.apaga(item); tab.imprime(); } System.out.println("n"); } // fim programa principal } // fim classe HashTable Exemplo de uso da classe HashLista Pacotes: import java.util.*; import java.lang.*;
  • 53.
    ► Quando oobjetivo é ter eficiência nas operações de pesquisa, inserção e remoção, desde que o número de inserções e remoções não provoque variações na quantidade máxima de elementos. Quando usar Tabela Hash? ► Ela também é indicada quando não há a necessidade de considerar a ordem das chaves e de saber a posição da chave de pesquisa em relação as outras chaves. ► Não é indicada para acesso a itens mínimos e máximos. ► São tipicamente usadas em verificadores de ortografia e como tabelas de símbolos em compiladores de linguagem de computador, onde um programa tem que verificar milhares de palavras ou símbolos em uma fração de segundo. ► É uma estrutura de dados do tipo dicionário, pois, não permite chaves repetidas.
  • 54.
    ► O objetivoprincipal da Tabela Hash é o de a partir de uma chave simples, fazer uma busca rápida e obter o valor desejado. Resumo... Sendo assim, levando em consideração as técnicas de pesquisa sequencial, binária e baseada em hashing teremos... Sequencial Binária Hashing Vantagem Simplicidade Eficiência Eficiência Desvantagem Custo elevado Arranjo deve estar ordenado Não recupera em ordem alfabética Pior Caso O(n) O(log n) O(n) Caso Médio O(n) O(log n) O(1)
  • 55.
    Tabela de Estruturasde Armazenamento de Propósito Geral Inserção Eliminação Pesquisa Vetor O(1) O(n) O(n) Vetor Ordenado O(n) O(n) O(log n) Lista Encadeada O(1) fim ou inicio O(n) meio O(1) fim ou inicio O(n) meio O(n) Lista Encadeada Ordenada O(n) O(1) fim ou inicio O(n) meio O(n) Tabela Hash O(n) O(n) O(n) Resumo das velocidades das várias estruturas de Armazenamento de Propósito Geral usando a notação Big O PIOR CASO
  • 56.
    Tabela de Estruturasde Armazenamento de Propósito Geral Inserção Eliminação Pesquisa Vetor O(1) O(n) O(n) Vetor Ordenado O(n) O(n) O(log n) Lista Encadeada O(1) fim ou inicio O(n) meio O(1) fim ou inicio O(n) meio O(n) Lista Encadeada Ordenada O(n) O(1) fim ou inicio O(n) meio O(n) Tabela Hash O(1) O(1) O(1) Resumo das velocidades das várias estruturas de Armazenamento de Propósito Geral usando a notação Big O CASO MÉDIO
  • 57.
    Atividades 1. Crie umprograma em java que implemente uma Tabela Hash com tratamento de colisões dado por uma lista encadeada ORDENADA. Trabalho 2 - Exercício 5. Desenvolva um programa em java que implemente uma Tabela Hash com tratamento de colisões do tipo lista. O elemento a ser armazenado será uma agenda telefônica com nome (item chave) e telefone. A agenda deve apresentar funções na forma de menu: 1. Armazenar um registro (nome e telefone) 2. Obter o telefone a partir de um nome 3. Remover um registro (nome e telefone) 4. Listar todos os registros armazenados. 5. Sair do programa