Você está na página 1de 33

Abrir Endereçamento

No endereçamento aberto, quando um dado não pode ser colocado no índice calculado pelo
função hash, outro local na matriz é procurado. Vamos explorar três métodos
de endereçamento aberto, que variam no método usado para encontrar a próxima célula vaga.
Esses métodos são sondagem linear, sondagem quadrática e hash duplo.
Sondagem Linear
Na sondagem linear procuramos sequencialmente células vazias. Se 5.421 estiver ocupado
quando
tentamos inserir um item de dados lá, vamos para 5.422, depois 5.423 e assim por diante,
incrementando
o índice até encontrarmos uma célula vazia. Isso é chamado de sondagem linear porque
passos sequencialmente ao longo da linha de células.
O miniaplicativo de oficina de hash
O miniaplicativo Hash Workshop demonstra a sondagem linear. Ao iniciar este miniaplicativo,
você verá uma tela semelhante à Figura 11.5.
Neste applet, o intervalo de chaves vai de 0 a 999. O tamanho inicial do array é 60.
A função de hash precisa espremer o intervalo de chaves para corresponder ao tamanho do array.
Isto
faz isso com o operador módulo (%), como vimos antes:
arrayIndex = chave % arraySize
Para o tamanho inicial do array de 60, isso é
arrayIndex = chave % 60;
Essa função de hash é simples o suficiente para que você possa resolvê-la mentalmente. Para
uma determinada chave,
continue subtraindo 60 até obter um número menor que 60. Por exemplo, para hash 143,
subtraia 60, dando 83, e então 60 novamente, dando 23. Este é o número de índice onde
o algoritmo irá colocar 143. Assim, você pode facilmente verificar se o algoritmo tem
hash uma chave para o endereço correto. (Um tamanho de array de 10 é ainda mais fácil de
descobrir,
pois o último dígito de uma chave é o índice para o qual ela será hash.)
Tal como acontece com outros applets, as operações são realizadas pressionando repetidamente o
mesmo
botão. Por exemplo, para localizar um item de dados com um número especificado, clique no
botão Localizar
botão repetidamente. Lembre-se, termine uma sequência com um botão antes de usar
outro botão. Por exemplo, não mude de clicar em Preencher para outro botão
até que a mensagem pressione qualquer tecla seja exibida.
Todas as operações exigem que você digite um valor numérico no início do
sequência. O botão Localizar exige que você digite um valor de chave, por exemplo, enquanto
Novo
requer o tamanho da nova tabela.
O botão Novo Você pode criar uma nova tabela de hash de um tamanho especificado usando o
botão
Novo botão. O tamanho máximo é 60; esta limitação resulta do número de
células que podem ser visualizadas na janela do applet. O tamanho inicial também é 60. Usamos
isso
number porque facilita verificar se os valores de hash estão corretos, mas como
veremos mais tarde, em uma tabela de hash de uso geral, o tamanho do array deve ser um primo
número, então 59 seria uma escolha melhor.
O botão de preenchimento Inicialmente, a tabela de hash contém 30 itens, portanto, está meio
cheia. No entanto,
você também pode preenchê-lo com um número específico de itens de dados usando o botão
Preencher. Manter
clicando em Preencher e, quando solicitado, digite o número de itens a serem preenchidos.
Tabelas de hash funcionam
melhor quando não estão mais da metade ou no máximo dois terços cheios (40 itens em um
tabela de 60 células).
Você verá que as células preenchidas não estão distribuídas uniformemente nas células. Às vezes

uma sequência de várias células vazias e às vezes uma sequência de células preenchidas.
Vamos chamar uma sequência de células preenchidas em uma tabela de hash de sequência
preenchida. À medida que você adiciona mais
e mais itens, as sequências preenchidas tornam-se mais longas. Isso é chamado de agrupamento e
é
mostrado na Figura 11.6

Ao usar o applet, observe que pode levar muito tempo para preencher uma tabela de hash se você
tente preenchê-lo muito (por exemplo, se você tentar colocar 59 itens em uma tabela de 60
células). Você
pode pensar que o programa parou, mas seja paciente. É extremamente ineficiente em
preenchendo uma matriz quase cheia.
12 0672324539 CH11 10/10/02 09:17 Página 530
Além disso, observe que, se a tabela de hash ficar completamente cheia, todos os algoritmos
param
trabalhando; neste applet eles assumem que a tabela tem pelo menos uma célula vazia.
O Botão Localizar O botão Localizar começa aplicando a função hash à chave
valor digitado na caixa de número. Isso resulta em um índice de matriz. A célula neste
index pode ser a chave que você está procurando; esta é a situação ideal, e o sucesso
será informado imediatamente.
No entanto, também é possível que esta célula já esteja ocupada por um item de dados com
algum
outra chave. Esta é uma colisão; você verá a seta vermelha apontando para uma célula ocupada.
Após uma colisão, o algoritmo de busca examinará a próxima célula na sequência. o
processo de encontrar uma célula apropriada após uma colisão é chamado de sonda.
Após uma colisão, o algoritmo Find simplesmente percorre a matriz olhando para
cada célula em sequência. Se encontrar uma célula vazia antes de encontrar a chave, é
procurando, ele sabe que a busca falhou. Não adianta procurar mais porque o
algoritmo de inserção teria inserido o item nesta célula (se não antes). Figura
11.7 mostra sondas lineares bem sucedidas e mal sucedidas.

