《大话数据结构》第8章.查找——散列表(哈希表)
2013-02-28 18:27
302 查看
散列表(哈希表)
关键词:冲突、同义词、堆积、散列函数、开放地址法、链地址法、除留余数法
核心思想:散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。不需要比较就可获得需要的记录的存储位置。查找时,根据这个确定的对应关系找到给定值key的映射f(key),若查找集合中存在这个记录,则必定在f(key)的位置上
时间复杂度:插入、删除、搜索 O(1) (在没有冲突,或很少冲突情况下)
(如果没有冲突的情况下。但没有冲突只是一种理想,在实际应用中,冲突是不可避免的。不同处理冲突的方法会影响平均性能。比如线性探测处理冲突可能会产生堆积,显然没有二次探测好,而链地址法处理冲突不会产生任何堆积,因为具有更佳的平均查找性能)
what散列函数(哈希函数):我们把这种对应关系f称为散列函数,又称为哈希(Hash)函数。
what散列表(哈希表):采用散列技术将记录存储在一块连续的存储空间中,这种连续存储空间称为散列表或哈希表(Hash table)
输入限制:
键值必须唯一(sgi stl中的hashtable将键和值合为一个参数传入,比如iht.isert_unique(obj),然后算法内部通过get_key(obj)取得键值。而c#和java中hashtable添加元素时,键和值分为两个参数传入,iht.Add("a",obj))
散列表查找步骤:
1)在存储时,通过散列函数计算记录的散列地址,并按此散列地址存储该记录。
2)当查找记录时,通过同样的散列函数计算记录的散列地址,按此散列地址访问该记录。由于存取用的是同一个散列函数,因此结果当然也是相同的
所以说,散列技术既是一种存储方法,也是一种查找方法。然而它与线性表、树、图等结构不同的是,前面几种结构,数据元素之间都存在某种逻辑关系,可以用连线图表示出来,而散列技术的记录之间不存在什么逻辑关系,它只与关键字有关联。因此,散列主要是面向查找的数据结构。
散列技术最适合求解的问题是查找与给定值相等的记录。对于查找来说,简化了比较过程,效率就会大大提高。但散列技术不具备很多常规数据结构的能力:
1)比如同样的关键字,能对应很多记录的情况
2)散列表也不适合范围查找
how如何设计散列函数,为了尽量减少冲突,减少插入和查找时的键比较:
A)设计一个简单、均匀、存储利用率高的散列函数是散列技术中最关键的问题
B)如何处理冲突。两个关键字key1!=key2,但是却有f(key1)==f(key2),这种现象称为为冲突(collision),并把key1和key2称为散列函数的同义词(synonym)。现实应用中,冲突是无法避免的,如何处理冲突是一个很重要的课题。
A:设计好的散列函数的2个原则:
1)计算简单。(简单)
复杂的散列函数即便设计优秀,不会产生冲突,但同时大大降低了插入记录,查找记录的效率,也是不提倡的。散列函数的计算时间不应该超过其他查找技术与关键字比较的时间。
2)散列地址分布均匀。(均匀、存储利用率高)
前面提到冲突带来的问题,最好的办法就是尽量让散列地址均匀地分布在存储空间中,这样可以保证存储空间的有效利用,并减少为处理冲突而耗费的时间。
设计好的散列函数要考虑的几个因素
1.关键字的分布情况 2.关键字的长度 3.计算散列地址所需的时间 4.散列表大小 5.记录查找的频率
几种常用的散列函数构造方法:
直接定址法
数字分析法
平方取中法
折叠法
除留余数法
随机数法
1)直接定址法
核心思想:取关键字的某个线性函数值为散列地址,即f(key)=a*key+b (a,b为常数)
适用环境:这种散列函数优点是简单、均匀、也不会产生冲突,但问题是这需要先知道关键字分布情况,适合查找表较小且连续的情况。由于这样的限制,在现实应用中,此方法虽然简单,但却不常用
2)数字分析法
核心思想:抽取。抽取方法是使用关键字的一部分来计算散列存储位置的方法,这在散列函数中是常常用到的手段。
适用环境:数字分析法适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑用这个方法。
3)平方取中法
核心思想:比如关键字是1234,那么它的平方就是1522756,再抽取中间的3位就是227用作散列地址。
适用环境:平方取中法比较适合于不知道关键字的分布,而位数又不是很大的情况。
4)折叠法
核心思想:将关键字从左到右分割成位数相等的几部分(注意最后一部分位数不够时可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。
适用环境:折叠法事先不需要知道关键字的分布,适合关键字位数较多的情况。
举例:关键字是9876543210,散列表表长为3位,我们将它分为4组,987|654|321|0,然后将它们叠加求和987+654+321+0=1962,再求后3位得到散列地址为962
有时可能这还不能保证分布均匀,不妨从一端向另一端来回折叠后对齐相加。比如我们将987和321反转,再与654与0相加,变成789+654+123+0=1566,此时散列地址为566
5)取模求余(除留余数法)(最常用的构造散列函数法)
核心思想:对于散列表长为m的散列函数公式:f(key)=key mod p (p<=m),f(key)属于[0,p-1]
这方法不仅可以对关键字直接取模,也可在折叠、平方取中后再取模。
本方法的关键在于选择合适的p,p如果选得不好,就可能会容易产生同义词
若散列表表长为m,通常p为小于或等于表长(最好接近m)的最小质数或不小于20质因子的合数
6)随机数法
核心思想:选择一个随机数,取关键字的随机函数值为它的散列地址。也就是f(key)=random(key)。这里random是随机函数。
适用环境:当关键字长度不等时,采用这个方法构造散列函数是合适的
how如何处理散列冲突
开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
开放定址法的4钟常用方法:线性探测法、二次探测法、再hash法、随机探测法
线性探测法: fi(key)=(f(key) + di) MOD m (di=1,2,3,......,m-1,m为散列表表长)
本来都不是同义词的关键字却需要争夺一个地址的情况,我们称这种现象为堆积。很显然,堆积的出现,使得我们需要不断处理冲突,无论是存入还是查找效率都会大大降低。
二次探测法: fi(key)=(f(key) + di) MOD m (di=1^2,-1^2,2^2,-2^2,...,q^2,-q^2,q<=m/2,m为散列表表长)
关键词:冲突、同义词、堆积、散列函数、开放地址法、链地址法、除留余数法
核心思想:散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。不需要比较就可获得需要的记录的存储位置。查找时,根据这个确定的对应关系找到给定值key的映射f(key),若查找集合中存在这个记录,则必定在f(key)的位置上
时间复杂度:插入、删除、搜索 O(1) (在没有冲突,或很少冲突情况下)
(如果没有冲突的情况下。但没有冲突只是一种理想,在实际应用中,冲突是不可避免的。不同处理冲突的方法会影响平均性能。比如线性探测处理冲突可能会产生堆积,显然没有二次探测好,而链地址法处理冲突不会产生任何堆积,因为具有更佳的平均查找性能)
what散列函数(哈希函数):我们把这种对应关系f称为散列函数,又称为哈希(Hash)函数。
what散列表(哈希表):采用散列技术将记录存储在一块连续的存储空间中,这种连续存储空间称为散列表或哈希表(Hash table)
输入限制:
键值必须唯一(sgi stl中的hashtable将键和值合为一个参数传入,比如iht.isert_unique(obj),然后算法内部通过get_key(obj)取得键值。而c#和java中hashtable添加元素时,键和值分为两个参数传入,iht.Add("a",obj))
散列表查找步骤:
1)在存储时,通过散列函数计算记录的散列地址,并按此散列地址存储该记录。
2)当查找记录时,通过同样的散列函数计算记录的散列地址,按此散列地址访问该记录。由于存取用的是同一个散列函数,因此结果当然也是相同的
所以说,散列技术既是一种存储方法,也是一种查找方法。然而它与线性表、树、图等结构不同的是,前面几种结构,数据元素之间都存在某种逻辑关系,可以用连线图表示出来,而散列技术的记录之间不存在什么逻辑关系,它只与关键字有关联。因此,散列主要是面向查找的数据结构。
散列技术最适合求解的问题是查找与给定值相等的记录。对于查找来说,简化了比较过程,效率就会大大提高。但散列技术不具备很多常规数据结构的能力:
1)比如同样的关键字,能对应很多记录的情况
2)散列表也不适合范围查找
how如何设计散列函数,为了尽量减少冲突,减少插入和查找时的键比较:
A)设计一个简单、均匀、存储利用率高的散列函数是散列技术中最关键的问题
B)如何处理冲突。两个关键字key1!=key2,但是却有f(key1)==f(key2),这种现象称为为冲突(collision),并把key1和key2称为散列函数的同义词(synonym)。现实应用中,冲突是无法避免的,如何处理冲突是一个很重要的课题。
A:设计好的散列函数的2个原则:
1)计算简单。(简单)
复杂的散列函数即便设计优秀,不会产生冲突,但同时大大降低了插入记录,查找记录的效率,也是不提倡的。散列函数的计算时间不应该超过其他查找技术与关键字比较的时间。
2)散列地址分布均匀。(均匀、存储利用率高)
前面提到冲突带来的问题,最好的办法就是尽量让散列地址均匀地分布在存储空间中,这样可以保证存储空间的有效利用,并减少为处理冲突而耗费的时间。
设计好的散列函数要考虑的几个因素
1.关键字的分布情况 2.关键字的长度 3.计算散列地址所需的时间 4.散列表大小 5.记录查找的频率
几种常用的散列函数构造方法:
直接定址法
数字分析法
平方取中法
折叠法
除留余数法
随机数法
1)直接定址法
核心思想:取关键字的某个线性函数值为散列地址,即f(key)=a*key+b (a,b为常数)
适用环境:这种散列函数优点是简单、均匀、也不会产生冲突,但问题是这需要先知道关键字分布情况,适合查找表较小且连续的情况。由于这样的限制,在现实应用中,此方法虽然简单,但却不常用
2)数字分析法
核心思想:抽取。抽取方法是使用关键字的一部分来计算散列存储位置的方法,这在散列函数中是常常用到的手段。
适用环境:数字分析法适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑用这个方法。
3)平方取中法
核心思想:比如关键字是1234,那么它的平方就是1522756,再抽取中间的3位就是227用作散列地址。
适用环境:平方取中法比较适合于不知道关键字的分布,而位数又不是很大的情况。
4)折叠法
核心思想:将关键字从左到右分割成位数相等的几部分(注意最后一部分位数不够时可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。
适用环境:折叠法事先不需要知道关键字的分布,适合关键字位数较多的情况。
举例:关键字是9876543210,散列表表长为3位,我们将它分为4组,987|654|321|0,然后将它们叠加求和987+654+321+0=1962,再求后3位得到散列地址为962
有时可能这还不能保证分布均匀,不妨从一端向另一端来回折叠后对齐相加。比如我们将987和321反转,再与654与0相加,变成789+654+123+0=1566,此时散列地址为566
5)取模求余(除留余数法)(最常用的构造散列函数法)
核心思想:对于散列表长为m的散列函数公式:f(key)=key mod p (p<=m),f(key)属于[0,p-1]
这方法不仅可以对关键字直接取模,也可在折叠、平方取中后再取模。
本方法的关键在于选择合适的p,p如果选得不好,就可能会容易产生同义词
若散列表表长为m,通常p为小于或等于表长(最好接近m)的最小质数或不小于20质因子的合数
6)随机数法
核心思想:选择一个随机数,取关键字的随机函数值为它的散列地址。也就是f(key)=random(key)。这里random是随机函数。
适用环境:当关键字长度不等时,采用这个方法构造散列函数是合适的
how如何处理散列冲突
开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
开放定址法的4钟常用方法:线性探测法、二次探测法、再hash法、随机探测法
线性探测法: fi(key)=(f(key) + di) MOD m (di=1,2,3,......,m-1,m为散列表表长)
本来都不是同义词的关键字却需要争夺一个地址的情况,我们称这种现象为堆积。很显然,堆积的出现,使得我们需要不断处理冲突,无论是存入还是查找效率都会大大降低。
二次探测法: fi(key)=(f(key) + di) MOD m (di=1^2,-1^2,2^2,-2^2,...,q^2,-q^2,q<=m/2,m为散列表表长)
相关文章推荐
- 大话数据结构—散列表查找(哈希表)
- 大话数据结构—散列表查找(哈希表)
- 查找 之 散列表查找(哈希表)
- 散列表(哈希表)查找 - 数据结构和算法83
- 哈希表(散列表)查找
- 【学习笔记----数据结构26-散列表查找(哈希表)】
- 《大话数据结构》 查找 以及一个简单的哈希表例子
- 27. 散列表(哈希表)查找
- 查找--- 哈希表( 散列表)
- 散列表(哈希表)查找
- 散列表(哈希表(散列函数构造、处理冲突、查找)
- 查找——散列表查找(哈希表)
- 【哈希表】散列表查找--避免冲突的两种解决方法程序实现
- 哈希表(散列表)查找的详解
- 数据结构_____散列表查找(哈希表)
- 数据结构之查找(七)——散列表查找(哈希表)
- 数据结构之哈希表(散列表查找)
- 数据结构之哈希表查找
- 数据结构实验之查找五:平方之哈希表
- 哈希表(散列表)概述