数据结构基础:P11.2-散列查找--->散列函数的构造方法

本系列文章为浙江大学陈越、何钦铭数据结构学习笔记,前面的系列文章链接如下
数据结构基础:P1-基本概念
数据结构基础:P2.1-线性结构—>线性表
数据结构基础:P2.2-线性结构—>堆栈
数据结构基础:P2.3-线性结构—>队列
数据结构基础:P2.4-线性结构—>应用实例:多项式加法运算
数据结构基础:P2.5-线性结构—>应用实例:多项式乘法与加法运算-C实现
数据结构基础:P3.1-树(一)—>树与树的表示
数据结构基础:P3.2-树(一)—>二叉树及存储结构
数据结构基础:P3.3-树(一)—>二叉树的遍历
数据结构基础:P3.4-树(一)—>小白专场:树的同构-C语言实现
数据结构基础:P4.1-树(二)—>二叉搜索树
数据结构基础:P4.2-树(二)—>二叉平衡树
数据结构基础:P4.3-树(二)—>小白专场:是否同一棵二叉搜索树-C实现
数据结构基础:P4.4-树(二)—>线性结构之习题选讲:逆转链表
数据结构基础:P5.1-树(三)—>堆
数据结构基础:P5.2-树(三)—>哈夫曼树与哈夫曼编码
数据结构基础:P5.3-树(三)—>集合及运算
数据结构基础:P5.4-树(三)—>入门专场:堆中的路径
数据结构基础:P5.5-树(三)—>入门专场:File Transfer
数据结构基础:P6.1-图(一)—>什么是图
数据结构基础:P6.2-图(一)—>图的遍历
数据结构基础:P6.3-图(一)—>应用实例:拯救007
数据结构基础:P6.4-图(一)—>应用实例:六度空间
数据结构基础:P6.5-图(一)—>小白专场:如何建立图-C语言实现
数据结构基础:P7.1-图(二)—>树之习题选讲:Tree Traversals Again
数据结构基础:P7.2-图(二)—>树之习题选讲:Complete Binary Search Tree
数据结构基础:P7.3-图(二)—>树之习题选讲:Huffman Codes
数据结构基础:P7.4-图(二)—>最短路径问题
数据结构基础:P7.5-图(二)—>哈利·波特的考试
数据结构基础:P8.1-图(三)—>最小生成树问题
数据结构基础:P8.2-图(三)—>拓扑排序
数据结构基础:P8.3-图(三)—>图之习题选讲-旅游规划
数据结构基础:P9.1-排序(一)—>简单排序(冒泡、插入)
数据结构基础:P9.2-排序(一)—>希尔排序
数据结构基础:P9.3-排序(一)—>堆排序
数据结构基础:P9.4-排序(一)—>归并排序
数据结构基础:P10.1-排序(二)—>快速排序
数据结构基础:P10.2-排序(二)—>表排序
数据结构基础:P10.3-排序(二)—>基数排序
数据结构基础:P10.4-排序(二)—>排序算法的比较
数据结构基础:P11.1-散列查找—>散列表

文章目录

  • 前言
  • 一、数字关键词的散列函数构造
    • 1.1 直接定址法
    • 1.2 除留余数法
    • 1.3 数字分析法
    • 1.4 折叠法
    • 1.5 平方取中法
  • 二、字符串关键词的散列函数构造
    • 2.1 一个简单的散列函数——ASCII码加和法
    • 2.2 简单的改进——前3个字符移位法
    • 2.3 好的散列函数——移位法


前言

散列函数的设计应该考虑以下两个因素

计算简单,以便提高转换速度;
关键词对应的地址空间分布均匀,能够把不同对象均匀地映射到不同的地址空间里去,以尽量减少冲突。映射得越均匀,这样冲突会越少,效率会更高。


一、数字关键词的散列函数构造

1.1 直接定址法

思路:取关键词的某个线性函数值为散列地址,即h(key)=a × key + b (a、b为常数)
例子

我们想管理不同年份里面的人口的总数,关键词是年份。它的值是1990一直到2011,而我们现在要映射的地址是0-21这样的一个地址空间。所以可以直接用一个线性函数来实现:h(key) = key - 1990
映射结果如下
数据结构基础:P11.2-散列查找--->散列函数的构造方法_第1张图片


1.2 除留余数法

思路:把我们的关键词通过求余运算,把一个大的整数转换成一个小的整数,这个小的整数就相当于是散列表里面的地址。散列函数为:h(key) = key mod p
例子

比方说我们前面的那个例子,通过对17求余来获得相应的地址,h(key) = key % 17
在这里插入图片描述
这里面p = Tablesize = 17。一般而言,为了映射均匀,p素数


1.3 数字分析法

思路:我们给的关键词可能由很多位数组成,每一位的变化情况可能不一样,有的某一位是一直都不动的,而某些位会随机变化。所以数字分析法只希望分析出对象的关键字在每一位上的一种表现,我们把那些能够随机变化的这些位取出来组成我们的地址,达到映射均匀的这样的一个目的。
例子

对于一些区域的手机号码,前面7位都是相同的,只有后面4位会变化。取11位手机号码key的后4位作为地址,散列函数为:h(key) = atoi(key+7) (char *key)
这里key指向了这11位电话号码的第一个字符的地址,所以我们加上7之后就往后挪了7位,指向了倒数第四位。
在这里插入图片描述
再通过atoi函数(int atoi(char *s),将类似"5678"的字符串转换成整数5678),把一个字母串转化为整数。

例子:

另外一个典型的例子看看我们的身份证号码,身份证号码有18位,在这个18位里面每一位都有特定的含义。
数据结构基础:P11.2-散列查找--->散列函数的构造方法_第2张图片
蓝色标记的6位变化较大,于是构造散列函数如下:
h 1 ( k e y ) = ( k e y [ 6 ] − ‘ 0 ’ ) × 1 0 4 + ( k e y [ 10 ] − ‘ 0 ’ ) × 1 0 3 + ( k e y [ 14 ] − ‘ 0 ’ ) × 1 0 2 + ( k e y [ 16 ] − ‘ 0 ’ ) × 10 + ( k e y [ 17 ] − ‘ 0 ’ ) \rm{h_1(key) = (key[6]-‘0’)×10^4 + (key[10]-‘0’)×10^3 + (key[14]-‘0’)×10^2 + (key[16]-‘0’)×10 + (key[17]-‘0’)} h1(key)=(key[6]0)×104+(key[10]0)×103+(key[14]0)×102+(key[16]0)×10+(key[17]0)
h ( k e y ) = h 1 ( k e y ) × 10 + 10 \rm{h(key) = h_1(key)×10 + 10} h(key)=h1(key)×10+10 (当 k e y [ 18 ] = ‘ x ’ \rm{key[18] = ‘x’} key[18]=x时)
= h 1 ( k e y ) × 10 + k e y [ 18 ] − ‘ 0 ’ \rm{= h_1(key)×10 + key[18]-‘0’} =h1(key)×10+key[18]0 (当 k e y [ 18 ] = ’ 0 ’ − 9 ’ \rm{key[18] =’0’-9’} key[18]=09时)


1.4 折叠法

思路:把很长的数字的关键词拆成若干个部分,然后把它们叠加在一起。
例子

比方说我们的关键词总共有8位,我们可以把它拆成若干个三位。后面三位542,再下来三位793,然后56我们前面补个0。然后把这三段相加,相加完之后得到结果是1391,这个时候我们取391作为我们的函数的结果值,也是作为地址。
数据结构基础:P11.2-散列查找--->散列函数的构造方法_第3张图片


1.5 平方取中法

思路:我们希望设计的哈希函数的结果能够被每一位所影响,所以有一种方法就是把这个大数取个平方,然后我们取中间几位。
例子

比方说我们有个8位的数,平方之后我们取中间三位
数据结构基础:P11.2-散列查找--->散列函数的构造方法_第4张图片


二、字符串关键词的散列函数构造

2.1 一个简单的散列函数——ASCII码加和法

思路:把每一个字符对应ASCII 码的编码简单相加,然后求个余数。散列函数定义如下:h(key) = (Σkey[i]) mod TableSize)
分析

①这种方法会出现较多的冲突
比如a3、b2、c1会冲突,eat、 tea会冲突。
②非常容易产生聚集
ASCII码的编码主要用到了7位,也就是说对每个字符来讲,他的编码范围是0-127。如果变量名是10位的,这样的一个字母串我们简单的把它Σ起来后的值就是在0-1270之间。而变量名实际上的变化是非常多的,所以如果以很窄的计算结果来对付一个很广的变量范围,这样的一个哈希函数很容易会产生聚集。


2.2 简单的改进——前3个字符移位法

思路:针对上面那种情况,我们可以变化范围再加大一点。我们可以把前面的三个字符看成是二十七进制里面的百位数、十位数、个位数,这样把整个的地址空间扩展开来(因为我们有26个字母,之后可能当中会有空格,所以有27个字符)。对应的哈希函数为: h ( k e y ) = ( k e y [ 0 ] × 2 7 2 + k e y [ 1 ] × 27 + k e y [ 2 ] ) % T a b l e S i z e {\rm{h}}\left( {{\rm{key}}} \right){\rm{ = }}\left( {{\rm{key}}\left[ {\rm{0}} \right]{\rm{ \times 2}}{{\rm{7}}^{\rm{2}}}{\rm{ + key}}\left[ {\rm{1}} \right]{\rm{ \times 27 + key}}\left[ {\rm{2}} \right]} \right){\rm{\% TableSize }} h(key)=(key[0]×272+key[1]×27+key[2])%TableSize
分析

仍然冲突
string、 street、strong、structure等等;
空间浪费
如果每个字符有26种变化情况,那么前三个字符的变化情况是263。而实际上有人通过统计,前三位一般的出现的情况是3000种。所以利用率仅3000/263 ≈ 30%


2.3 好的散列函数——移位法

思路:进一步的考虑呢,我们就不是取前面三个字符进行组合,我们把所有的字符都进行组合。就是你给我一个字符串,我把所有位位全部考虑进去,同时我把它看成是一个32进制的.
例子

如何快速计算 h ( “ a b c d e ” ) = ‘ a ’ ∗ 3 2 4 + ’ b ’ ∗ 3 2 3 + ’ c ’ ∗ 3 2 2 + ’ d ’ ∗ 32 + ’ e ’ \rm{h(“abcde”)=‘a’*32^4+’b’*32^3+’c’*32^2+’d’*32+’e’} h(abcde)=a324+b323+c322+d32+e
方法①
(((a*32+b)*32+c)*32+d)*32+e,可以减少乘法次数
方法②:
可以通过移位运算,*32相当于向左移动5位。

方法2对应代码如下:

Index Hash ( const char *Key, int TableSize )
{ 
	 unsigned int h = 0; /* 散列函数值,初始化为0 */
	 while ( *Key != '\0') /* 位移映射 */
		 h = ( h << 5 ) + *Key++;
	 return h % TableSize;
}

你可能感兴趣的:(数据结构基础,数据结构,散列表,算法,c算法,c语言)