Botão Ins O botão Ins insere um item de dados, com um valor de chave que você digita
na caixa numérica, na tabela de hash. Ele usa o mesmo algoritmo que o Find
botão para localizar a célula apropriada. Se a célula original estiver ocupada, ela sondará
linearmente para uma célula vazia. Quando encontra um, insere o item.
12 0672324539 CH11 10/10/02 09:17 Página 531
Tente inserir alguns novos itens de dados. Digite um número de três dígitos e veja o que
acontece. A maioria dos itens irá para a primeira célula que eles tentarem, mas alguns sofrerão
colisões
e precisa avançar para encontrar uma célula vazia. O número de passos que eles dão é o
comprimento da sonda. A maioria dos comprimentos de sonda tem apenas algumas células. Às
vezes, porém, você
pode ver comprimentos de sonda de quatro ou cinco células, ou ainda mais à medida que a
matriz se torna
excessivamente cheio.
Observe quais chaves fazem hash para o mesmo índice. Se o tamanho do array for 60, as chaves
7, 67, 127,
187, 247 e assim por diante até 967, todos hash para o índice 7. Tente inserir esta sequência ou
um
semelhante. Tais sequências irão demonstrar a sonda linear.
O botão Del O botão Del exclui um item cuja chave é digitada pelo usuário.
A exclusão não é realizada simplesmente removendo um item de dados de uma célula, deixando-
o
vazio. Por que não? Lembre-se de que, durante a inserção, o processo do apalpador percorre um
série de células, procurando uma vaga. Se uma célula ficar vazia no meio deste
sequência de células cheias, a rotina Find desistirá quando vir a célula vazia, mesmo
se a célula desejada pode eventualmente ser alcançada.
Por esta razão, um item excluído é substituído por um item com um valor de chave especial que
identifica-o como excluído. Neste applet, assumimos que todos os valores de chave legítimos são
positivos,
então o valor deletado é escolhido como –1. Os itens excluídos são marcados com a tecla
especial
*Del*.
O botão Inserir irá inserir um novo item na primeira célula vazia disponível ou em um *Del*
item. O botão Localizar tratará um item *Del* como um item existente para fins de
procurando por outro item mais adiante.
Se houver muitas exclusões, a tabela de hash será preenchida com esses itens de dados ersatz
*Del*,
o que o torna menos eficiente. Por esta razão, muitas implementações de tabelas de hash não
permitir a exclusão. Se for implementado, deve ser usado com moderação.
Duplicatas permitidas?
Você pode permitir que itens de dados com chaves duplicadas sejam usados em tabelas de hash?
O preenchimento
rotina no miniaplicativo Hash Workshop não permite duplicatas, mas você pode inserir
com o botão Inserir, se desejar. Então você verá que apenas o primeiro pode ser
acessado. A única maneira de acessar um segundo item com a mesma chave é excluir o
primeiro. Isso não é muito conveniente.
Você pode reescrever o algoritmo Find para procurar todos os itens com a mesma chave
apenas do primeiro. No entanto, seria necessário pesquisar todas as células de
cada sequência linear que encontrou. Isso desperdiça tempo para todos os acessos à tabela,
mesmo
quando não há duplicatas envolvidas. Na maioria dos casos, você provavelmente quer proibir
duplicatas.

Agrupamento
Tente inserir mais itens na tabela de hash no miniaplicativo Hash Workshop. Como fica
mais cheios, os aglomerados crescem. O agrupamento pode resultar em comprimentos de sonda
muito longos.
Isso significa que o acesso às células no final da sequência é muito lento.
Quanto mais cheio o array estiver, pior será o agrupamento. Geralmente não é um problema
quando o array está meio cheio, e ainda não é tão ruim quando está dois terços cheio. Além
isso, no entanto, degrada seriamente o desempenho à medida que os clusters crescem cada vez
mais.
Por esse motivo, é fundamental ao projetar uma tabela de hash para garantir que ela nunca
fica mais da metade, ou no máximo dois terços, cheio. (Vamos discutir a matemática
relação ical entre o quão cheia a tabela de hash está e os comprimentos de sonda no final da
este capítulo.)
Código Java para uma tabela de hash de sonda linear
Não é difícil criar métodos para lidar com pesquisa, inserção e exclusão com linear
tabelas de hash de sonda. Mostraremos o código Java para esses métodos e, em seguida, um
programa hash.java que os coloca em contexto.
O método find()
O método find() primeiro chama hashFunc() para fazer o hash da chave de pesquisa para obter o
índice
número hashVal. O método hashFunc() aplica o operador % à chave de pesquisa e
o tamanho do array, como vimos antes.
Em seguida, em uma condição while, find() verifica se o item neste índice está vazio
(nulo). Caso contrário, verifica se o item contém a chave de pesquisa. Se o item fizer
contém a chave, find() retorna o item. Se não, find() incrementa hashVal e
volta ao início do loop while para verificar se a próxima célula está ocupada.
Aqui está o código para find():
public DataItem find(int key) // localiza o item com a chave
// (supõe que a tabela não está cheia)
{
int hashVal = hashFunc(chave); // hash a chave
while(hashArray[hashVal] != null) // até célula vazia,
{ // encontrou a chave?
if(hashArray[hashVal].getKey() == chave)
return hashArray[hashVal]; // sim, retorna item
++hashVal; // vai para a próxima célula
hashVal %= arraySize; // envolve se necessário
}
retornar nulo; // não pode encontrar o item
}

