Hash Tables
Hash Tables
Olá, sejam bem-vindos a mais uma aula do curso de Estruturas de Dados. Nessa aula, vamos falar sobre hash tables.
Mas eu gostaria de seguir uma outra abordagem. Vamos começar com um problema e, a partir dele, vamos entender o que são hash tables e como elas funcionam.
Problema
O problema é bem simples. Queremos implementar um algoritmo que encontra, em uma string, o primeiro caractere que se repete. Por exemplo, na string “abacabad”, o primeiro caractere que se repete é o caractere “a”.
NOTA: Para esse problema, vamos considerar apenas caracteres ASCII, com letras minúsculas e sem acentos.
Podemos resolver esse problema com as estruturas que já vimos até aqui, com certeza.
Usando arrays podemos fazer:
def first_repeated_char(string)
chars = Array.new(26, 0)
string.each_char do |char|
index = char.ord
return char if chars[index] == 1
chars[index] += 1
end
end
A implementação acima resolve o problema, mas não é muito eficiente em termos de espaço. Veja para um exemplo de uma string pequena o que acontece:
# TODO: Implementar
Perceba que usar o código do ascii como índice, parece, mas não é uma boa ideia. Sabemos que nosso alfabeto tem 26 letrar mas nosso array já tem mais de 6 posições. Isso é um desperdício de espaço.
Além do que, isso só é possível em linguagens como Ruby que permitem arrays de tamanho variável - e essas realocações têm seu custo como bem sabemos da aula de arrays. Em linguagens como C, por exemplo, isso não seria possível.
Bom, alocamos um Array de 26 posições e não precisamos de mais do que isso. Nosso problema agora é achar uma forma de mapear os códigos ASCII das letras para índices de 0 a 25.
Vamos criar uma função que recebe um caracter e retorna um índice de 0 a 25. Chamaremos essa função de hash.
def hash(char)
char.ord % 97 # 'a'.ord == 97
end
Assim, substituímos o index = char.ord por index = hash(char) temos:
def first_repeated_char(string)
chars = Array.new(26, 0)
string.each_char do |char|
index = hash(char)
return char if chars[index] == 1
chars[index] = 1
end
nil
end
Função de Hash
Em nosso caso: A função de hash é uma função que recebe uma chave e retorna um índice na tabela. No nosso caso, a chave é um caractere e o índice é um número de 0 a 25.
Em geral: A função hash é uma função que recebe um input arbitrário e retorna uma sequência fixa de bytes dentro de um espaço determinado.
Propriedades de uma Função de Hash
- Determinística: A mesma entrada sempre produz a mesma saída.
- Rápida: A função de hash deve ser rápida de calcular.
- Uniforme: A função de hash deve distribuir as chaves uniformemente pelo espaço de índices.
- Eficiente: A função de hash deve ser eficiente em termos de espaço.
- Resistente a Colisões: A função de hash deve minimizar colisões.
Hash Tables
Uma hash table é uma estrutura de dados que mapeia chaves a valores. É uma estrutura de dados que permite a busca, inserção e remoção de elementos em tempo constante*. As hash tables recebem esse nome por utilizarem uma função de hash para mapear chaves a valores.
* No pior caso, a busca, inserção e remoção em uma hash table é O(n), mas, em média, é O(1).
Em nosso exemplo fizemos bem isso. Mapeamos chave (código ASCII de um caractere) a valor (1 ou 0 para indicar se aquele caractere já existia).
O Hash vem da Função de Hash, mas o Table vem de onde?
Naturalmente espera-se que tenhamos uma tabela em algum momento. E temos. A tabela é um array. Cada índice do array é um bucket. Cada bucket é uma lista encadeada de pares chave-valor.
Em nosso exemplo anterior é como se tívemos uma tabela com uma única coluna apenas.
[mostrar no excel como uma hash table é fazendo as clunas serem os buckets e as linhas serem os elementos]
ADT Hash Table
build(X): Cria uma nova hash table.insert(key, value): Insere um novo par chave-valor na hash table.delete(key): Remove um par chave-valor da hash table.search(key): Busca um valor a partir de uma chave.
Implementando uma Hash Table
class HashTable
def initialize
@table = Array.new(1000)
end
def insert(key, value)
index = hash(key)
@table[index] = value
end
def delete(key)
index = hash(key)
@table[index] = nil
end
def search(key)
index = hash(key)
@table[index]
end
private
def hash(key)
key.hash % 1000
end
end
Casos de Uso
- Dicionários: Hash tables são muito utilizadas para implementar dicionários.
- Cache: Hash tables são muito utilizadas para implementar caches.
- Conjuntos: Hash tables são muito utilizadas para implementar conjuntos.
- Contagem de Elementos: Hash tables são muito utilizadas para contar elementos.
- Indexação: Hash tables são muito utilizadas para indexar elementos.
- …
Implementando Set com Hash Table
class Set
def initialize
@table = HashTable.new
end
def add(value)
@table.insert(value, true)
end
def delete(value)
@table.delete(value)
end
def include?(value)
@table.search(value) != nil
end
end
Tempos de execução:
add: O(1)delete: O(1)include?: O(1)