散列表是实现字典操作的一种有效的数据结构,尽管在最坏情况下查找一个元素时间和链表相同。但实际应用中,散列表的性能是极好的,合理情况下,平均时间复杂度为O(1)。
typedef struct Datatype {
int key;
USER_TYPE value;
}Datatype;
typedef struct HashTable {
int times;
Datatype data;
}HashTable;
散列表是一个在时间和空间上做出权衡的数据结构。如果空间允许,可以把每个关键字当做数组下标;如果时间允许,不考虑时间消耗的话,顺序查找即可。散列表恰恰值介于这二者之间,散列表使用一个长度与实际存储数目成比例的数组来存储。
常见的构造哈希函数是取余构造,就是把余数当做哈希值,除此之外还有:
int HashFunc(int key) {
//除留取余的构造哈希函数方法
return key % HashTableSize;
}
平方取中散列法:通过次方放大关键字,再取中间几位作为哈希值,通过次方放大关键字减少各关键字之间的碰撞。
基数转换法:把十进制数看成其他进制,再按照这个进制转换为十进制,提取若干位作为哈希值。
在构造散列表的时候,通过哈希函数计算哈希值,如果出现关键字不同但是哈希值相同时,就是产生哈希碰撞,需要给出解决哈希碰撞的方案。解决哈希碰撞的方法有:
1、链地址法:把一个哈希值产生冲突关键字放进一个链表里面,某个哈希值产生冲突了,就把这个关键字放到这个哈希值槽的链表里面。Java中的HashMap就是这样解决冲突的,不过现在一旦同一个哈希值冲突超过7个还是8个就会把链表转换为红黑树,查找的时候效率更高。
2、开放寻址法:大体的思想是一旦产生哈希碰撞,接着找一下空的哈希槽位,直到找到合适的位置,其中方法包括:线性探查,二次探查、伪随机探查。
线性探查就沿着产生冲突的位置接着找它紧挨着下一个,直到不冲突了。
二次探查看上去像在冲突的位置左右跳跃的找不冲突的位置。
伪随机探查:建立一个随机数发生器,并给定一个随机数作为起始点,在冲突的位置借助这个随机数发生器生成随机数,去探查下一个合适的位置。
3、完全散列(算法导论156页):我的理解是散列的散列来解决冲突,不用链表也不用线性再探测找一个不冲突的位置。每一个哈希桶里面放的还是一个散列表,第一次算出在外层哪个哈希桶里面,接着计算在这个哈希桶的散列的第几个。重点就砸要保证第二次的散列计算不产生冲突,书里面写了一堆证明,生日悖论、马尔科夫啥的,我没看懂。这种二级散列可以用指针数组做到,数组(一级散列表)中每一个指针指向一个一维数组(二级散列表)