Here’s the code for find ():


public DataItem find(int key) // find item with key
// (assumes table not full)
{
int hashVal = hashFunc(key); // hash the key
while(hashArray[hashVal] != null) // until empty cell,
{ // found the key?
if(hashArray[hashVal].getKey() == key)
return hashArray[hashVal]; // yes, return item
++hashVal; // go to next cell
hashVal %= arraySize; // wrap around if necessary
}
return null; // can’t find item
}

À medida que o hashVal percorre o array, ele eventualmente chega ao fim. Quando isso
acontece, queremos que ele volte para o início. Podemos verificar isso com uma instrução if,
definindo hashVal como 0 sempre que igualar o tamanho da matriz. No entanto, nós pode fazer a
mesma coisa aplicando o operador % a hashVal e a matriz Tamanho.
Programadores cautelosos podem não querer assumir que a tabela não está cheia, como é feito
aqui. A tabela não deve ficar cheia, mas se isso acontecer, este método faria um loop para
sempre. Por simplicidade, não verificamos essa situação.
O método insert()
O método insert(), mostrado aqui, usa aproximadamente o mesmo algoritmo que find() para
localizar onde um item de dados deve ir. No entanto, ele está procurando por uma célula vazia ou
excluída item (chave –1), em vez de um item específico. Quando uma célula vazia for localizada,
insert() coloca o novo item nele.
public void insert(DataItem item) // insere um DataItem
// (supõe que a tabela não está cheia)
{
int chave = item.getKey(); //extrai chave
int hashVal = hashFunc(chave); // hash a chave
// até célula vazia ou -1,
while(hashArray[hashVal] != null &&
hashArray[hashVal].iData != -1)
{
++hashVal; // vai para a próxima célula
hashVal %= arraySize; // envolve se necessário
}
hashArray[hashVal] = item; //inserir item
} // fim da inserção()

O método delete()
O método delete() a seguir encontra um item existente usando um código semelhante ao find().
Quando o item é encontrado, delete() escreve sobre ele com o item de dados especial nonItem,
que é predefinido com uma chave de –1.

public DataItem delete(int key) // exclui um DataItem


{
int hashVal = hashFunc(chave); // hash a chave
while(hashArray[hashVal] != null) // até célula vazia,
{ // encontrou a chave?
if(hashArray[hashVal].getKey() == chave)
{
DataItem temp = hashArray[hashVal]; // salva o item
hashArray[hashVal] = nonItem; // apagar item
temperatura de retorno; // devolver item
}
++hashVal; // vai para a próxima célula
hashVal %= arraySize; // envolve se necessário
}
return null // não pode encontrar o item
} // fim da exclusão()

O programa hash.java
A Listagem 11.1 mostra o programa hash.java completo. Neste programa um DataItem
O objeto contém apenas um campo, um inteiro que é sua chave. Como em outras estruturas de
dados
discutimos, esses objetos podem conter mais dados ou uma referência a um objeto de
outra classe (como empregado ou partNumber).
O campo principal na classe HashTable é um array chamado hashArray. Outros campos são o
tamanho
da matriz e o objeto especial nonItem usado para exclusões.
LISTAGEM 11.1 O Programa hash.java
// hash.java
// demonstra a tabela de hash com sondagem linear
// para executar este programa: C:>java HashTableApp

