哈希表的定义
存储位置=f(关键字)
散列技术(哈希表的技术)是在记录的存储位置和它的关键字之间建立一个确定的对应关系 f,使得每个关键字 key 对应一个存储位置f(key)。查找时,根据这个确定的对应关系找到给定值 key 的映射 f(key),若查找集合中存在这个记录,则必定在f(key)的位置上。
这里我们把这种对应关系f称为散列函数,又称为哈希(Hash)函数。
采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。
散列表查找步骤
整个散列过程其实就是两步。
(1)在存储时,通过散列函数计算记录的散列地址,并按此散列地址存储该记录。就像张三丰我们就让他在体育馆,那如果是‘爱因斯坦’我们让他在图书馆,如果是‘居里夫人’,那就让她在化学实验室,如果是‘巴顿将军’,这个打仗的将军一一我们可以让他到网吧。总之,不管什么记录,我们都需要用同一个散列函数计算出地址再存储。
(2)当查找记录时,我们通过同样的散列函数计算记录的散列地址,按此散列地址访问该记录。说起来很简单,在哪存的,上哪去找,由于存取用的是同一个散列函数,因此结果当然也是相同的。
所以说,散列技术既是一种存储方法,也是一种查找方法。
散列函数的构造方法
直接定址法
如果我们现在要对0~100 岁的人口数字统计表,如表所示,那么我们对年龄这个关键字就可以直接用年龄的数字作为地址。此时f(key)=key。
如果我们现在要统计的是80 后出生年份的人口数,如表所示,那么我们对出生年份这个关键字可以用年份减去1980来作为地址。此f(key)=key-1980。
也就是说,我们可以取关键字的某个线性函数值为散列地址,即f(key)=a×key+b (a、b为常数)
数字分析法
如果我们的关键字是位数较多的数字,比如我们的 11 位手机号 “130xxxx1234”, 其中前三位是接入号,一般对应不同运营商公司的子品牌,如 130是联通如意通、136 是移动神州行、153 是电信等;中间四位是HLR 识别号,表示用户号的归属地;后四位才是真正的用户号,如表所示。
这里我们提到了一个关键词——抽取。抽取方法是使用关键字的一部分来计算散列存储位置的方法,这在散列函数中是常常用到的手段。
数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑用这个方法。
除留余数法
此方法为最常用的构造散列函数方法。对于散列表长为m的散列函数公式为:
f(key)=key mod p(p≤m)
mod 是取模(求余数)的意思。事实上,这方法不仅可以对关键字直接取模,也可在折叠、平方取中后再取模。
很显然,本方法的关键就在于选择合适的p,p 如果选得不好,就可能会容易产生同义词。
例如表,我们对于有 12 个记录的关键字构造散列表时,就用了f(key) =key.mod 12的方法。比如 29 mod12=5,所以它存储在下标为5的位置。
我们不选用p=12来做除留余数法,而选用p=11,如表所示。
此就只有12和144有冲突,相对来说,就要好很多。
例子:
这里我们写了一个例子,看哈希表如何应用。
hash.h头文件
#ifndef _HASH_H_
#define _HASH_H_
#define HASN_SIZE 27
typedef struct per //创建数据内容
{
char name[64];
char tel[31];
char addr[64];
int age;
}DATA_TYPE;
typedef struct hsnode
{
DATA_TYPE data;
struct hsnode *pnext;
}HASH_NODE;
extern int insert_hash_table(DATA_TYPE data);
extern void hash_for_each();
extern HASH_NODE *find_hash_table(char *name);
extern void destroy_hash_table();
#endif
hash.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "hash.h"
HASH_NODE *hash_table[HASN_SIZE] = {NULL};
int hash_fun(char ch) //判断,因为是通过首字母排序
{
if (ch >= 'a' && ch <= 'z')
{
return ch-'a';
}
else if (ch >= 'A' && ch <= 'Z')
{
return ch-'A';
}
else
{
return HASN_SIZE-1;
}
}
int insert_hash_table(DATA_TYPE data) //插入数据
{
HASH_NODE *pnode = malloc(sizeof(HASH_NODE));
if (NULL == pnode)
{
perror("fail malloc");
return -1;
}
pnode->data = data;
pnode->pnext = NULL;
int addr = hash_fun(data.name[0]);
//hash_table[addr] ========>phead
pnode->pnext = hash_table[addr];
hash_table[addr] = pnode;
return 0;
}
void hash_for_each() //遍历哈希表
{
for (int i = 0; i < HASN_SIZE; i++)
{
HASH_NODE *ptmp = hash_table[i];
while (ptmp != NULL)
{
printf("%s %s %s %d\n", ptmp->data.name, ptmp->data.tel, ptmp->data.addr, ptmp->data.age);
ptmp = ptmp->pnext;
}
printf("\n");
}
}
HASH_NODE *find_hash_table(char *name) //通过名字查找其其余信息
{
int addr = hash_fun(name[0]);
HASH_NODE *ptmp = hash_table[addr];
while (ptmp != NULL)
{
if (!strcmp(name, ptmp->data.name))
{
return ptmp;
}
ptmp = ptmp->pnext;
}
return NULL;
}
void destroy_hash_table() //删除哈希表
{
HASH_NODE *ptmp = NULL;
for (int i = 0; i < HASN_SIZE; i++)
{
while(hash_table[i] != NULL)
{
ptmp = hash_table[i];
hash_table[i] = ptmp->pnext;
free(ptmp);
}
}
}