数据结构--散列总结

前言

最近学习数据结构,参考数据是《数据结构与算法分析》。现在总结自己的知识点

散列

散列也叫做哈希,根据英语单词hash英译过来。散列相对于数组而言,数据项存储在空间中的位置是根据散列码来决定,而数组则是根据数据项的顺序对应到内存空间。散列解决数组删除、访问、查询需要大量时间的问题,而散列则是固定时间,每次对数据项的获取,删除都通过哈希码来获取对应数据项。

散列函数

在现有的Java API中,散列方法2种,如果数据成员类型是数据,通过位运算得到散列码。

    private static int secondaryHash(int h) {
        // Spread bits to regularize both segment and index locations,
        // using variant of single-word Wang/Jenkins hash.
        h += (h <<  15) ^ 0xffffcd7d;
        h ^= (h >>> 10);
        h += (h <<   3);
        h ^= (h >>>  6);
        h += (h <<   2) + (h << 14);
        return h ^ (h >>> 16);
    }

使用位运算来得到hash码的好处是运算简单,减少计数量。而如果是对象,那么根据对象的hash码来进行上面的位运算得到。

另外hash表中的对象最好实现hashcode方法。由于String.java是final类,它已经实现了hashCode方法。遍历每一个字符,上一次的hashcode乘以素数31加上字符的编码值。由于String的字符数组不会改变,所以缓存了hashCode,避免再次运算。

    @Override public int hashCode() {
        int hash = hashCode;
        if (hash == 0) {
            if (count == 0) {
                return 0;
            }
            for (int i = 0; i < count; ++i) {
                hash = 31 * hash + charAt(i);
            }
            hashCode = hash;
        }
        return hash;
    }

一般散列表初始大小为素数,将数据项的hash值对散列表求余,余数为数据项在表中的位置。为什么散列表的size为素数呢,因为求余时减少余数为空的几率,比如表的大小为8,那么散列码是8、16、32,余数都是零,在散列表中数据会冲突。
不同的散列码求余后还是有可能得到相同的余数,解决这种现象的方法有多种:分离连接法,开放定址法。

分离连接法

是java集合类中实现的方法,在同一个地址上,采用链表组织数据,新添加的数据在链表头部,因为新添加的数据往往更容易被访问。如果在散列地址上已经存在数据,通过一个链表,把相同散列地址的数据项归并起来。在查找的时候,依次比对关键字是否相同。
#####开放定址法
解决的思路是,在散列地址上发现已经有数据,那么此时再次去寻找下一个空闲的散列位置。探测方法有下面几种:

  • 线性探测法
    此探测法是在遇到冲突的散列地址时,直接找下一个地址,如果地址可用则将数据插入到此位置,如果不可用,则继续一直找到为止.比如当前散列地址是7,判断8位置是否可用,可用则插入到8位置,否则判断9位置,如果到最末尾,则重地址0开始.
  • 平方探测法
    是在上面的线性探测法的基础上,探测的位置为下下个位置,如果当前7位置冲突,则探测9位置.
  • 双散列
    在遇到冲突时,通过对散列码进行再次求余,得到散列地址.第一散列地址是数组的大小(素数),第二次散列地址是用小于此素数的另外因个素数. 比如第一次用17散列地址,第二次用11去得到散列地址.此种方式很难去预料,第二次散列地址的状态,不能确定还会冲突.

再散列

当散列地址用完,数组需要扩大容量,其中已有的数据项要再次分配散列地址.为了防止频繁再散列,散列后的表大小为原大小的2倍左右.在散列时机有三种,A当插入失败,B当占用大小超过一半时.C当已用散列地址超过一个比例值时.

归结

  • 散列表的目的,使访问在一个常数下.
  • 解决散列时遇到的冲突.

你可能感兴趣的:(数据结构,算法,数据结构,哈希表,散列,散列探测)