哈希表简单讲解
什么是哈希表
哈希表按我自己的理解就是一个查找表,我们以简单应用的方式来学习它。
简单应用
比如一个链表,它一个节点里含有两个内容
typedef struct ListNode{
int value;
char name[5];
struct ListNode* next;
}ListNode;
value用来存学生的学号,name[5]字符数组用来存学生姓名,对与这样一个链表如果我们想要找到某个学号的学生姓名就需要遍历整个链表,而哈希表便可以直接找到他/她,这里我们可以用以下指针数组
ListNode **H = (ListNode **)malloc(sizeof(*H)*100);
这个H[100]数组中每一个都是可以指向链表节点的指针,这样我们利用学号按每位相加来确定索引,例如学号为123456的学生所在的节点就被H[21]这个指针所指,这样我们查找时只需要输入学号不用遍历便可直接找到该节点
代码实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct ListNode{
int value;
char name[5];
struct ListNode* next;
}ListNode;
void print(ListNode *head)
{
ListNode *p = head;
for(;p;p = p->next)
{
printf("%d\t%s\n",p->value,p->name);
}
}
int haxi(int value)
{
int n = value;
int sum = 0;
while(n>0)
{
sum = sum + n%10;
n = n / 10;
}
return sum;
}
int main()
{
int n;
printf("请输入学生人数:\n");
scanf("%d",&n);
ListNode **H = (ListNode **)malloc(sizeof(*H)*100);
memset(H, 0, sizeof(ListNode));
ListNode *head = NULL;
ListNode *tail = NULL;
for(int i = 0;i<n;i++)
{
int val;
char str[5];
printf("请输入学生学号:\n");
scanf("%d", &val);
printf("请输入学生姓名:\n");
scanf("%s", str);
ListNode *p = (ListNode*)malloc(sizeof(ListNode));
p->value = val;
strcpy(p->name, str);
p->next = NULL;
int x = haxi(val);
H[x] = p;
if(!head)
{
head = p;
tail = p;
}
else
{
tail->next = p;
}
tail = p;
}
print(head);
int m;
printf("请输入你要查询学生的学号:\n");
scanf("%d", &m);
int y = haxi(m);
printf("%s",H[y]->name);
return 0;
}
哈希函数
以上面的简单应用为例,哈希函数就是将学号123456转化为21的函数也就是1+2+3+4+5+6=21;
int haxi(int value)
{
int n = value;
int sum = 0;
while(n>0)
{
sum = sum + n%10;
n = n / 10;
}
return sum;
}
这就是哈希函数,哈希函数有很多计算方法,例如每位相乘,取反相加取中,等等。哈希函数完全可以按自己的想法设定,没有固定,合适最重要。
哈希冲突
以上面简单应用为例,如果有两个学生的学号为123456和123546,利用哈希函数算出的值都为21,就发生了哈希冲突,解决哈希冲突的方法有:
- 开放地址法
1.线性探测
按顺序决定值时,如果某数据的值已经存在,则在原来值的基础上往后加一个单位,直至不发生哈希冲突。
2.再平方探测
按顺序决定值时,如果某数据的值已经存在,则在原来值的基础上先加1的平方个单位,若仍然存在则减1的平方个单位。随之是2的平方,3的平方等等。直至不发生哈希冲突。
3.伪随机探测
按顺序决定值时,如果某数据已经存在,通过随机函数随机生成一个数,在原来值的基础上加上随机数,直至不发生哈希冲突。 - 链式地址法(HashMap的哈希冲突解决方法)
- 对于相同的值,使用链表进行连接。使用数组存储每一个链表。
优点:
(1)拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
(2)由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
(3)开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
(4)在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。
缺点:
指针占用较大空间时,会造成空间浪费,若空间用于增大散列表规模进而提高开放地址法的效率。 - 建立公共溢出区
建立公共溢出区存储所有哈希冲突的数据。 - 再哈希法
对于冲突的哈希值再次进行哈希处理,直至没有哈希冲突。