一、哈希概述
Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。(by百度百科)
简单来说,哈希是一种映射的体现,可以说是数据离散化、map等等的同胞了。
主要原理就是把大范围映射到小范围,因此输入范围必须和小范围相当或者比它更小,否则增加冲突。
二、哈希表
哈希表是一种根据关键码去寻找值的数据映射结构,它将大型数据增加、删除、查找,每次操作算法复杂度均摊复杂度为o(n)。
举个形象的例子,我新建了一个通讯录:

假如说我要找xuao恩聊天,我会找x,假如我要打电话给dalao gmk 我会找d。
哈希表建立了一个映射关系f(key) =address,对于这个情景来说f(xuao恩)=x,f(dalao gmk)=d。
三、哈希函数
哈希函数(Hash Function),也称为散列函数或杂凑函数。哈希函数是一个公开函数
哈希函数是一个公开函数,可以将任意长度的消息M映射成为一个长度较短且长度固定的值H(M),称H(M)为哈希值、散列值(Hash Value)、杂凑值或者消息摘要(Message Digest)。
对于c++、noip使用来说,Hash函数具有如下特点:
易压缩:对于任意大小的输入,Hash值的长度很小,在实际应用中,Hash值长度是固定。
易计算:对于任意给定的消息,计算求其Hash值比较容易。
单向性:哈希函数逼近单向函数,所以可以用来对数据进行加密。(单项函数:如果某个函数在给定输入的时候,很容易计算出其结果来;而当给定结果的时候,很难计算出输入来)
抗碰撞性:理想的Hash函数是无碰撞的,但在实际算法的设计中很难做到这一点。
有两种抗碰撞性:一种是弱抗碰撞性,即对于给定的消息,要发现另一个消息,满足在计算上是不可行的;另一种是强抗碰撞性,即对于任意一对不同的消息,使得在计算上也是不可行的。
凭借上述规律,我们来解释一下内容
四、哈希算法
一般的说,Hash函数可以简单的划分为如下几类:
1. 加法Hash;
2. 位运算Hash;
3. 乘法Hash;
4. 除法Hash;
5. 查表Hash;
6. 混合Hash;
哈希种类多种多样,可以自行设计(只要你觉得自己很dalao)。
五、取余法
哈希表示一个索引1到m的数组,m根据你的空间限制自己决定。函数的值为[1,m]。
如果两个函数f(u1)==f(u2)这就产生了哈希冲突。
取余法是一种很简单的常用方法,即h(k)=k mod m,其中 seed 和 m 都是大小合适的质数,m就是hash表的大小。
int hash(string x)
{
int seed=131;
int m=10007;
int Hash=0;
int len=x.length()-1;
for(int i=0;i<=len;++i)
{
Hash=(Hash*seed+x[i]-'0')%m;//‘0’都可以
}
return Hash;
}
五、哈希冲突
为了方便理解,我再延续上面那个例子:
假如我又加了喜羊羊为好友,这可怎么办呢,f(xuao恩)=f(喜羊羊),我和xuao恩关系非常好,可不能将xuao恩挤掉,但我又想结识青青草原的大名人,冲突由此产生。
具体定义:在哈希表中,不同的关键字值对应到同一个存储位置的现象。即关键字K1≠K2,但f(K1)= f(K2)。均匀的哈希函数可以减少冲突,但不能避免冲突。发生冲突后,必须解决;也即必须寻找下一个可用地址。
六、解决冲突的方法(提供几个额外的思路)
1.直接定址法:
直接定址法是根据关键字得到的某一线性函数值作为散列表地址。f(key) = a*key+b,(其中a,b都为常数)
这种方法比较简单,但也有缺陷,这种定址方法需要事先知道关键字的分布情况,且查找时比较适合在表小且连续的表中查找。
2.除留余数法:
除留余数法是经常用来构造散列表的一种方法。看名字很容易想到这种定址方法就是:用关键字除某一个数p得到的余数作为散列表的地址,f(key) = key % p,(这里的p就为表的大小)。
但是这里又引来问题了,很可能有好几个关键字都映射在一个地址上,比如:当表长p为5,关键字key有1,15,26,30等时,1和26就都映射在散列地址为1的位置上,这种情况就是哈西冲突(底下有讲^-^)。
所以,这里选取表的大小p就显得尤为重要。一般来讲,p都尽量取素数值,这样能降低哈希冲突的概率,注意,哈希冲突是不能被避免的,任意的散列表都有可能存在哈希冲突!!!
3.数字分析法:
假如我们现在需要对某一公司员工进行登记,用他们的电话当作关键字来存储,由于电话号码中只有后四位才是真正的用户,那么很可能前7位数字都是一样的。
130 **** 1212
130 **** 2291
137 **** 0912
137 **** 9238
137 **** 1832
所以,我们就用后四位当作散列地址进行存储。当然,这样也会出现冲突,我们就可以对抽取的数字进行左环移位、右环移位、反转后者将前两位与后两位叠加的方法(如:1832----18+32=50)
如果事先知道关键字分布且关键字的若干位分布均匀时,就可以选用此方法。
4.折叠法:
首先,我们将关键字均匀分割成位数相等的几组(最后一组假如位数不够也可以短一些),然后将这几组数进行相加,得到和以后,根据表的大小取后几位作为散列地址。
例如:现已知表长为3,有关键字:9876543210,我们将其分割称987,654,321,0四组,987+654+321+0=1962,我们就取962为散列地址。
这种方法事先不需要知道关键字的分布情况,且关键字位数较大时适合用折叠法。
5.平方取中法:
如现在有关键字1234,我们将1234取平方后得到1522756,我们就取中间的三位227作为其散列地址。如果关键字时4321, 4321^2=18671041,我们就可以取671或者710作为其散列地址。
用这种方法时事先不需要知道关键字的分布情况,且当关键字位数又不是很大!
6.随机数法:
随机数法需要用到随机数函数random,即 f(key) = random(key)
当关键字的长度不等时可以采用这种方法构造散列表。。
7、bkdr方法:
int BKDR(char s[20]){
int seed=131;
int Hash=0,i=0,Len=strlen(s);
while(i<Len)Hash=Hash*seed+str[i++];
return (Hash%Max);//保证Hash值在[0,Max)之间
}
本文介绍了哈希的基本概念,强调它是数据离散化的映射体现,以及哈希表作为数据映射结构的高效性。讨论了哈希函数的特性,包括易压缩、易计算、单向性和抗碰撞性。此外,文章还涵盖了常见的哈希算法类型,并详细阐述了取余法作为构建哈希表的简单策略。哈希冲突的定义被提出,同时提到了多种解决冲突的策略,如直接定址法、除留余数法、数字分析法、折叠法、平方取中法和随机数法。
1748

被折叠的 条评论
为什么被折叠?