import java.io.*;
///////////////////////////////////////////////// //////////////
class DataItem
{ // (poderia ter mais dados)
privado int iData; // item de dados (chave)
//------------------------------------------------ --------------
public DataItem(int ii) // construtor
{ iDados = ii; }
//------------------------------------------------ --------------
public int getKey()
{ return iData; }
//------------------------------------------------ --------------
} // fim da classe DataItem
///////////////////////////////////////////////// //////////////
classe HashTable
{
private DataItem[] hashArray; // array contém tabela de hash
private int arraySize;
private DataItem nonItem; // para itens deletados
// ------------------------------------------------ -------------
public HashTable(int size) // construtor
{
tamanho da matriz = tamanho;
hashArray = new DataItem[arraySize];
nonItem = new DataItem(-1); // chave de item deletado é -1
}
// ------------------------------------------------ -------------
public void displayTable()
{
System.out.print(“Tabela: “);
for(int j=0; j<arraySize; j++)
{
if(hashArray[j] != null)
System.out.print(hashArray[j].getKey() + “ “);
senão
System.out.print(“** “);
}
System.out.println(“”);
}
// ------------------------------------------------ -------------
public int hashFunc(int key)
{
chave de retorno % arraySize; //função hash
}
// ------------------------------------------------ -------------
public void insert(DataItem item) // insere um DataItem
// (supõe que a tabela não está cheia)
{
int chave = item.getKey(); //extrai chave
int hashVal = hashFunc(chave); // hash a chave
// até célula vazia ou -1,
while(hashArray[hashVal] != null &&
hashArray[hashVal].getKey() != -1)
{
++hashVal; // vai para a próxima célula
hashVal %= arraySize; // envolve se necessário
}
hashArray[hashVal] = item; //inserir item
} // fim da inserção()
// ------------------------------------------------ -------------
public DataItem delete(int key) // exclui um DataItem
{
int hashVal = hashFunc(chave); // hash a chave
while(hashArray[hashVal] != null) // até célula vazia,
{ // encontrou a chave?
if(hashArray[hashVal].getKey() == chave)
{
DataItem temp = hashArray[hashVal]; // salva o item
hashArray[hashVal] = nonItem; // apagar item
temperatura de retorno; // devolver item
}
++hashVal; // vai para a próxima célula
hashVal %= arraySize; // envolve se necessário
}
retornar nulo; // não pode encontrar o item
} // fim da exclusão()
// ------------------------------------------------ -------------
public DataItem find(int key) // localiza o item com a chave
{
int hashVal = hashFunc(chave); // hash a chave
while(hashArray[hashVal] != null) // até célula vazia,
{ // encontrou a chave?
if(hashArray[hashVal].getKey() == chave)
return hashArray[hashVal]; // sim, retorna item
++hashVal; // vai para a próxima célula
hashVal %= arraySize; // envolve se necessário
}
retornar nulo; // não pode encontrar o item
}
// ------------------------------------------------ -------------
} // fim da classe HashTable
///////////////////////////////////////////////// //////////////
class HashTableApp
{
public static void main(String[] args) hrows IOException{
DataItem aDataItem;
int aKey, size, n, keysPerCell;
System.out.print("Digite o tamanho da tabela de hash: ");
tamanho = getInt();
System.out.print(“Digite o número inicial de itens: “);
n = getInt();
chavesPerCell = 10;
//faz a tabela
HashTable theHashTable = new HashTable(tamanho);
for(int j=0; j<n; j++) // insere dados
{
aKey = (int)(java.lang.Math.random() *
keysPerCell * tamanho);
aDataItem = new DataItem(aKey);
theHashTable.insert(aDataItem);
}
while(true) // interage com o usuário
{
System.out.print(“Digite a primeira letra de”);
System.out.print(“mostrar, inserir, excluir ou localizar: “);
escolha do caractere = getChar();
alternar (escolha)
{
case 's':
theHashTable.displayTable();
break;
case 'i':
System.out.print(“Digite o valor da chave para inserir: “);
aChave = getInt();
aDataItem = new DataItem(aKey);
theHashTable.insert(aDataItem);
break;;
case 'd':
System.out.print("Digite o valor da chave para excluir: ");
aChave = getInt();
theHashTable.delete(aKey);
break;
case 'f':
System.out.print("Digite o valor da chave para localizar: ");
aChave = getInt();
aDataItem = theHashTable.find(aKey);
if(aDataItem != null)
{
System.out.println(“Encontrado “ + aKey);
}
senão
System.out.println(“Não foi possível encontrar “ + aKey);
parar;
predefinição:
System.out.print(“Entrada inválida\n”);
} // fim do interruptor
} // fim enquanto
} // fim de main()
//------------------------------------------------ --------------
public static String getString() hrows IOException
{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
retornar s;
}
//------------------------------------------------ --------------
public static char getChar() hrows IOException
{
String s = getString();
return s.charAt(0);
}
//------------------------------------------------ -------------
public static int getInt() hrows IOException
{
String s = getString();
return Integer.parseInt(s);
}
//------------------------------------------------ --------------
} // fim da classe HashTableApp
///////////////////////////////////////////////// //////////////
A rotina main() na classe HashTableApp contém uma interface de usuário que permite a
usuário para mostrar o conteúdo da tabela de hash (digite s), insira um item (i), exclua um
item (d), ou encontre um item (f).

Inicialmente, o programa solicita que o usuário insira o tamanho da tabela de hash e o


