常见散列方法
1.取余法:hash(Key)=Key%M。其中M的取值有技巧。M首先尽量不能选取较为规整的数字,如2的n次方,这样是很容易出现分布不均匀的情况,也就是很容易出现散列冲突,散列的意义就变小了。那么我们一般选取一个合适的素数M来作为余数。个人理解:很多个数余上同一个数M,会冲突是几乎没办法的事情,但是如果能让某一类的数都不可能发生冲突那么就很好了。那么假设数S,gcd(S,M)一定是为1的,根据数论或者可以自己理解的话我们可以知道,KS(K为整数)在M个位置还没被填满之前是不可能发生冲突的。也就是只要是S的倍数都不可能在同一个位置上,我们还可以画一个范围是M的数轴把S当作一个周期,KS当作一个个周期往后走来理解。所以这样S的倍数全都不可能冲突,而离的比较近的数S1,S2,余上同一个数会相同的概率也很小。所以整体来说减少了很多冲突的机会,所以用合适的素数作为M是很合适的。
2.MAD法: 除余法的改进,让散列有更高阶的均匀性。取M为素数,a>0,b>0,a%M!=0,hash(Key)=(a*Key+b)%M。视具体情况所定,有时候并不需要更高阶的均匀性。
3.平方去中法:Key平方后,取中间的几位作为散列地址。为何取中?平方可以看成位移后相加的操作,平方后的数的中间几位,是由Key的更多数位相加构成的,具有更多Key的特性,两侧则相反。所以平方后取中可以尽可能体现Key的特性,使得分布减少冲突。
4.折叠法:将Key折叠切割,如123456789可以将其地址转换为123+456+789=1368。当然还有更多的折叠方式,具体视情况而定。
5.伪随机数法: hash(Key)=rand(Key)=【rand(0)pow(a,Key)】%M。(伪)随机数发生器的实现,因具体平台,不同历史版本而异创建的散列表可移植性差,慎用。
总之,散列函数,越是随机,越是没有规律,越好。
6.字符串转化法(多项式法):有时候Key可能是一个字符串,用其来表示Key需要进行转化。如多项式法,把每个字符转化为数字,作为x0,x1,x2…xn,再确定一个常数a,求xpow(a,k)(0<=k<=n-1)的和,也就是多项式的和。那么我们再做两件事,加快多项式计算,就用秦九韶算法,再模仿这一算法,进行一个与多项式计算近似的一个运算。
int hashcode(char s[])
{
int h=0;
for(int strlen(s),i=0;i<n;++i){
h=(h<<5|h>>27);
h+=(int)s[i];}
return h;
}