logo

Hashing na estrutura de dados

Introdução ao hash na estrutura de dados:

Hashing é uma técnica popular na ciência da computação que envolve o mapeamento de grandes conjuntos de dados para valores de comprimento fixo. É um processo de conversão de um conjunto de dados de tamanho variável em um conjunto de dados de tamanho fixo. A capacidade de realizar operações de pesquisa eficientes torna o hash um conceito essencial em estruturas de dados.

O que é hash?

Um algoritmo de hash é usado para converter uma entrada (como uma string ou número inteiro) em uma saída de tamanho fixo (referida como código hash ou valor hash). Os dados são então armazenados e recuperados usando esse valor hash como um índice em uma matriz ou tabela hash. A função hash deve ser determinística, o que garante que sempre produzirá o mesmo resultado para uma determinada entrada.

mb x gb

Hashing é comumente usado para criar um identificador exclusivo para um dado, que pode ser usado para pesquisar rapidamente esses dados em um grande conjunto de dados. Por exemplo, um navegador da web pode usar hashing para armazenar senhas de sites com segurança. Quando um usuário insere sua senha, o navegador a converte em um valor hash e a compara com o valor hash armazenado para autenticar o usuário.

O que é uma chave hash?

No contexto do hash, uma chave hash (também conhecida como valor hash ou código hash) é uma representação numérica ou alfanumérica de tamanho fixo gerada por um algoritmo de hash. Ele é derivado dos dados de entrada, como uma sequência de texto ou um arquivo, por meio de um processo conhecido como hash.

Hashing envolve a aplicação de uma função matemática específica aos dados de entrada, que produz uma chave hash exclusiva que normalmente tem comprimento fixo, independentemente do tamanho da entrada. A chave hash resultante é essencialmente uma impressão digital dos dados originais.

A chave hash serve a vários propósitos. É comumente usado para verificações de integridade de dados, pois mesmo uma pequena alteração nos dados de entrada produzirá uma chave hash significativamente diferente. As chaves hash também são usadas para recuperação e armazenamento eficiente de dados em tabelas hash ou estruturas de dados, pois permitem operações rápidas de pesquisa e comparação.

Como funciona o hash?

O processo de hashing pode ser dividido em três etapas:

  • Entrada: Os dados a serem hash são inseridos no algoritmo de hash.
  • Função Hash: O algoritmo hash pega os dados de entrada e aplica uma função matemática para gerar um valor hash de tamanho fixo. A função hash deve ser projetada de forma que diferentes valores de entrada produzam diferentes valores de hash e pequenas alterações na entrada produzam grandes alterações na saída.
  • Saída: O valor hash é retornado, que é usado como um índice para armazenar ou recuperar dados em uma estrutura de dados.

Algoritmos de hash:

Existem vários algoritmos de hash, cada um com vantagens e desvantagens distintas. Os algoritmos mais populares incluem o seguinte:

conjunto js
  • MD5: Um algoritmo de hash amplamente utilizado que produz um valor de hash de 128 bits.
  • SHA-1: Um algoritmo de hash popular que produz um valor de hash de 160 bits.
  • SHA-256: Um algoritmo de hash mais seguro que produz um valor de hash de 256 bits.
Hashing na estrutura de dados

Função hash:

Função Hash: Uma função hash é um tipo de operação matemática que recebe uma entrada (ou chave) e gera um resultado de tamanho fixo conhecido como código hash ou valor hash. A função hash deve sempre produzir o mesmo código hash para a mesma entrada para ser determinística. Além disso, a função hash deve produzir um código hash exclusivo para cada entrada, que é conhecido como propriedade hash.

Existem diferentes tipos de funções hash, incluindo:

    Método de divisão:

Este método envolve dividir a chave pelo tamanho da tabela e considerar o restante como valor hash. Por exemplo, se o tamanho da tabela for 10 e a chave for 23, o valor do hash seria 3 (23% 10 = 3).

    Método de multiplicação:

Este método envolve multiplicar a chave por uma constante e tomar a parte fracionária do produto como o valor hash. Por exemplo, se a chave for 23 e a constante for 0,618, o valor do hash seria 2 (floor(10*(0,61823 - floor(0,61823))) = floor(2,236) = 2).

    Hash universal:

Este método envolve o uso de uma função hash aleatória de uma família de funções hash. Isso garante que a função hash não seja influenciada por nenhuma entrada específica e seja resistente a ataques.

Resolução de colisão

