此前学习了链表与数组两种存储数据的结构,但是二者都有其缺点,数组因为其存储空间连续的特性可以直接访问到其中任意元素,但是此特性也导致其存储空间不能随意的增加或减少(连续空间有可能被其他程序调用)。而链表因其不依赖连续空间的关系可以随意增删,但是想要直接访问其中的元素并不容易,需要借助头节点依次访问。
于是这里将两种数据结构组合,用来制作哈希表(链地址法)
哈希表的基础思想在于存储位置与其实际值的关系,一般是通过一个函数(hash函数)得到其存储位置(数组下标)

比较常用的是除留余数法,这里以此为例写代码
由于不同值经过hash函数后可能得到相同的结果,对此问题,有两种解决方法
链地址法:哈希表的每个数组元素不再只存一个数据,而是一个链表(链)的头指针(也可以是一个节点)。当不同的 key 计算出相同的哈希地址(冲突)时,它们被"串"在同一个链表的节点中。
开放地址法:在开放地址法中,哈希表数组的大小(m)必须大于元素个数(n),当插入时发现下标 H(key) 已被占用,就按照某种规则去探测下一个空闲位置,直到找到空位为止。

哈希表创建函数
这里直接用指针作为数组元素,宏定义数组大小
#include <stdio.h>
#include <stdlib.h>
#define SIZE 10
typedef int data_t;
typedef struct node
{
data_t data;
struct node *next;
}node_t;
node_t *hash_table[SIZE]; //NULL
int hash_create(data_t *a,int len)
{
int i = 0;
for (i = 0; i <len; ++i)
{
int addr = a[i] % SIZE;
node_t *p_new = malloc(sizeof(node_t));
if (p_new == NULL)
{
return -1;
}
p_new->data = a[i];
p_new->next = NULL;
if (hash_table[addr] == NULL)
{
hash_table[addr] = p_new;
}else
{
p_new->next = hash_table[addr];
hash_table[addr] = p_new;
}
}
return 0;
}
tips:取余时并不局限在10(结果在0~9),可以用更大或更小的数,关键在于让其分散存储,要避免数据聚集,数据聚集会导致查找效率下降(链表需要遍历)。
哈希表遍历函数
void hash_show()
{
// 拿出hash_table中的元素
// 看是否为NULL
// 不为NULL
// 则遍历链表
int i = 0;
for (i = 0; i < SIZE; ++i)
{
printf("%d[%p]->",i,hash_table[i]);
if (hash_table[i] != NULL)
{
node_t *p = hash_table[i];
while (p)
{
if (p->next!=NULL)
printf("%d->",p->data);
else
printf("%d|NULL",p->data);
p = p->next;
}
}
putchar('\n');
}
}
哈希表查找函数
node_t *hash_find(data_t data)
{
int addr = data%SIZE;
if (hash_table[addr] == NULL)
{
return NULL;
}else
{
node_t *p = hash_table[addr];
while (p)
{
if (p->data == data)
{
return p;
}
p = p->next;
}
}
return NULL;
}
哈希表修改函数
node_t *hash_update(data_t old,data_t new)
{
// 1.先求出addr
// data % SIZE
// 2.找到位置
// 遍历链表 找到对应的值
// 3.找到了返回 对应节点的地址
// 找不到则 返回NULL
int addr = old%SIZE;
if (hash_table[addr] == NULL)
{
return NULL;
}else
{
node_t *p = hash_table[addr];
while (p)
{
if (p->data == old)
{
p->data = new; //修改
return p;
}
p = p->next;
}
}
return NULL;
}
哈希表删除函数
int hash_delete_key(data_t data)
{
//思路:
//1.表中如果没有这个值
// 没法删
//2.有该数据
//
// 链表可能就只有一个节点
// 不止一个节点
// 找到该节点的前一个节点 删除
int addr = data%SIZE;
if (hash_table[addr] == NULL)
{
return -1;
}else
{
node_t *p = hash_table[addr];
if(p->next == NULL)
{
if (p->data == data)
{
free(p);
hash_table[addr] = NULL;
}
}else
{
if (p->data == data)
{
hash_table[addr] = p->next;
free(p);
}else
{
while (p->next && p->next->data != data) //当p的下一节点为空,或者下一节点
{ //查找的节点时停下
p=p->next;
}
if (p->next == NULL)
{
return -1;
}
node_t *p_temp = p->next; //删除目标节点
p->next = p_temp->next;
free(p_temp);
}
}
}
return 0;
}
哈希表销毁函数
void hash_destroy(void)
{
int i = 0;
for (i = 0; i < SIZE; ++i)
{
if (hash_table[i] !=NULL)
{
node_t *p = hash_table[i];
hash_table[i] = NULL;
while (p)
{
node_t *p_temp = p;
p = p->next;
free(p_temp);
}
}
}
}