世界微尘里,吾宁爱与憎
哈希表(散列)
哈希表(散列)

哈希表(散列)

哈希技术

在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字对于一个存储位置f(key),这种对应关系成为散列函数,又称为哈希函数

散列表/哈希表(Hash Table)

通过哈希技术将记录存储在一块连续的存储空间中,这块连续的存储空间就成为散列表或者哈希表

哈希表的本质

哈希表的本质是一个数组,哈希表是经过加工处理的数组。一般来说,哈希表的实现一般采用两种办法:

  • 数组+链表
  • 数组+二叉树

为了查找电话簿中某人的号码,可以创建一个按照人名首字母顺序排列的表(即建立人名 x 到首字母 F(x) 的一个函数关系),在首字母为 W 的表中查找“王”姓的电话号码,显然比直接查找就要快得多。这里使用人名作为关键字,“取首字母”是这个例子中散列函数的函数法则 F(),存放首字母的表对应散列表。关键字和函数法则理论上可以任意确定。

散列函数的构造

直接定址法

使用关键字的某个线性函数值直接作为地址

f(key)=a*key+b(a,b常数)

Tips;这种方式基本不会存在哈希冲突,不过比较鸡肋.

数字分析法

如果关键字是位数较多的数字且事先知道关键字的分布,则抽取关键字的一部分来计算散列位置的存储位置

Tips:这种方式极度不灵活,限制太多.

平方取中法

顾名思义,对关键字进行平方后取中间的几位数,比较适用于不知道关键字的分布,而位数又不是很多的情况,可以理解为数字分析法的一种更高级的办法

Tips:这种方式中间的几位数都和关键字的每一位都有关,产生的散列地址较为的均匀。

折叠法

将关键字从左至右分割成位数相等的几部分,然后将这几部分叠加求和,根据表长取结果的后几位作为散列地址,适用于关键字位数较多的情况

除留余数法

最常用的构造散列函数的方法,对于散列表长为m的散列函数公式为:

f(key)=key mod p(p≤m)

Tips:可以与折叠法,平法取中法联用,即在折叠法、平方取中法等运算之后取模.对 p 的选择很重要,一般取素数或 m,若 p 选择不好,容易产生碰撞.

随机法

利用random函数随机产生关键字的地址,适合用于关键字的长度不等时

哈希冲突

key1≠key2,f(key1)=f(key2)

处理哈希冲突

开放定址法

链地址法

公共溢出法

再散列

实现代码

typedef struct
{
   int *elem;//数据元素存储基址,动态分布数组
   int count;//当前数据元素个数
}HashTable;
int m=0;//散列表表长
int InitHashTable(HashTable *H)
{
   int i;
   m=HASHSIZE;
   H->count=m;
   H->elem=(int *)malloc(m*sizeof(int));
   for(i=0;i<m;i++)
       H->elem[i]=NULLKEY;
}
int Hash(int key)
{
   return key % m;//除留余数法
}
void InsertHash(HashTable *H,int key)//插入操作
{
   int addr=Hash(key);
   while(H->elem[addr]!=NULLKEY)//如果不为空,则冲突
       addr=(addr+1)%m;
   H->elem[addr]=key;//找到空位后插入关键字
}
int SearchHash(HashTable H,int key,int *addr)
{
   *addr=Hash(key);//求散列地址
   while(H.elem[*addr]!=key)
  {
       *addr=(*addr+1)%m;//开放地址线性探测法
       if(H.elem[*addr]==NULLKEY||*addr==Hash(key))//如果循环回到原点或者最终指向NULL说明关键字不存在
           return 0;
  }
   return 1;
}

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注