número de itens nele. Você pode fazer quase qualquer tamanho, de alguns itens a 10.000.
(Construir tabelas maiores do que isso pode levar um pouco de tempo.) Não use o s (para
mostrar)
opção em tabelas de mais de algumas centenas de itens; eles rolam para fora da tela e
exibi-los leva muito tempo.
Uma variável em main(), keysPerCell, especifica a proporção do intervalo de chaves para o
tamanho de
a matriz. Na listagem, é definido como 10. Isso significa que, se você especificar um tamanho de
tabela de
20, as teclas variam de 0 a 200.
Se você quiser ver o que está acontecendo, é melhor criar tabelas com menos de 20
itens para que todos os itens possam ser exibidos em uma linha. Aqui estão alguns exemplos de
interação
ção com hash.java:
Digite o tamanho da tabela de hash: 12
Digite o número inicial de itens: 8
Digite a primeira letra de mostrar, inserir, excluir ou localizar: s
Tabela: 108 13 0 ** ** 113 5 66 ** 117 ** 47
Digite a primeira letra de mostrar, inserir, excluir ou localizar: f
Digite o valor da chave para encontrar: 66
Encontrado 66
Digite a primeira letra de mostrar, inserir, excluir ou localizar: i
Digite o valor da chave para inserir: 100
Digite a primeira letra de mostrar, inserir, excluir ou localizar: s
Tabela: 108 13 0 ** 100 113 5 66 ** 117 ** 47
Digite a primeira letra de mostrar, inserir, excluir ou localizar: d
Insira o valor da chave para excluir: 100
Digite a primeira letra de mostrar, inserir, excluir ou localizar: s
Tabela: 108 13 0 ** -1 113 5 66 ** 117 ** 47
Os valores de chave vão de 0 a 119 (12 vezes 10, menos 1). O símbolo ** indica que um
célula está vazia. O item com a chave 100 é inserido no local 4 (o primeiro item é
numerado 0) porque 100%12 é 4. Observe como 100 muda para –1 quando este item é
excluído.
Expandindo a Matriz
Uma opção quando uma tabela de hash fica muito cheia é expandir sua matriz. Em Java, matrizes
têm um tamanho fixo e não podem ser expandidos. Seu programa deve criar um novo e maior
array e, em seguida, insira o conteúdo do array pequeno antigo no novo grande.
Lembre-se de que a função hash calcula a localização de um determinado item de dados com
base
no tamanho da matriz, para que os itens não sejam localizados no mesmo lugar na matriz grande
como
eles estavam na pequena matriz. Você não pode, portanto, simplesmente copiar os itens de um
matriz para a outra. Você precisará percorrer a matriz antiga em sequência, célula por célula,
inserindo cada item encontrado no novo array com o método insert(). Isto é
chamado rehashing. É um processo demorado, mas necessário para que a matriz seja
expandido.
A matriz expandida geralmente é feita com o dobro do tamanho da matriz original. Na realidade,
como o tamanho do array deve ser um número primo, o novo array precisará ser um pouco
mais de duas vezes maior. Calcular o novo tamanho do array faz parte do rehashing
processo.
Aqui estão algumas rotinas para ajudar a encontrar o novo tamanho do array (ou o tamanho
original do array, se
você não confia no usuário para escolher um número primo, o que geralmente é o caso). Você
começa
fora com o tamanho especificado e, em seguida, procure o próximo primo maior que isso. o
getPrime() obtém o próximo primo maior que seu argumento. Ele chama isPrime() para
verifique cada um dos números acima do tamanho especificado.
private int getPrime(int min) // retorna 1º primo > min
{
for(int j = min+1; true; j++) // para todo j > min
if( isPrime(j) ) // j é primo?
return j; // sim, devolve
}
// ------------------------------------------------ -------------
private boolean isPrime(int n) // é n primo?
{
for(int j=2; (j*j <= n); j++) // para todos os j
if( n % j == 0) // divide uniformemente por j?
return false; // sim, então não é primo
return true; // não, então primo
}
Essas rotinas não são o máximo em sofisticação. Por exemplo, em getPrime() você
poderia verificar 2 e depois números ímpares a partir de então, em vez de todos os números.
No entanto, esses refinamentos não importam muito porque você geralmente encontra um primo
após
verificando apenas alguns números.
Java oferece uma classe Vector que é uma estrutura de dados do tipo array que pode ser
expandida.
No entanto, não ajuda muito devido à necessidade de refazer todos os itens de dados quando o
tabela muda de tamanho.