Um dos principais desafios do hashing é lidar com colisões, que ocorrem quando dois ou mais valores de entrada produzem o mesmo valor de hash. Existem várias técnicas usadas para resolver colisões, incluindo:

  • Encadeamento: Nesta técnica, cada slot da tabela hash contém uma lista vinculada de todos os valores que possuem o mesmo valor hash. Essa técnica é simples e fácil de implementar, mas pode levar a um desempenho ruim quando as listas vinculadas ficam muito longas.
  • Endereçamento aberto: Nesta técnica, quando ocorre uma colisão, o algoritmo procura um slot vazio na tabela hash sondando slots sucessivos até que um slot vazio seja encontrado. Essa técnica pode ser mais eficiente do que o encadeamento quando o fator de carga é baixo, mas pode levar ao agrupamento e ao desempenho ruim quando o fator de carga é alto.
  • Hash duplo: Esta é uma variação do endereçamento aberto que usa uma segunda função hash para determinar o próximo slot a ser investigado quando ocorre uma colisão. Essa técnica pode ajudar a reduzir o agrupamento e melhorar o desempenho.

Exemplo de resolução de colisão

Vamos continuar com nosso exemplo de tabela hash com tamanho 5. Queremos armazenar os pares de valores-chave 'John: 123456' e 'Mary: 987654' na tabela hash. Ambas as chaves produzem o mesmo código hash 4, portanto ocorre uma colisão.

Podemos usar o encadeamento para resolver a colisão. Criamos uma lista vinculada no índice 4 e adicionamos os pares de valores-chave à lista. A tabela hash agora se parece com isto:

0:

1:

redes de computadores

2:

3:

4: João: 123456 -> Maria: 987654

5:

Tabela de hash:

Uma tabela hash é uma estrutura de dados que armazena dados em uma matriz. Normalmente, é selecionado um tamanho para a matriz que é maior do que o número de elementos que podem caber na tabela hash. Uma chave é mapeada para um índice na matriz usando a função hash.

A função hash é usada para localizar o índice onde um elemento precisa ser inserido na tabela hash para adicionar um novo elemento. O elemento é adicionado a esse índice se não houver uma colisão. Se houver uma colisão, o método de resolução de colisão é usado para encontrar o próximo slot disponível na matriz.

lista vinculada em java

A função hash é usada para localizar o índice em que o elemento está armazenado para recuperá-lo da tabela hash. Se o elemento não for encontrado naquele índice, o método de resolução de colisão é usado para procurar o elemento na lista vinculada (se for usado encadeamento) ou no próximo slot disponível (se for usado endereçamento aberto).

Operações de tabela hash

Existem diversas operações que podem ser realizadas em uma tabela hash, incluindo:

  • Inserção: Inserir um novo par de valores-chave na tabela hash.
  • Exclusão: remoção de um par de valores-chave da tabela hash.
  • Pesquisa: pesquisando um par de valores-chave na tabela hash.

Criando uma tabela hash:

Hashing é frequentemente usado para construir tabelas hash, que são estruturas de dados que permitem rápida inserção, exclusão e recuperação de dados. Um ou mais pares de valores-chave podem ser armazenados em cada uma das matrizes de buckets que compõem uma tabela hash.

Para criar uma tabela hash, primeiro precisamos definir uma função hash que mapeie cada chave para um índice exclusivo no array. Uma função hash simples pode ser pegar a soma dos valores ASCII dos caracteres na chave e usar o restante quando dividido pelo tamanho do array. No entanto, esta função hash é ineficiente e pode levar a colisões (duas chaves mapeadas para o mesmo índice).

Para evitar colisões, podemos usar funções hash mais avançadas que produzem uma distribuição mais uniforme de valores hash no array. Um algoritmo popular é a função hash djb2, que usa operações bit a bit para gerar um valor hash:

 unsigned long hash(char* str) { unsigned long hash = 5381; int c; while (c = *str++) { hash = ((hash << 5) + hash) + c; } return hash; } 

Esta função hash recebe uma string como entrada e retorna um valor hash inteiro longo e não assinado. A função inicializa um valor hash de 5381 e, em seguida, itera sobre cada caractere da string, usando operações bit a bit para gerar um novo valor hash. O valor final do hash é retornado.

Tabelas hash em C++

Em C++, a biblioteca padrão fornece uma classe contêiner de tabela hash chamada unordered_map. O contêiner unordered_map é implementado usando uma tabela hash e fornece acesso rápido a pares chave-valor. O contêiner unordered_map usa uma função hash para calcular o código hash das chaves e, em seguida, usa endereçamento aberto para resolver colisões.

