散列表的概念:就是将所有可能出现的关键字的集合U(全集)映射到一个表T[0..m-1]的下标集上,这个表就是散列表。
2.而关键字与这个表地址之间以什么样的关系发生联系呢,这就要通过一个函数来建立,这个函数是以U中的关键字为自变量,以相应结点的存储地址为函数值,它就称为散列函数。将结点按其关键字的散列地址存储到散列表的过程称为散列。
3.根据某种散列函数,一个关键字的散列函数值是唯一的,但是有可能两个或多个不同关键字的函数值是相同的,这时就会把几个结点存储到同一个表位置上,这时就造成冲突(或碰撞)现象,这两个关键字称为该散列函数的同义词。
要完全(不是"安全")避免冲突需满足两个条件,一是关键字集合U不大于散列表长m,另一个是选择合适的散列函数,如果用h(ki)=0)这样的函数的话,看看有什么结果。
4.通常情况下U总是大大于m的,因此不可能完全避免冲突。冲突的频繁程度还与表的填满程度相关。装填因子α表示表中填入的结点数与表长的比值,通常取α≤1,因为α越大,表越满,冲突的机会也越大。
5.散列函数的选择有两条标准:简单和均匀。看看h(ki)=0这样的函数,简单是简单,但绝不均匀。
6.下面是常见的几种散列函数的构造方法:
(1)平方取中法
(2)除余法:它是用表长m来除关键字,取余数作为散列地址。若选除数m是关键字的基数的幂次,就会使得高位不同而低位相同的关键字互为同义词。因此最好选取素数为除数.
(3)相乘取整法:有两个步骤,先用关键字key乘上某个常数A(0)
(4)随机数法,此法以关键字为自变量,通过一随机函数得到的值作为散列地址。
7.处理冲突的方法:当不可避免发生冲突时,就必须对冲突加以解决,使发生冲突的同义词能存储到表中。
8.通常有两类方法处理冲突:开放定址法和拉链法。前者是将所有结点均存放在散列T[0..m-1]中,后者是将互为同义词的结点链成一个单链表,而将此链表的头指针放在散列表中。
9.开放定址法的一般形式为:hi=(h(key)+di)%m 1≤i≤m-1
10.开放定址法要求散列表的装填因子α≤1。开放定址法又有线性探查法、二次探查法和双重散列法之分。
(1)由于线性探查法在构造散列表时,遇到冲突(有同义词)的时候会按探查序列向后面的空地址插入,从而使原来应插入到此位置的结点又与它发生冲突,当一连串的位置均已有结点时,本应插入到这些位置的结点又只能将其插入到更后面的同一个空结点上,这种散列地址不同的结点争夺同一个后继散列地址的现象就是聚集或堆积。(注意,同义词发生冲突不是堆积)
为了减小堆积现象的发生,可以用二次探查法和双重散列法进行探查。
(2)拉链法解决冲突的做法是,将所有关键字为同义词的结点链接在同一个单链表中。
11.与开放定址法相比,拉链法有如下几个优点:
(1)拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;(简单无堆积)
(2)由于拉链法中各链表上的结点空间是动态申请的,故它更适于造表前无法确定表长的情况;(动态申表长)
(3)开放定址法为减少冲突要求装填因子α较小,当结点规模较大时会浪费很多空间,拉链法中α可以大于1,且结点较大时,其指针域可忽略不计,因此节省空间;(空间可节省)
(4)拉链法构造的散列表删除结点易实现,而开放定址法中则不能真正删除结点只能做删除标记。(删除易实现)
12.拉链法也有缺点:当结点规模较小时,用拉链法中的指针域也要占用额外空间,还是开放定址法省空间。
13.在散列表上的运算有查找、插入和删除,主要是查找。这三个操作的算法并不复杂,也容易理解。关于查找操作的时间性能,可看教材p202的表9.1。由表可见,散列表的平均查找长度不是结点个数n的函数,而是装填因子α的函数。α越小,冲突的概率越小,但空间的浪费将增加,当α大小合适时,散列表上的平均查找长度就是一个常数,时间性能是O(1).