散列表

来源:互联网 发布:强制性脊柱炎 知乎 编辑:程序博客网 时间:2024/05/06 01:20

          散列表(hash table)是实现字典操作的一种有效数据结构,尽管最坏的情况下散列表中查找一个元素与链表中查找的时间相同,达到了时间复杂度:n。但在合理的假设下,在散列表中查找一个元素的平均时间是 :1;散列表成为数组寻址的一种有效替代,因为散列表使用一个长度与实际存储的关键字数目成比例的数组来存储。因此,有时候通过散列函数计算出的下标,可能在多个关键字上产生冲突,因此就需要有效地解决冲突的方法。JAVA中的map  set容器的hash结构,具有所有存储方式中最快的查找速度。

 1.直接寻址表

     当关键字的全域比较小时(即最大值(上界)较小时),直接寻址是一种简单有效的技术。假设某动态集合中,每一个元素都是取自于全域U={0,1,2……,m-1}的一个关键字,这里m不是一个很大的数,否则会浪费很多空间。并且假设两个元素没有相同的关键字。我们用一个数组T[0……m]来表示一个直接寻址表,表中的每一个位置称为槽,对应全域中的一个关键字,没有存放关键字的槽为NIL。如图:

其字典操作非常简单:


search(T,k)//查询{     return T[k];}insert(T,x)//插入{     T[x.k]=x;}delete(T,k)//删除{   T[k]=null;}
2.散列表

      直接寻址简单,但是当全域很大时,就会面临他的缺陷。在散列方式下,钙元素存在槽h(k),中h为将key映射入散列表(T[0……m-1])的函数,由此计算出关键字存储在槽中的位置。这里散列表的大小m一般要比|U|小的多,h(k)是k的散列值,散列函数减少了数组的大小。

    因为|U|>m,加上散列函数的映射,肯定会多个关键值存放在相同槽中,在同一个槽中的key可能相同,也可能不同主要解决冲突的方法有链表法和开放寻址法。

2.1散列函数

     好的散列函数能满足简单均匀假设,每个关键字都能等可能的散列到m个槽中的任何一列。我们一般遇到的关键字都是自然数,当不是自然数的时候,要将关键字进行转换。常见的散列方法:

2.1.1乘法散列

       构造散列函数的乘法散列包含两个步骤。第一步:用关键字k乘上常数A(0<A<1),并提取KA的小数部分。第二部,用m乘以这个值,在向下取整。总之散列函数为:

          这里的kA mod 1是取kA的小数部分,即

  乘法散列的m的值一般选择他为2的某个幂次。

2.1.2除法散列

         通过取k除以m的余数,将关键字k映射到m个槽中的某一个上,即散列函数为:

                                            h(k)=k mod m

着里需要注意的地方就是避免m的某些值,例如m不应为2的幂,一个不太接近2的整数幂的素数,常常是m的一个较好的选择。

2.1.3全域散列法

       为了避免出现,任何一个特定的散列函数都可能出现最坏的情况,采用一种有效的解决方法是随机选择散列函数,使之独立于要储存的关键字。这种方法称为全域散列。

3.链表法

    链表法即散列到同一个槽中的元素都存放在一个链表中,槽j中有一个指针指向存放j槽中所有元素组成的链表的表头,如果槽中没有元素,则为NIL,使用链表解决冲突后,insert delete和search操作就和在链表中操作一样,插入元素时,有时还要进行必要的查找操作,查找操作的运行时间与表的长度成正比。,如果为双向链表则进行操作会比较方便。

一个能存放n个元素的、具有m个槽位的散列表T,定义T的装载因子为 :α=n/m,即一个链表的平均存储元素数。

链表法散列的分析

性质:1.在简单均匀散列的假设下,对于用链表法解决冲突的散列表,一次不成功查找的平均时间为 1+α

           2.在简单均匀散列假设下,对于用链表法解决冲突的散列表,一次成功的查找所需的平均时间为 :1+α。

      散列表槽中的链表可以使用未占用的槽位进行分配,首先初始化将所有的槽链成自由链表,然后每次查无元素的时候进行分配插入,注意当一个元素计算出的散列地址处的key,通过h(k)不能映射进这个槽时,要把这个元素一出去,换到另一个空槽,再把将要插入的元素存放于此槽中。


4.开放寻址法

          此方法与链表法不同,链表法所以的元素并不是都存储在散列表中,冲突的元素都靠一个链表连接起来,而开放散列中,所有的元素都在散列表中,所以m>=n,装载因子绝对不会超过1.开放寻址法的相关代码:

insert(){    i=0;   repeat    j=h(k,i);    if(T[j]==NIL)       T[j]=k;       return   j;    else  i++;    until   i==merror   "hash table overflow"}search(T,k){    i=0;    repeat       j=h(k,i)       if(k==T[j])            return j;       else i++     until   <span style="color:#FF0000;">T[j]==NIL</span> or i==m//注意这里的条件当查找完所有的曹后或者遇到第一个槽                                //      为空的时候退出,如果含有此元素,肯定在这个槽中。插入也是在                  //先散列的空槽中插入,后面的盛放此关键字的槽肯定也为空,要想节省时间必须很了解运行的性质。   return  NIL;}
   对与删除操作,在开放寻址中有个缺点,当删除一个元素后,可能就不能查找到存在的元素了,遇到空的就停止了。这时可以加入一个删除标记,NIL与删除标志有区别,插入时删除标记与NIL一样。在必须删除元素的情况下,常见的做法是采用链表法解决冲突。因为开放寻址法当加入删除标志后查找时间就不依赖于删除标志了。

4.1线性探查

     散列函数:

      

         存在问题:一次群集,查找时间随着连续被占用的槽不断增加,平均查找时间不断增加。

4.2二次探查

      

                问题:二次群集。这是一种轻度的群集。

4.3双重散列

   

      为了查找整个散列表,值必须与表的大小互素。有一种简单的方法保证这个条件就是,取m为2的幂,并设计一个总产生基数的。另一种是取m为素数,并设计一个总是返回较m小的正整数的函数

双重散列非常接近理想 的均匀散列的性能。


    5.完全散列(再散列)

          完全散列的结构与链表法解决冲突的数据结构很像,只不过并不是使用的链表,存放冲突元素的是数组,数组再采用散列的方式,是二次散列列表。采用两级散列的方法二级散列表的长度为  这个槽中的元素个数平方。利用此方法可以确保不会产生冲突。此结构可以用二维数组实现。利用粗糙数组的方式int a[][]=new int[2][];
        a[0]=new int[2];a[0]其实就是指向一维数组的引用。二维的需要几个长度就分配几个长度,每一维上的再分配的长度可以不一样。

0 0
原创粉丝点击