哈希表是什么?
哈希表(HashTable)又叫做散列表,是根据关键码值(即键值对)而直接访问的数据结构。也就是说,它通过把关键码映射到表中一个位置来访问记录,以加快查找速度。看到这里你可能比较疑惑,它是怎么加快查找速度的?下一节就有说明!这个映射函数就叫做散列(哈希)函数,存放记录的数组叫做散列表。
为什么哈希表的速度快?
在数据结构中,我们对两种数据结构应该会非常熟悉:数组与链表。数组的特点就是查找容易,插入删除困难;而链表的特点就是查找困难,但是插入删除容易。既然两者各有优缺点,那么我们就将两者的有点结合起来,让它查找容易,插入删除也会快起来。哈希表就是讲两者结合起来的产物。
哈希如何查找?
哈希的查找就是下面两个步骤:
<1>使用哈希函数将被查找的键转化为数组的索引。在理想的状态下,不同的键会被转化成不同的索引值。但是那是理想状态,我们实践当中是不可能一直是理想状态的。当不同的键生成了相同的索引的时候,也就是我们所说的冲突,我们这个时候就要处理冲突。
<2> 处理冲突的方法很多,后面我们介绍线性探索法和拉链法。
哈希表是一个时间和空间上平衡的例子。如果没有空间的限制,我们可以直接用键来作为数组的索引,这样可以将查找时间做到最快(O(1))。如果没有时间的限制,我们可以使用无序链表进行顺序查找,这样只需要很少的内存。
什么是哈希函数?
哈希函数其实就是我们常说的哈希算法,主要应用在以下这几个方面:文件校验、数字签名、鉴权协议。常用的哈希算法有以下这些。
<1>MD5:MD5即Message-Digest Algorithm 5(信息-摘要算法5),用于确保信息传输完整一致。MD5是输入不定长度信息,输出固定长度128bits的算法。
<2>SHA-1:常用于HTTPS传输和软件签名。
<3>SHA-2:SHA-224/SHA-256/SHA-384/SHA-512并成为SHA-2
<4>SHA-3:之前名为Keccak算法,是一个加密杂凑算法。
如何避免哈希冲突?
拉链法
线性探索法
HashTable结构
#define HASHTABLESIZE 13
typedef int KeyType;
// typedef int DataType;
typedef struct Node
{
KeyType key; // 数据原有的key值,此值一般不能有重复的 主键(唯一)
// DataType data;
struct Node *next;
}LinkList;
typedef struct Hash
{
LinkList *hash_table[HASHTABLESIZE];
int number[HASHTABLESIZE]; // 记录每一个链表的节点的个数
}Hash;
HashTable实现
#include "hash.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <math.h>
//初始化哈希表
void InitHash(Hash *hash)
{
if(hash == NULL) exit(0);
for(int i = 0; i < HASHTABLESIZE; ++i)
{
hash->hash_table[i] = NULL;
hash->number[i] = 0;
}
}
//定义哈希函数
static int HashFun(KeyType key)
{
return abs(key % HASHTABLESIZE);//abs取绝对值,key%HASHTABLESIZE 得到存放数组的下标
}
//插入元素
int InsertHash(Hash *hash, KeyType key)
{
if(hash == NULL) exit(0);
int hash_key = HashFun(key); //hash_key 就是下标
LinkList *p = hash->hash_table[hash_key];
while(p != NULL)
{
if(p->key == key) return 0;//判断key是否存在 是否唯一
p = p->next;
}
LinkList *new_node = (LinkList*)malloc(sizeof(LinkList));
if(new_node == NULL) return 0;
new_node->key = key;
new_node->next = hash->hash_table[hash_key]; //新结点next指向原先结点,若原先没有结点即指向NULL
hash->hash_table[hash_key] = new_node;
hash->number[hash_key]++;
return 1;
}
//删除哈希元素
int DeleteHash(Hash *hash, KeyType key)
{
if(hash == NULL) exit(0);
int hash_key = HashFun(key);
LinkList *p = hash->hash_table[hash_key];
LinkList *q = NULL; // p的前驱节点
while(p != NULL)
{
if(p->key == key)//遍历寻找需要删除的结点
{
if(q == NULL)
{
hash->hash_table[hash_key] = p->next;
}
else
{
q->next = p->next;
}
free(p);
hash->number[hash_key]--;
return 1;
}
q = p;
p = p->next;
}
return 0;
}
//查询,与上面插入或删除时相同
LinkList *Search(Hash *hash, KeyType key)
{
if(hash == NULL) exit(0);
int hash_key = HashFun(key);
LinkList *p = hash->hash_table[hash_key];
while(p != NULL)
{
if(p->key == key) return p;
p = p->next;
}
return NULL;
}
//销毁哈希表
void DestroyHash(Hash *hash)
{
if(hash == NULL) exit(0);
for(int i = 0; i < HASHTABLESIZE; ++i)
{
LinkList *p = hash->hash_table[i];
while(p != NULL)
{
hash_table[i] = p->next;
free(p);
p = hash_table[i];
}
hash->number[i] = 0;
}
}