散列表是表示集合和字典的一种有效方法,它提供一种完全不同的存储和搜索方式,通过将关键码映射到表中的某个位置来存储元素,然后根据关键码用同样的方式直接访问。
散列表与散列方法
理想的搜索方法是不经过任何比较,一次直接从字典中得到要搜索的元素。如果在元素的存储位置与它的关键码之间建立一个确定的对应函数关系Hash(),使得每个关键吗与结构中的一个唯一的位置相对应:Adress=Hash(key).在插入的时候,一次函数计算存储位置并且按此位置存放。在搜索的时候,根据关键吗进行同样的函数计算,把求得的函数值当作元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。这种方法就是散列方法,在散列方法中使用的转换方法叫做散列函数。而按此想法构造出来的表或者结构就叫做散列表。
事实上,通过散列函数建立了从元素关键码集合到散列表地址集合的一个映射。一般来说,散列函数是一个压缩映像函数。通常关键码集合比散列地址集合大得多。因此很容易产生冲突。称这些散列地址相同的不同关键码为同义词。
散列函数
1.除留余数法;
设散列函数中允许的地址数为m,去一个不大于m,但是最接近或等于m的质数p作为除数,利用以下公式把关键吗转换成散列地址。散列函数为:hash(key)=key%p;要求这时的质数p不是接近2的幂。如果选p为2的幂,那么散列函数计算出来的地址就是Key的最低i位二进制数字。如果是十进制数字,那么p也应该避免取10的次幂,原因相同,这样会导致得到的地址过于集中,导致冲突增多。
给出一个数m,求p的代码:
public class Test { /** * @param args */ public static void main(String[] args) { int max = maxPrime(10000); System.out.println(max); } public static int maxPrime(int sum){ int result = 0; for(int i = sum; i > 1; i--){ if(isPrime(i)){ result = i; break; } } return result; } public static boolean isPrime(int number){ if(number % 2 == 0 && number != 2) return false; boolean result = true; int temp = (int)Math.sqrt(number)+1; for(int i = 3; i < temp; i = i+2){ if(number % i == 0){ result = false; break; } } return result; } }
2.数字分析法;
3.平方取中法;
4.折叠法;
处理冲突的闭散列方法
解决冲突的方法又称为溢出处理技术。为了减少冲突,我们对散列表加以改造,若设散列表HT有m个地址,将其改造为m个桶,其桶号与散列地址一一对应。如果两个不同的元素的关键码用散列函数计算得到的散列地址相同,就产生了冲突,它们可以放在同一个桶内的不同位置。只有当桶内所有的s个元素的位置都放满元素后再加入元素才会产生溢出。通常桶的大小s取的比较小,因此在桶内大多采用顺序搜索。
处理冲突的一种常用方法就是闭散列,也叫做开地址法。所有的桶都直接放在散列表数组中,并把该数组组织成环形结构,如果遇到冲突,那么就去寻找下一个空桶,直到所有的桶全满为止。找下一个空桶的方法很多:
1.线性探查法;
2.二次探查法;
3.双散列法;
开散列地址方法(又称为链地址法),就是将冲突的元素放在一个桶里面,使用链表将它们连接起来
根据试验结果分析开散列法优于比散列法;在散列函数中,用除留余数法做散列函数优于其它类型的散列函数,最差的是折叠法。