Para usar o contêiner unordered_map em C++, você precisa incluir o arquivo de cabeçalho. Aqui está um exemplo de como criar um contêiner unordered_map em C++:

 #include #include int main() { // create an unordered_map container std::unordered_map my_map; // insert some key-value pairs into the map my_map['apple'] = 10; my_map['banana'] = 20; my_map['orange'] = 30; // print the value associated with the 'banana' key std::cout << my_map['banana'] << std::endl; return 0; } 

Explicação:

  • Este programa demonstra o uso do contêiner unordered_map em C++, que é implementado usando uma tabela hash e fornece acesso rápido a pares chave-valor.
  • Primeiro, o programa inclui os arquivos de cabeçalho necessários: e.
  • Em seguida, o programa cria um contêiner unordered_map vazio chamado my_map, que possui chaves de string e valores inteiros. Isso é feito usando a sintaxe std::unordered_map my_map;
  • Em seguida, o programa insere três pares de valores-chave no contêiner my_map usando o operador []: 'apple' com valor 10, 'banana' com valor 20 e 'orange' com valor 30.
  • Isso é feito usando a sintaxe my_map['apple'] = 10;, my_map['banana'] = 20; e my_map['orange'] = 30; respectivamente.
  • Finalmente, o programa imprime o valor associado à chave 'banana' usando o operador [] e o objeto std::cout.

Saída do programa:

Hashing na estrutura de dados

Inserindo dados em uma tabela hash

Para inserir um par de valores-chave em uma tabela hash, primeiro precisamos criar um índice na matriz para armazenar o par de valores-chave. Se outra chave for mapeada para o mesmo índice, teremos uma colisão e precisaremos tratá-la adequadamente. Um método comum é usar o encadeamento, onde cada intervalo na matriz contém uma lista vinculada de pares de valores-chave que possuem o mesmo valor de hash.

mínimo máximo

Aqui está um exemplo de como inserir um par de valores-chave em uma tabela hash usando encadeamento:

 typedef struct node { char* key; int value; struct node* next; } node; node* hash_table[100]; void insert(char* key, int value) { unsigned long hash_value = hash(key) % 100; node* new_node = (node*) malloc(sizeof(node)); new_node->key = key; new_node->value = value; new_node->next = NULL; if (hash_table[hash_value] == NULL) { hash_table[hash_value] = new_node; } else { node* curr_node = hash_table[hash_value]; while (curr_node->next != NULL) { curr_node = curr_node->next; } curr_node->next = new_node; } } 

Explicação:

  • Primeiro, é definida uma estrutura chamada node, que representa um único nó na tabela hash.
  • Cada nó possui três membros: uma chave char* para armazenar a chave, um valor int para armazenar o valor associado e um ponteiro para outro nó chamado next para lidar com colisões na tabela hash usando uma lista vinculada.
  • Uma matriz de ponteiros de nó chamada hash_table é declarada com tamanho 100. Essa matriz será usada para armazenar os elementos da tabela hash.
  • A função insert usa uma chave char* e um valor int como parâmetros.
  • Ele começa calculando o valor hash para uma determinada chave usando a função hash, que se presume estar definida em outro lugar do programa.
  • O valor hash é então reduzido para caber no tamanho da matriz hash_table usando o operador de módulo % 100.
  • Um novo nó é criado usando alocação dinâmica de memória (malloc(sizeof(node))) e seus membros (chave, valor e próximo) são atribuídos com a chave, valor e NULL fornecidos, respectivamente.
  • Se o slot correspondente na matriz hash_table estiver vazio (NULL), indicando que nenhuma colisão ocorreu, o novo nó será atribuído diretamente a esse slot (hash_table[hash_value] = new_node).

No entanto, se já houver um nó presente nesse índice na matriz hash_table, a função precisará lidar com a colisão. Ele percorre a lista vinculada começando no nó atual (hash_table[hash_value]) e passa para o próximo nó até chegar ao final (curr_node->next != NULL). Quando o final da lista for alcançado, o novo nó será anexado como o próximo nó (curr_node->next = new_node).

Implementação de Hashing em C++:

