首先哈希表是用于查找数据中比较常用的数据结构。
那为什么用哈希表呢?数组和链表不也可以查找吗?
先说数组,如果数组存储的数据与下标相对应,那么查找的速度确实很快,但假如是下面这样的例子呢?
(画的不好,不要介意)
可以看出,数组中每个元素与其下标都不是相对应的。那么再查找数据的时候就需要一个个元素进行比较,数据量小的时候还可以接受但是数据量大了之后效率就会相当的低下。
链表也是一样。
接下来我再介绍个例子。
聪明的你可能已经发现规律了,就是将arr中每个元素与其中最大的元素也就是10,进行取余操作,出现的结果次数分别保存再一个新的数组中。
其中,这个新的数组大小与arr中最大的值大小相等。也就是说,如果最大的值是100,这个数组大小就是100。先不讨论空间问题。先看看看查找的效率,假如我要查找10,我只要将10与10进行取余,也就是查找新数组中0位置的值是否为0,不为0,说明arr中有10。这样的查找效率是不是很高呢?但是这个方法的缺陷就是空间资源消耗大。
因此,在这个方法作出改进,哈希查找就演变出来了。
(实在是画的太丑了,如果你们有比较方便的画图软件,麻烦你们留个言。)
数组arr里最大的数是99,如果按上面的方法就需要申请一个大小为99*sizeof(int)的整型数组。
但是数据只有11个数据,这样会造成资源的浪费。
这里创建了一个大小为11的数组,为什么是11?因为大小是11吗?不是的,因为这里11是质数,可以使得数组进行取余操作时,余数分布比较均匀。
首先说一下哈希表的一些概念。
“散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。
(这里的映射函数的功能就是对数组元素取余,散列表就是图中大小为11的数组)
那为什么数组中每个元素后面都接一个链表呢?
细心的朋友可能已经发现,在同一个链表中的元素,与11取余后余数时相同的,这样的情况,称之为哈希冲突。为了解决这个问题,在元素后面添加链表保存余数相同的数据。
解决哈希冲突常用的方法用:
1、开放定址法
2、链地址法(通常就是散列表+链表,如果想再优化一下可以将链表替换成二叉树)
好,概念介绍的差不多了,现在说一下怎么使用哈希表进行数据保存和数据查找了。
首先我们拿到要保存数据的数组,先找定义哈希表的长度,一般取质数(没有规定的方法,但是这个数的选取也会影响查找的效率)。
将数据按上图的方法保存起来。
然后查找数据时,先获取该数据的关键值(就是取余,取余的对象一般是哈希表的长度)。
然后就根据链表或者二叉树的查找数据的形式查找数据。
下面是链式地址法的代码(散列表+链表)
#define TABLESIZE 7
typedef struct pNode
{
int data; // 链表中的数据域
struct pNode* next; // 链表中每个结点的下一个结点的地址
}Node;
typedef struct Hash_Table
{
int size; // 哈希表中存储数据的个数
int length; // 哈希表的长度
struct pNode* head; // 链表数组的首地址
}Hash;
int hash_key(int data) // 返回哈希表下标
{
return data % TABLESIZE;
}
Hash* Init_HashTable() // 初始化哈希表
{
Hash* hash = (Hash*)malloc(sizeof(struct Hash_Table));
hash->length = TABLESIZE;
hash->size = 0;
hash->head = (Node*)malloc(sizeof(Node) * TABLESIZE); // 申请存储不同下标链表头的地址数组空间
for (int i = 0; i < TABLESIZE; i++)
{
hash->head[i].data = -1;
hash->head[i].next = NULL;
}
return hash;
}
int find(Hash* hash, int data) // 查找某个数据是否存在哈希表中
{
if (!hash) return 0;
int key = hash_key(data);
Node* pCur = hash->head[key].next;
while (pCur)
{
if (pCur->data == data) return 1;
pCur = pCur->next;
}
return 0;
}
void insert(Hash* hash, int data) // 插入到哈希表中
{
if (!find(hash, data))
{
int key = hash_key(data);
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = hash->head[key].next;
hash->head[key].next = newNode;
hash->size += 1;
return;
}
else
{
printf("该数据已存在哈希表中!");
return;
}
}
void del(Hash* hash, int data) // 删除某个在哈希表的元素
{
if (!hash)return;
if (!find(hash, data))
{
printf("不存在该元素!\n");
return;
}
else
{
int key = hash_key(data);
Node* pre = &hash->head[key];
Node* pCur = hash->head[key].next;
while (pCur)
{
if (pCur->data == data)
{
pre->next = pCur->next;
free(pCur);
pCur = NULL;
return;
}
pre = pCur;
pCur = pCur->next;
}
}
}
void destory(Hash* hash) // 销毁哈希表
{
if (!hash) return;
Node *next, *pCur;
for (int i = 0; i < TABLESIZE; i++)
{
pCur = hash->head[i].next;
while (pCur)
{
next = pCur->next;
free(pCur);
pCur = next;
}
}
free(hash->head);
free(hash);
}
void foreach_hashtable(Hash* hash) // 遍历哈希表
{
if (!hash) return;
for (int i = 0; i < TABLESIZE; i++)
{
Node* pCur = hash->head[i].next;
printf("[%d]:\n", i);
if (!pCur)
{
printf("NULL\n");
}
while (pCur)
{
printf("%d\n", pCur->data);
pCur = pCur->next;
}
}
}