Sondagem Quadrática
Vimos que os clusters podem ocorrer na abordagem de sonda linear para endereçamento aberto.
Uma vez que um aglomerado se forma, ele tende a crescer. Itens que têm hash para qualquer
valor n intervalo do cluster irá avançar e inserir-se no final do cluster, tornando-o ainda maior.
Quanto maior o cluster fica, mais rápido ele cresce.
É como a multidão que se reúne quando alguém desmaia no shopping. O primeiro as chegadas
vêm porque viram a vítima cair; as chegadas posteriores se reúnem porque se perguntou o que
todo mundo estava olhando. Quanto maior a multidão cresce, mais as pessoas são atraídas por
isso.
A razão entre o número de itens em uma tabela e o tamanho da tabela é chamada de fator de
carga.
Uma tabela com 10.000 células e 6.667 itens tem um fator de carga de 2/3.
loadFactor = nItems / arraySize;
Os clusters podem se formar mesmo quando o fator de carga não é alto. Partes da tabela de hash
podem consistem em grandes aglomerados, enquanto outros são escassamente habitados. Os
clusters reduzem o desempenho mance.
A sondagem quadrática é uma tentativa de impedir a formação de clusters. A ideia é sondar
células mais amplamente separadas, em vez daquelas adjacentes ao site de hash primário.
O Passo é o Quadrado do Número do Passo
Em uma sonda linear, se o índice de hash primário for x, as sondas subsequentes vão para x+1,
x+2, x+3 e assim por diante. Na sondagem quadrática, as sondas vão para x+1, x+4, x+9, x+16,
x+25, e assim sobre. A distância da sonda inicial é o quadrado do número do passo: x+12 , x+22,
x+32, x+42, x+52, e assim por diante.
A Figura 11.8 mostra algumas sondas quadráticas.
É como se uma sonda quadrática se tornasse cada vez mais desesperada à medida que sua busca
se alongava. No primeiro ele calmamente escolhe a célula adjacente. Se estiver ocupado, ele
pensa que pode estar em um pequeno cluster, então ele tenta algo a 4 células de distância. Se
estiver ocupado, torna-se um pouco preocupado, pensando que pode estar em um cluster maior, e
tenta a 9 células de distância. Se isso ocorrer pied, ele sente os primeiros toques de pânico e salta
16 células de distância. Muito em breve, está voando histericamente em todo o lugar, como você
pode ver se você tentar pesquisar com o Miniaplicativo HashDouble Workshop quando a mesa
está quase cheia.
O miniaplicativo HashDouble com sondas quadráticas
O miniaplicativo HashDouble Workshop permite dois tipos diferentes de tratamento de colisões:
sondas quadráticas e hash duplo. (Veremos o hash duplo na próxima seção.) Este applet gera
uma exibição muito parecida com a do Hash Workshop applet, exceto que inclui botões de opção
para selecionar sondagem quadrática ou dupla hash.
O problema com sondas quadráticas
As sondas quadráticas eliminam o problema de agrupamento que vimos com a sonda linear, que
é chamado de agrupamento primário. No entanto, sondas quadráticas sofrem de uma diferença e
problema de agrupamento mais sutil. Isso ocorre porque todas as chaves com hash para um
determinada célula seguem a mesma sequência na tentativa de encontrar um espaço vago.

Digamos que 184, 302, 420 e 544 todos tenham hash para 7 e sejam inseridos nesta ordem. Então
302 exigirá uma sonda de uma etapa, 420 exigirá uma sonda de quatro etapas e 544 exigirá uma
sonda de nove passos. Cada item adicional com uma chave com hash para 7 exigirá um sonda
mais longa. Esse fenômeno é chamado de agrupamento secundário.

O agrupamento secundário não é um problema sério, mas a sondagem quadrática geralmente não
é usado porque há uma solução um pouco melhor.

Hash duplo

Para eliminar o agrupamento secundário, bem como o agrupamento primário, podemos usar
outro abordagem: hash duplo. O agrupamento secundário ocorre porque o algoritmo que gera a
sequência de passos na sonda quadrática sempre gera o mesmo passos: 1, 4, 9, 16 e assim por
diante.

O que precisamos é de uma maneira de gerar sequências de sondagem que dependam da chave
de ser o mesmo para todas as teclas. Em seguida, números com chaves diferentes que hash para o
mesmo índice usará sequências de sonda diferentes.

A solução é fazer o hash da chave uma segunda vez, usando uma função de hash diferente e use
o resultado como o tamanho do passo. Para uma determinada chave, o tamanho do passo
permanece constante uma sonda, mas é diferente para chaves diferentes.

A experiência mostrou que esta função hash secundária deve ter certas características:

• Não deve ser igual à função hash primária.

• Ele nunca deve emitir um 0 (caso contrário, não haveria etapa; cada sonda seria pousaria na
mesma célula e o algoritmo entraria em um loop infinito).
Especialistas descobriram que as funções da seguinte forma funcionam bem:

stepSize = constante - (chave % constante);

onde constante é primo e menor que o tamanho da matriz. Por exemplo,

stepSize = 5 - (chave % 5);

Esta é a função de hash secundária usada no miniaplicativo HashDouble Workshop.

Chaves diferentes podem ter hash para o mesmo índice, mas elas (provavelmente) gerarão
diferentes tamanhos de passos. Com esta função de hash, os tamanhos dos passos estão todos no
intervalo de 1 a 5.

Isso é mostrado na Figura 11.9

O miniaplicativo HashDouble com hash duplo

Você pode usar o miniaplicativo HashDouble Workshop para ver como funciona o hash duplo.
Isto

inicia automaticamente no modo de hash duplo, mas se estiver no modo quadrático, você

pode mudar para Double criando uma nova tabela com o botão New e clicando no

Botão duplo quando solicitado. Para ver melhor as sondas no trabalho, você precisará preencher
o

mesa bastante cheia, digamos a cerca de nove décimos de capacidade ou mais. Mesmo com tão
alto

fatores de carga, a maioria dos itens de dados será encontrada imediatamente pela primeira
função de hash;

apenas alguns exigirão sequências de sonda estendidas.

