散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key),adr = f(key)。查找时,根据这个确定的对应关系找到给定值key的映射f(key),若查找集合中存在这个记录,则必定在f(key)的位置上。
这里我们把这种对应关系f称为散列函数,又称为哈希函数(Hash).按这个思想,采用散列技术将记录存储在一块连续的存储空间中,这块连续的存储空间称为散列表或哈希表(Hash table)。关键字对应的记录存储位置称为散列地址。
散列技术既是一种存储方法,也是一种查找方法。
散列技术最适合求解的问题是查找与给定值相等的记录。
散列函数设计原则:简单、均匀、存储利用率高
两个关键字 key1 ≠ key2,但是却有 f(key1) = f(key2),这种现象我们称为冲突(collision),并把key1和key2称为这个散列函数的同义词(synonym)。
散列函数设计原则:
计算简单: 散列函数的计算时间不应该超过其他查找技术与关键字比较的时间。
散列地址分布均匀:尽量让散列地址均匀地分布在存储空间中,这样可以保证存储空间的有效利用,并减少为处理冲突而耗费的时间。
直接定址法 f(key) = a * key + b(a、b为常数)
取关键字的某个线性函数值为散列地址。(这样的散列函数简单、均匀、不会产生冲突;需要事先知道关键字的分布情况,适合查找表较小且连续的情况,较少使用)
数字分析法
抽取关键字的一部分来计算散列函数位置的方法。适合处理关键字位数比较大的情况,(如果事先知道关键字的分布且关键字的若干位分布比较均匀,就可以考虑用这个方法。)
平方取中法
计算关键字的平方,再取中间几位做为关键字。eg:1234,平方1522756,取中间3为227为关键字。(平方取中法比较适合不知道关键字的分布,而位数又不是很大的情况。)
折叠法
将关键字从左到右分割成位数相等的几部分,然后将这几部分叠加求和,并按散列表表长,取后几位作为散列表地址。(折叠法事先不需要知道关键字的分布,适合关键字位数较多的情况。)
除留余数法
此方法为最常用的构造散列函数的方法。对于散列表长为m的散列函数公式为:f(key) = key mod p(p≤m)
这种方法不仅可以对关键字直接取模,也可在折叠、平方取中后再取模。**散列表长为m,通常p为小于或等于表长(最好接近m)的最小质数或不包含小于20质因子的合数。
随机数法
选择一个随机数,取关键字的随机函数值为它的散列地址,也就是 f(key) = random(key)。这里random是随机函数。(当关键字的长度不等时,采用这个方法构造散列函数是比较合适的)
选择散列函数应该考虑事项:
1、计算散列地址所需时间
2、关键字的长度
3、散列表的大小
4、关键字的分布情况
5、记录查找的频率
处理散列冲突的方法
开放定址法
一旦发生了冲突,就去寻找下一个空的散列地址,知道散列表足够大,空的散列表地址总能找到,并将记录存入。这种解决冲突的开放定址发称为线性探测法。
fi(key) = (f(key) + di) MOD m (di = 1,2,3,...,m-1)
在发生冲突进行重新定址的过程中会导致后面不是同义词的关键字( f(keyi) ≠ f(key2) )争夺一个地址的情况,这种现象为堆积。
线性探测都是发生冲突后加上一个di然后取余数来寻找下一个空间地址,如果发生冲突的位置之前就有一个空位置,要找到这个空位置要循环效率就很低
fi(key) = (f(key) + di) MOD m (di = 1^2,-1^2,2^2,...,q^2,-q^2,q≤m/2)
增加平方运算的目的是为了不让关键字都聚集在某一块区域,这种方法叫做二次探测法。
在冲突时,对于位移量di采用随机函数计算得到。
fi(key) = (f(key) + di) MOD m (di 是一个伪随机数列) 。 伪随机,只要随机种子相同,每次得到的数列都会是相同的。这就是随机探测法
再散列函数法
fi(key) = RHi(key) (i=1,2,...,k)
其中RHi就是不同的散列函数,每当发送冲突时,就换一个散列函数计算,总有一个可以解决冲突
链地址法:将所有关键字为同义词的记录存储在一个单链表中,这种链表叫做同义词子表,使用除留余数法,就不存在冲突的问题了,只是在链表中增加一个结点。 |
公共溢出区法:
对所有冲突的关键字建立一个公共溢出区来存放
|
#include<iostream>
#include<stdlib.h>
#define max ~( 1<<(sizeof(int)*8-1) )
using namespace std;
//散列函数采用除留余数法
//冲突解决采用开放定址法的线性探测法
int hashFunc(int key,int length);
int initHashTable(int* hashTable,int length); //成功返回0,失败返回-1
int hashInsert(int* hashTable,int key,int length); //成功返回0,失败返回-1
static enum status{failture=-1,success=0} flag;
int hashSearch(int*hashTable,int key,int length);
void test();
int main()
{
test();
system("pause");
}
int initHashTable(int* hashTable,int length)
{
if(nullptr==hashTable || length<=0)
return -1;
for(int idx=0;idx!=length;++idx)
{
hashTable[idx] = max;
}
return 0;
}
int hashFunc(int key,int length)
{
if(key==max||length<=0)
return -1;
return key % length; //除留余数
}
int hashInsert(int* hashTable,int key,int length)
{
if(nullptr==hashTable||length<=0)
return -1;
int hashAddr = hashFunc(key,length);
if(-1==hashAddr)
return -1;
for(int idx=0;idx!=length;++idx) //循环,最大哈希表长度
{
if(hashTable[hashAddr]!=max) //冲突
hashAddr = (hashAddr+1) % 12; //开放定址法的线性探测法,查找下一个可存放数据的空间
else
break;
}
if(max==hashTable[hashAddr])
{
hashTable[hashAddr] = key;
return 0;
}
return -1;
}
|
void test()
{
int arr[12] = {12,67,56,16,25,37,22,29,15,47,48,34};
int* hashTable = new int[12]();
int ret = initHashTable(hashTable,12);
if(-1==ret)
cout<<"initHashTable fail!"<<endl;
cout<<"哈希表待插入元素:";
for(int idx=0;idx!=12;++idx)
{
cout<<arr[idx]<<" ";
hashInsert(hashTable,arr[idx],12);
}
cout<<endl;
cout<<"哈希表:";
for(int idx=0;idx!=12;++idx)
{
cout<<hashTable[idx]<<" ";
}
cout<<endl;
cout<<"对应插入元素序列在哈希表查找元素:";
for(int idx=0;idx!=12;++idx)
{
int ret = hashSearch(hashTable,arr[idx],12);
if( ret==-1 && flag == failture)
{
cout<<"search "<<arr[idx]<<" fail"<<endl;
}
cout<<hashTable[ret]<<" ";
}
cout<<endl;
cout<<"查找1:"<<endl;
int rett = hashSearch(hashTable,1,12);
if( rett==-1 && flag == failture)
{
cout<<"search "<<1<<" fail"<<endl;
return ;
}
cout<<hashTable[rett]<<endl;
}
int hashSearch(int*hashTable,int key,int length)
{
flag = success;
if(nullptr==hashTable||length<=0)
{
flag = failture;
return -1;
}
int hashAddr = hashFunc(key,length);
while(key!=hashTable[hashAddr])
{
hashAddr = (hashAddr+1) % length;
if(max==hashTable[hashAddr] || hashAddr == hashFunc(key,length)) //如果探测到下一个地址为空,还没有找到,或者循环找了一遍又回到最开始的hashAddr
{
flag = failture;
return -1;
}
}
return hashAddr;
}
|