之前看过,今天又看了一下。为了避免以后再看,今天记录一下。
1. 散列就想是一个字典,由一个一个的名值对。对于一个动态的集合进行管理时,之所以选择散列,就是为了提高效率。进行的操作主要有插入,查找和删除。如果我们直接用顺序表(数组或者链表),基本查找的时间为O(n),插入删除的或为O1或为O(n).而如果使用散列的话,基本都可以做到O1.其带来的效率真的是很高的。
2.散列主要是涉及到散列函数。给定一个对象,我们通过散列函数将其映射到内存的某个区域中。对散列函数的要求是尽量使散列结果均匀分布,且尽量减少冲突。
3 常用的散列函数包括以下几种:
3.1 平方取中法。所谓平方,是指对被映射的对象进行平方操作。所谓取中,是指平方后,一般中间的几位,是对原来的数的每位都有影响的(一般而言),可以简单代表。然后取这r位中间的数字,就可以确定其值应该在的位置了。设w是计算机的字长,取r位可以让该数右移(w-r)即可。
3.2质数求余法。f(x)=x%M. 其中x是被散列的关键字。M是可供使用的内存的大小。M的选取很关键。最好为素数,且kruth已经证明了选择素数的好处。也叫除法散列法。
3.3乘法散列法。H(k)=(Ak mod 2^w) rsh(w-r). A是一个常数值,选取具有一定的技巧。w是计算机的字长。 k是要散列的数字。总体的意思是通过把关键字扩大A倍,然后取余,然后保留玉树的r位,作为结果。
4溢出处理。常见的溢出处理有两种,一是拉链法,二是开放地址法。
4.1.拉链法,就是当遇到冲突时,可以这个节点后面加一条连,将所有映射到这个节点的数附加到链的后面。当然了,这个时候,就需要考虑散列表的长度和链的长度了。哈希表越长,链可能就越短(如果散列函数选取的正确的话);哈希表越短,链的长度可能就越长,最终退化成为一条,也就是一个数组了,起不到作用了。今天我还在想,其实表长和链长就是一个博弈的过程,此消彼长。用这个模型来存储分类的结果,还是很不错的。哈哈。
4.2开放地址法。这个方法适合于哈希表长度比较长,填充率比较低的情况。当某个k映射到某个位置l后,l被填充了,产生冲突,于是,我们就找l的下一个位置。直到找到要给为空的位置为止(或者表满了)。具体做法,我们对哈希函数做一个扩充,添加一个参数,由h(k)变成h(k,i),其中,i表示第i次探查。这样在插入时,最多可以插入i=m次,除非表满了。
查找时,一次查找,直到找到一个空内存,表示该条目真的不在表中,否则应该可以找到,然后进行比对就可以了。
删除比较难操作,因为删除一个节点以后,可能会是某些节点明明在表中却找不到。
改造散列函数时,一般有三种方法:1线性探查法 h(k,i)=(h(k) +i ) mod m
2二次探查法h(k,i)=(h(k) +c1i+c2*i^2 ) mod m
3双重散列法h(k,i)=(h1(k) +i*h2(k) ) mod m
经过分析,发现平均探查时间为 1/(1-a),a是该表的填充率。
接下来是代码部分。包括哈希表的初始化,简单的hash函数的实现,以及两种解决冲突的方法的简单实现。后面会继续介绍一下动态散列的内容。