Tente encontrar as chaves existentes. Quando alguém precisa de uma sequência de sonda, você
verá como todos os

etapas são do mesmo tamanho para uma determinada chave, mas que o tamanho da etapa é
diferente - entre 1
e 5—para teclas diferentes.

The hashDouble.java Program

// hashDouble.java

// demonstrates hash table with double hashing

// to run this program: C:>java HashDoubleApp

import java.io.*;

////////////////////////////////////////////////////////////////

class DataItem

{ // (could have more items)

private int iData; // data item (key)

//--------------------------------------------------------------

public DataItem(int ii) // constructor

{ iData = ii; }

//--------------------------------------------------------------

public int getKey()

{ return iData; }

//--------------------------------------------------------------

} // end class DataItem

////////////////////////////////////////////////////////////////

class HashTable

private DataItem[] hashArray; // array is the hash table


private int arraySize;

private DataItem nonItem; // for deleted items

// -------------------------------------------------------------

HashTable(int size) // constructor

arraySize = size;

hashArray = new DataItem[arraySize];

nonItem = new DataItem(-1);

// -------------------------------------------------------------

public void displayTable()

System.out.print(“Table: “);

for(int j=0; j<arraySize; j++)

if(hashArray[j] != null)

System.out.print(hashArray[j].getKey()+ “ “);

else

System.out.print(“** “);

System.out.println(“”);

// -------------------------------------------------------------
public int hashFunc1(int key)

return key % arraySize;

// -------------------------------------------------------------

public int hashFunc2(int key)

// non-zero, less than array size, different from hF1

// array size must be relatively prime to 5, 4, 3, and 2

return 5 - key % 5;

// -------------------------------------------------------------

// insert a DataItem

public void insert(int key, DataItem item)

// (assumes table not full)

int hashVal = hashFunc1(key); // hash the key

int stepSize = hashFunc2(key); // get step size

// until empty cell or -1

while(hashArray[hashVal] != null &&

hashArray[hashVal].getKey() != -1)

hashVal += stepSize; // add the step


hashVal %= arraySize; // for wraparound

hashArray[hashVal] = item; // insert item

} // end insert()

// -------------------------------------------------------------

public DataItem delete(int key) // delete a DataItem

int hashVal = hashFunc1(key); // hash the key

int stepSize = hashFunc2(key); // get step size

while(hashArray[hashVal] != null) // until empty cell,

{ // is correct hashVal?

if(hashArray[hashVal].getKey() == key)

DataItem temp = hashArray[hashVal]; // save item

hashArray[hashVal] = nonItem; // delete item

return temp; // return item

hashVal += stepSize; // add the step

hashVal %= arraySize; // for wraparound

return null; // can’t find item

} // end delete()

// -------------------------------------------------------------
public DataItem find(int key) // find item with key

// (assumes table not full)

int hashVal = hashFunc1(key); // hash the key

int stepSize = hashFunc2(key); // get step size

while(hashArray[hashVal] != null) // until empty cell,

{ // is correct hashVal?

if(hashArray[hashVal].getKey() == key)

return hashArray[hashVal]; // yes, return item

hashVal += stepSize; // add the step

hashVal %= arraySize; // for wraparound

return null; // can’t find item

// -------------------------------------------------------------

} // end class HashTable

////////////////////////////////////////////////////////////////

class HashDoubleApp

public static void main(String[] args) throws IOException

int aKey;

DataItem aDataItem;
int size, n;

// get sizes

System.out.print(“Enter size of hash table: “);

size = getInt();

System.out.print(“Enter initial number of items: “);

n = getInt();

// make table

HashTable theHashTable = new HashTable(size);

for(int j=0; j<n; j++) // insert data

aKey = (int)(java.lang.Math.random() * 2 * size);

aDataItem = new DataItem(aKey);

theHashTable.insert(aKey, aDataItem);

while(true) // interact with user

System.out.print(“Enter first letter of “);

System.out.print(“show, insert, delete, or find: “);

char choice = getChar();

switch(choice)

case ‘s’:

theHashTable.displayTable();
break;

case ‘i’:

System.out.print(“Enter key value to insert: “);

aKey = getInt();

aDataItem = new DataItem(aKey);

theHashTable.insert(aKey, aDataItem);

break;

case ‘d’:

System.out.print(“Enter key value to delete: “);

aKey = getInt();

theHashTable.delete(aKey);

break;

case ‘f’:

System.out.print(“Enter key value to find: “);

aKey = getInt();

aDataItem = theHashTable.find(aKey);

if(aDataItem != null)

System.out.println(“Found “ + aKey);

else

System.out.println(“Could not find “ + aKey);

break;

default:

System.out.print(“Invalid entry\n”);
} // end switch

} // end while

} // end main()

//--------------------------------------------------------------

public static String getString() throws IOException

InputStreamReader isr = new InputStreamReader(System.in);

BufferedReader br = new BufferedReader(isr);

String s = br.readLine();

return s;

//--------------------------------------------------------------

public static char getChar() throws IOException

String s = getString();

return s.charAt(0);

//-------------------------------------------------------------