Vamos ver uma implementação de hash em C++ usando endereçamento aberto e sondagem linear para resolução de colisões. Implementaremos uma tabela hash que armazena inteiros.

 #include using namespace std; const int SIZE = 10; class HashTable { private: int arr[SIZE]; public: HashTable() { for (int i = 0; i <size; i++) { arr[i]="-1;" } int hashfunction(int key) return key % size; void insert(int index="hashFunction(key);" i="0;" while (arr[(index + i) size] !="-1" && arr[(index i++; if cout << 'element already exists in the hash table' endl; else remove(int deleted from return; not found display() for (int < (arr[i]="=" -1 || -2) continue; 'index ' ': }; main() hashtable ht; ht.insert(5); ht.insert(15); ht.insert(25); ht.insert(35); ht.insert(45); ht.display(); ht.remove(15); ht.remove(10); ht.insert(55); 0; pre> <p> <strong>Explanation:</strong> </p> <ul> <li>This program implements a hash table data structure using linear probing to handle collisions.</li> <li>A hash table is a data structure that stores data in key-value pairs, where the keys are hashed using a hash function to generate an index in an array. This allows for constant-time average-case complexity for inserting, searching, and deleting elements from the hash table.</li> <li>The HashTable class has a private integer array arr of size SIZE, which is initialized to -1 in the constructor. The hash function method takes an integer key and returns the hash value of the key, which is simply the remainder of the key when divided by SIZE.</li> <li>The insert method takes an integer key and uses the hash function to get the index where the key should be inserted.</li> <li>If the index is already occupied by another key, linear probing is used to find the next available index in the array. Linear probing checks the next index in the array until it finds an empty slot or the key itself.</li> <li>If the key is already in the hash table, the method displays a message saying that the element already exists. Otherwise, it inserts the key at the calculated index.</li> <li>The remove method takes an integer key and uses the hash function to get the index where the key is located.</li> <li>If the key is not in the calculated index, linear probing is used to search for the key in the next indices in the array. Once the key is found, it is deleted by setting its value to -2.</li> <li>If the key is not found in the hash table, the method displays a message saying that the element is not found.</li> <li>The display method simply iterates through the array and prints out all non-empty key-value pairs.</li> <li>In the main function, an instance of the HashTable class is created, and several integers are inserted into the hash table using the insert method.</li> <li>Then, the display method is called to show the contents of the hash table. The remove method is called twice, first to remove an element that exists in the hash table and then to remove an element that does not exist.</li> <li>The display method is called after each remove method call to show the updated contents of the hash table.</li> <li>Finally, another integer is inserted into the hash table, and the display method is called again to show the final contents of the hash table.</li> </ul> <p> <strong>Program Output:</strong> </p> <img src="//techcodeview.com/img/ds-tutorial/65/hashing-data-structure-3.webp" alt="Hashing in Data Structure"> <h2>Applications of Hashing</h2> <p>Hashing has many applications in computer science, including:</p> <ul> <li>Databases: Hashing is used to index and search large databases efficiently.</li> <li>Cryptography: Hash functions are used to generate message digests, which are used to verify the integrity of data and protect against tampering.</li> <li>Caching: Hash tables are used in caching systems to store frequently accessed data and improve performance.</li> <li>Spell checking: Hashing is used in spell checkers to quickly search for words in a dictionary.</li> <li>Network routing: Hashing is used in load balancing and routing algorithms to distribute network traffic across multiple servers.</li> </ul> <h2>Advantages of Hashing:</h2> <ul> <li>Fast Access: Hashing provides constant time access to data, making it faster than other data structures like linked lists and arrays.</li> <li>Efficient Search: Hashing allows for quick search operations, making it an ideal data structure for applications that require frequent search operations.</li> <li>Space-Efficient: Hashing can be more space-efficient than other data structures, as it only requires a fixed amount of memory to store the hash table.</li> </ul> <h2>Limitations of Hashing:</h2> <ul> <li>Hash Collisions: Hashing can produce the same hash value for different keys, leading to hash collisions. To handle collisions, we need to use collision resolution techniques like chaining or open addressing.</li> <li>Hash Function Quality: The quality of the hash function determines the efficiency of the hashing algorithm. A poor-quality hash function can lead to more collisions, reducing the performance of the hashing algorithm.</li> </ul> <h2>Conclusion:</h2> <p>In conclusion, hashing is a widely used technique in a data structure that provides efficient access to data. It involves mapping a large amount of data to a smaller hash table using a hash function, which reduces the amount of time needed to search for specific data elements. The hash function ensures that data is stored in a unique location within the hash table and allows for easy retrieval of data when needed.</p> <p>Hashing has several advantages over other data structure techniques, such as faster retrieval times, efficient use of memory, and reduced collisions due to the use of a good hash function. However, it also has some limitations, including the possibility of hash collisions and the need for a good hash function that can distribute data evenly across the hash table.</p> <p>Overall, hashing is a powerful technique that is used in many applications, including database indexing, spell-checking, and password storage. By using a good hash function and implementing appropriate collision resolution techniques, developers can optimize the performance of their applications and provide users with a seamless experience.</p> <hr></size;>