8. COMPARATIVE STUDY OF ORMEN LANGE LNG PROJECT
8.2 OVERVIEW OF THE ORMEN-LANGE LNG PROJECT
Um dos tipos de tabelas hash mais populares, dada sua simplicidade, são as tabelas com endereçamento aberto (open addressing). Neste tipo de tabela, as chaves são diretamente armazenadas nas posições da tabela correspondentes ao seu hash (ao contrário do método de encadeamento - chainning - que veremos mais adiante). Quando uma determinada chave precisa ser armazenada na tabela e o hash gerado é um endereço que já está ocupado, a entrada e armazenada em posições alternativas da tabela. Caso a posição alternativa também não esteja disponível, procura-se uma outra até que encontre-se uma posição vaga. Este processo é
denominado amostragem (probing), onde as possíveis posições de armazenamento são testadas uma a uma. B 0 1 E 2 3 A 4 D 5 6 C 7
Figura 7.4.1 - Tabela hash com endereçamento aberto
7.4.1. Tratamento de colisões em endereço aberto
Muitas das técnicas para tratamento das colisões em tabelas hash com endereçamento aberto são bem conhecidas e definidas, tais como as abordadas em [11]. Para realizar a procura em uma tabela hash que foi preenchida através do uso do processo de amostragem, a mesma rota deve ser seguida até que se encontre a chave desejada ou uma posição vazia. Tenhamos o exemplo da figura 7.4.1.1 onde três chaves quaisquer geraram o mesmo hash e, por conseqüência, deveriam ser armazenados na mesma posição, que simbolizamos por “n”.
Posição ... ... n-1 k3 n k1 n+1 k2 ...
h(K)
h(B) = 0 h(C) = 7 h(A) = 4 h(k1) = h(k2) = h(k3)A chave k1, a primeira das três a ser armazenada, foi realmente colocada na posição “n”.
Em seguida, a chave k2 também gerou o mesmo hash. Porém, como a posição já estava ocupada, foi armazenada na primeira posição acima (n+1). Da mesma forma, k3 gerou o mesmo hash e
teve de ser armazenado na primeira posição abaixo de “n” uma vez que “n+1” também já estava ocupada.
Para realizar a procura na tabela, devemos seguir o mesmo caminho utilizado na armazenagem das chaves. Desta forma, para procurar a entrada k3, precisamos consultar a
posição “n”, depois “n+1”, “n-1” e assim sucessivamente até que a entrada seja encontrada ou que uma posição vazia o seja, indicando que a chave procurada não está mais na tabela.
É importante notar que quando uma entrada da tabela é apagada, a posição não pode ser marcada como vazia, mas sim como “apagada”. Suponhamos que a entrada k1 seja apagada. Se a posição “n” for marcada como vazia, então as entradas k3 e k2 nunca mais serão encontradas na
tabela, pois como vimos acima, a procura por uma entrada termina quando uma posição vazia ou a própria entrada são encontradas. Desta forma, a posição deve ser marcada como “apagada” sinalizando para o mecanismo de escrita que a mesma pose ser utilizada novamente a ao mesmo tempo sinalizando para o mecanismo de procura que ainda podem existir chaves mais adiante.
Neste tipo de técnica, endereço aberto, podemos ver que o custo para procurar uma determinada chave na tabela é igual ao custo que foi para inseri-la, não importando o fator de ocupação da tabela. Mais adiante, iremos ver que com o emprego de técnicas mais sofisticadas, o custo de procura por uma determinada chave pode diminuir à medida em que o fator de ocupação da tabela diminui.
Em uma tabela do tipo endereçamento aberto, a questão da utilização de memória é bem definida. Como os elementos são diretamente armazenados nas posições da tabela, o número máximo de elementos armazenados é igual ao número de posições. Em sistemas onde o uso de memória é crítico e alocação dinâmica de memória é uma tarefa dispendiosa, este tipo de abordagem é interessante. Veremos a mais adiante, através do mecanismo de encadeamento, como é possível que uma tabela possua mais elementos do que posições disponíveis.
Amostragem linear
No exemplo acima, fixamos que o caminho a ser seguido caso a posição desejada já estivesse ocupada seguia uma seqüência linear (n+1, n-1, n+2, n-2, ...). Este tipo de amostragem é denominado “linear”, descrito pela expressão a seguir, que usa o método da divisão, visto no capítulo anterior.
h(k) = (h1(k)+i) mod m para i=0, 1, ...., m-1
O maior problema desta técnica é que quando colisões ocorrem grandes espaços da tabela com posições ocupadas podem ser criados. Esse fenômeno é denominado de aglomeração (clustering). Isso, à medida em que o fator de uso da tabela cresce, faz com que a performance da mesma se aproxime de uma busca seqüencial.
Para amenizar este problema, existem duas outras maneiras bastante conhecidas de realizar o amostragem , são elas: o método quadrático e o duplo hash.
Amostragem quadrática
No método quadrático, o intervalo entre as consultas, que antes era linear, agora é incrementado linearmente entre elas, ou seja, os índices são descritos por uma função quadrática, conforme abaixo:
h(k) = (h1(k)+i2) mod m para i = 0, 1,2, 3, ..., m-1
Mesmo com o método quadrático, caso h(k1) == h(k2), as seqüências para procura das posições de k1 e k2 são exatamente as mesmas, o que irá gerar a aglomerações, porém os mesmos estarão mais distribuídos pela tabela. Estas aglomerações são denominadas aglomerações secundárias.
Método do quociente quadrático
De forma a eliminar o efeito das aglomerações secundárias, Bell propôs uma modificação no método quadrático [16]. Bell propôs que o método quadrático, outrora proposto por Maurer
casos em que o hash de chaves distintas colidem entre si.. Denominamos de função de deslocamento a parcela que é somada ao hash da chave propriamente dito quando existe uma colisão. Por exemplo, no caso específico do método quadrático proposto em [57] temos:
hi(K) = h0(K) + a×i + b×i2
Neste caso, a função de deslocamento é a×i + b×i2, onde a e b são constantes, o que torna
a função constante para o mesmo indexador i e chaves diferentes. O que Bell propôs foi uma modificação no método obtendo a seguinte função de hash:
hi(K) = h0(K) + a×i + b(K)×i2
De forma a não degradar o desempenho do método, a função b(K) deve ser rapidamente calculada. Quando o hash inicial é obtido pelo método da divisão, ou seja, h0(K) = K mod N (onde N é o tamanho da tabela) temos o quociente da divisão disponível sem custo adicional, podendo ser utilizado como a função b(K), parte da função de deslocamento.
Duplo hash
Para eliminar de vez o problema da aglomeração, o método de duplo hash pode ser empregado. Neste método, a próxima posição a ser consultada é calculada por uma segunda função de hash, como é mostrado abaixo:
h(k) = (h1(k)+ i h2(k)) mod m
Sempre existe a possibilidade de que duas chaves k1 e k2 gerem o mesmo hash. Porém, dado que h1(k1) == h2(k2), a probabilidade de que h2(k1) == h2(k2) é muito menor. Em outras palavras, o método de duplo hash essencialmente elimina a possibilidade de duas chaves que colidem em uma posição tenham a mesma seqüência de amostragem. Para que isso seja verdade, uma boa função de hash deve ser escolhida. Isso pode depender do tipo de aplicação que está se desenvolvendo e quais os tipos esperados das entradas.
Com o suporte de módulos de hardware, o método de hash duplo pode se tornar muito interessante, dada a possibilidade de executar o hash h1(k) e h2(k) paralelamente no hardware e só
utilizar o segundo hash se necessário. Em aplicações puramente de software, o método torna-se dispendioso devido à execução das duas funções de hash ser feita serialmente e não paralelamente.