public static int getInt() throws IOException

String s = getString();

return Integer.parseInt(s);

}
//--------------------------------------------------------------

} // end class HashDoubleApp

//////////////////////////////////////////////////////////

As primeiras 15 teclas geralmente são hash para uma célula vazia (a 10ª é uma anomalia).
Depois

que, à medida que a matriz fica mais cheia, as sequências de sonda se tornam bastante longas.
Aqui está o

matriz resultante de chaves:

** 1 24 3 15 5 25 30 31 16 10 11 12 1 37 38 16 36 18 19 20 ** 41

Tamanho da tabela um número primo

O hash duplo requer que o tamanho da tabela de hash seja um número primo. Ver

ora, imagine uma situação em que o tamanho da tabela não seja um número primo. Por exemplo,

suponha que o tamanho da matriz seja 15 (índices de 0 a 14) e que uma chave específica tenha
hashes para

um índice inicial de 0 e um tamanho de etapa de 5. A sequência da sonda será 0, 5, 10, 0, 5,

10, e assim por diante, repetindo sem parar. Apenas essas três células são examinadas, então o

algoritmo nunca encontrará as células vazias que podem estar esperando em 1, 2, 3 e assim por
diante.

O algoritmo irá travar e queimar.

Se o tamanho do array fosse 13, que é primo, a sequência de sondas eventualmente visitará

cada célula. É 0, 5, 10, 2, 7, 12, 4, 9, 1, 6, 11, 3 e assim por diante. Se houver mesmo

uma célula vazia, a sonda a encontrará. Usando um número primo como o tamanho do array faz

é impossível para qualquer número dividi-lo uniformemente, então a sequência da sonda acabará

verifique todas as células.


Abrir Endereçamento 551

TABELA 11.1 Continuação

Hash do item

Células do tamanho da etapa do valor da chave numérica na sequência de sondagem

12 0672324539 CH11 10/10/02 09:17 Página 551

Um efeito semelhante ocorre usando a sonda quadrática. Nesse caso, no entanto, o tamanho do
passo

fica maior a cada passo e acabará por transbordar a variável que a contém, assim

evitando um loop infinito.

Em geral, o hash duplo é a sequência de teste escolhida quando o endereçamento aberto é

usado.

Encadeamento Separado

No endereçamento aberto, as colisões são resolvidas procurando uma célula aberta no hash
tabela. Uma abordagem diferente é instalar uma lista vinculada em cada índice na tabela de hash.
UMA chave do item de dados é hash para o índice da maneira usual, e o item é inserido em a
lista vinculada nesse índice. Outros itens com hash para o mesmo índice são simplesmente
adicionados à lista vinculada; não há necessidade de procurar células vazias no primário
variedade

. A Figura 11.10 mostra a aparência do encadeamento separado.

Eficiência de hash

Observamos que a inserção e a pesquisa em tabelas de hash podem se aproximar do tempo O(1).
Se nenhuma colisão ocorre, apenas uma chamada para a função hash e uma única referência de
matriz são necessário inserir um novo item ou localizar um item existente. Este é o acesso
mínimo
Tempo. Se ocorrerem colisões, os tempos de acesso se tornarão dependentes dos comprimentos
de sonda resultantes.

Cada célula acessada durante uma sonda adiciona outro incremento de tempo à busca por uma
célula vazia (para inserção) ou para uma célula existente. Durante um acesso, uma célula deve
ser verificada para ver se está vazio e - no caso de pesquisa ou exclusão -se contém o item
desejado.

Assim, um tempo de busca ou inserção individual é proporcional ao comprimento da sonda. Isso


é além de um tempo constante para a função hash.

O comprimento médio da sonda (e, portanto, o tempo médio de acesso) depende o fator de carga
(a proporção de itens na tabela para o tamanho da tabela). Como a carga fator aumenta, os
comprimentos das sondas crescem mais.

Veremos a relação entre comprimentos de sonda e fatores de carga para os vários tipos de tabelas
de hash que estudamos.

Abrir Endereçamento

A perda de eficiência com fatores de carga elevados é mais grave para os vários esquemas de
endereçamento do que para encadeamento separado.

No endereçamento aberto, as pesquisas malsucedidas geralmente demoram mais do que as bem-


sucedidas pesquisas. Durante uma sequência de sondagem, o algoritmo pode parar assim que
encontrar o item desejado, que está, em média, na metade da sequência da sonda. No

Por outro lado, deve percorrer todo o caminho até o final da sequência antes de ter certeza de que
não pode encontrar um item.

Sondagem Linear

As equações a seguir mostram a relação entre o comprimento da sonda (P) e a carga fator (L)
para sondagem linear. Para uma pesquisa bem-sucedida é P = (1 + 1 / (1 – L)2) / 2 e para uma
busca malsucedida é P = ( 1 + 1 / (1 – L) ) / 2.

Estas fórmulas são de Knuth (ver Apêndice B, “Leituras Adicionais”), e sua derivação é bastante
complicada. A Figura 11.12 mostra o resultado do gráfico desses
equações.

Você também pode gostar