数据结构---哈希表(散列表)

我们在Java容器中谈到:有哈希表(也称为散列表)支持的HashMapLinkedHashSet等都具有非常高的查询效率。这其中就是Hash起的作用。顺序查找的时间复杂度为O(N) ,二分查找查找树的时间复杂度为O(logN),而 哈希表的时间复杂度为O(1) 。不过这只是理想状态,实际并不那么完美。


1.哈希表的概念和思想

哈希表是唯一的专用于集合的数据结构。可以以常量的平均时间实现插入、删除和查找。

哈希表的思想是:用一个与集合规模差不多大的数组来存储这个集合,将数据元素的关键字映射到数组的下标,这个映射称为“散列函数”,数组称为“散列表”。查找时,根据被查找的关键字找到存储数据元素的地址,从而获取数据元素。

由于任何类型的数据都能转化为一个整数型,我们假设所有的数据元素的关键字都是较小的非负整数,就可以用一个简单的数组Data保存这个集合,数据类型就是集合中的元素的类型。

开始时,给数组的每个元素赋空值,NULL。当要在集合中插入一个关键字为Key的数据元素时,就检查Data[Key]是否为空。如果为空,则吧插入的元素存储在Data[Key]中;如果不为空,则表示遇到重复元素,插入失败。

当要查找某个数据元素时,只需要根据查找的关键字Key直接取出Data[Key]的值,如果Data[Key]的值为空,则表示关键字对应的元素不存在。否则,Data[Key]就是要查找的元素。删除时,只需要将对应数组的元素设为NULL即可。每种操作的时间为一个常量。

散列函数函数的应用带来一个复杂的问题:

因为散列函数的定义域范围比值域大,两个或更多的数据元素可能被映射到同一个位置,称为“冲突或碰撞”。这种情况是不可避免的。因此,实现散列表的两个最基本的问题是:如何设计散列函数,如何解决碰撞。


2.散列函数

在散列表中。插入、删除和查找都会用到散列函数。散列函数的计算速度直接影响散列表的性能。好的散列函数的一个标准就是:计算速度快。另一点就是:结点的散列地址尽可能均匀。使得冲突的机会尽可能少

常用的散列函数包括直接定址法、保留余数法、数字分析法、平方取中法和折叠法等。

(1)直接定址法

直接取关键字的值或关键字的某个线性函数的值作为散列地址。设关键字为x那么散列地址可表示为:

H(x) = x 或  H(x) = ax + b (a、b为常数)

例如:关键字集合为{100, 400,600, 200, 800, 900},利用直接定址法,若选取散列函数为H(x) = x/100,则散列表如下:

(2)保留余数法

如果M是散列表的大小,关键字 x 的数据元素的散列地址为:H(x) = x mod M

在保留余数法中,选取合适的余数M很重要,如果选取不当,则导致大量的碰撞。

经验表明:M为素数(除了1和它本身以外不再有其他因数。)时,散列表的分布比较均匀。

(3)数字分析法

在某些领域,关键字之间的区别集中在某些位上面,例如计算机的IP地址,一个IP地址由两部分组成:网络号和主机号。在同一子网中的主机的网络号是相同的。在某个网络中,我们可以将IP地址作为关键字。如果采取散列方法保存这个集合,可以选取IP地址的主机号部分作为存储地址。

更一般的说,如果在关键字集合中,每个关键字均由n位组成,分析关键字中每一位的分布规律,并从中提取分布均匀的若干位或它们的组合作为地址。


例如:右边的数据

第一列、第二列、第五列 对于不是区分关键字没有价值。

第三列只有 7 、 8 、9 三种数字。

余下的几列比较均匀,可作为散列表的地址选用。

若散列表的地址是三位,直接选取剩下的三位即可;

若散列表的地址小于三位。则选择其他方法,比如:保留余数法等等

将三位关键字映射到一个更小的值域中。

(4)平方取中法

如果关键字中的各位的分布都比较均匀,但关键字的值域比数组的规模大,则可以将关键字平方后,取其结果中间各位作为散列函数值。

由于中间各位和每一位数字有关系,因此均匀分布的可能性较大。

例如:4731 X 4731 = 22 382 361.中间选取几位,依赖于散列表的单元总数。若散列表中有100个单位,选取中间4,5两位,即关键字

4731的地址为82.

(5)折叠法

如果关键字相当长,以至于和散列表的单元总数相比大的多,则采取折叠法。如果数字的分布大体上是均匀的,通常选取一个长度后,将关键字按长度分组相加。例如:542 242 241,折叠后542 + 242 + 241 = 1025,抛弃进位,得到散列表的结果为25.

不存在一种万能的散列函数,在任何情况下都是出色的。但是大部分情况下,保留余数法比较好。

实际中选取散列函数需要考虑的因素有

  • 计算散函数所需时间。
  • 关键字长度。
  • 散列表长度(散列表地址范围)。
  • 关键字分布情况。
  • 记录的查找频率。

3.碰撞的解决

在选取散列函数时,由于很难选取一个既均匀分布又简单,同时保证关键字和散列地址一一对应的散列表,所以冲突时不可避免的。如果具有不同关键字的 k 个数据元素的散列地址完全相同,就必须为 k-1个数据元素重新分配存储单元。通常称其为“溢出”的数据元素。

常用的处理冲突的方法有两种:

(1)将溢出的数据元素存放到散列表中没有使用的单元中。

这种方法是“封闭”的,不需要使用额外的存储单元,称为“闭散列表”。具体的实施方案有:线性探针法、二次探测发、再散列法。

  • 线性探针法:在数组中从映射到的位置开始顺序查找空位置,如果需要,从最后一个位置绕回第一个位置。这种方法碰撞可能引起连锁反应,使表中形成一些较长的连续被占用的单元,如:从1---10的地址全部被使用。从而使散列表的性能降低。
  • 二次探测发:不直接检查下一个单元,而是检查远离初始探测点的某一单元,以消除线性探测法中的初始聚集的问题.
  • 再散列法:有两个散列函数H1和H2。H1用来计算探测序列的起始地址,H2用来计算下一个位置的步长。第二个散列函数必须仔细选择。例如,它永远不能计算出0,必须保证所有单元能够探测到。

(2)将映射到同一地址的数据元素分别保存到散列表以外的各自线性表中。

由于地址相同的数据元素个数变化比较大,因此通常采用链表的方式。散列表本身只保存一个指向各自链表中第一个节点的指针。这种方法称为“开散列表",或拉链法,可以理解为“链表的数组”。

开散列表将具有同一散列地址的数据元素都存储在一个单链表中。在散列表中插入、查找或删除一个元素,就是在对应的单链表中进行的。为了方便操作,将单链表设计为带头结点的单链表。

例如;一个规模为11的开散列表中依次插入关键字17、21、23、60、29、38......,散列函数为

H(Key)= key mod 11,散列表如下:


在开散列表中,插入、删除和查找操作的实现都相当简单。

插入x时,首先计算H(x),将x插入到H(x)指向的单链表的表头。

查找时,也是先计算H(x),然后顺序查找H(x)指向的单链表。

删除操作同样简单,先计算H(x),然后顺序查找H(x)指向的单链表,

找到x后删除

2018-01-05

原文地址:https://www.cnblogs.com/zhuweiheng/p/8207255.html

时间: 2024-10-12 21:40:53

数据结构---哈希表(散列表)的相关文章

哈希表/散列表

哈希表/散列表,是根据关键字(key)直接访问在内存存储位置的数据结构. 构造哈希表的常用方法: 直接地址法---取关键字的某个线性函数为散列地址,Hash(Key) = Key或Hash(key) = A*Key + B, A,B为常数. 除留余数法---取关键值被某个不大于散列表长m的数p除后的所得的余数为散列地址. Hash(key) = key % p. 若采用直接地址法(Hash(Key) = Key)存在一定的缺陷. 当Key值特别大时,而Key之前的数很少,就会造成空间浪费.大多时

HashTable-哈希表/散列表

HashTable-散列表/哈希表,是根据关键字(key)而直接访问在内存存储位置的数据结构.它通过一个关键值的函数将所需的数据映射到表中的位置来访问数据,这个映射函数叫做散列函数,存放记录的数组叫做散列表. 构造哈希表的几种方法 直接定址法--取关键字的某个线性函数为散列地址,Hash(Key)= Key 或 Hash(Key)= A*Key + B,A.B为常数. 除留余数法--取关键值被某个不大于散列表长m的数p除后的所得的余数为散列地址.Hash(Key)= Key % P. 平方取中法

9-12-哈希查找表/散列表-查找-第9章-《数据结构》课本源码-严蔚敏吴伟民版

课本源码部分 第9章  查找 - 哈希查找表/散列表 ——<数据结构>-严蔚敏.吴伟民版        源码使用说明  链接??? <数据结构-C语言版>(严蔚敏,吴伟民版)课本源码+习题集解析使用说明        课本源码合辑  链接??? <数据结构>课本源码合辑        习题集全解析  链接??? <数据结构题集>习题解析合辑        本源码引入的文件  链接? Base.c        相关测试数据下载  链接? 数据包      

数据结构哈希表(转)

数据结构哈希表 参考代码如下: [plain] view plain copy /* 名称:哈希表 语言:数据结构C语言版 编译环境:VC++ 6.0 日期: 2014-3-26 */ #include <stdio.h> #include <malloc.h> #include <windows.h> #define NULLKEY 0   // 0为无记录标志 #define N 10        // 数据元素个数 typedef int KeyType;// 

JavaScript数据结构——实现简单的散列表

散列算法的作用是尽可能快地在数据结构中找到一个值.如果数据很大,但是有需要遍历整个数据结构来查找到该值,花费的时间就太多了.所以散列表在查找方面中比较优势:使用散列函数,就知道具体位置,能够快速检索.散列函数的作用:给定一个key值,返回key值在表中的地址. 1 function HashTable(){ 2 //初始化散列表 3 var table=[]; 4 //散列函数(私有方法) 5 var loseloseHashCode = function(key){ 6 var hash=0;

《数据结构》C++代码 散列表

       散列表,又名哈希表.Hash表.这是一个神奇的数据结构,它的复杂度是常数级别,由于我非常喜欢这个数据结构,在此简单介绍一下.        (没有学过Hash表的同学,我推荐一个教程:http://www.cnblogs.com/jiewei915/archive/2010/08/09/1796042.html)        让我们回忆一下之前学过的数据结构,列个表,与Hash表做个比较(表中c表示常数): 插入时间 删除时间 查找时间 编程复杂度 信息剖析度 无序数组 1 N

数据结构 - 哈希表

哈希表 1. 哈希表的引入 1.1 哈希表的简单概述   哈希表一个通过哈希函数来计算数据存储位置的数据结构,通常支持如下操作 (高效的操作):python中的字典是通过哈希表实现的 insert(key, value):插入键值对(key,value) get(key):如果存在键为key的键值对则返回其value,否则返回空值 delete(key):删除键为key的键值对  1.2.直接寻址表 当关键字的key 的 全域U(关键字可能出现的范围)比较小时,直接寻址是一种简单而有效的方法 存

Redis源码解析(四):redis之数据类型哈希表、列表、集合和有序集合

哈希表也是redis支持的数据结构之一,它使用REDIS_ENCODING_ZIPLIST(压缩列表) 和REDIS_ENCODING_HT(数据字典) 两种编码方式. 当哈希表使用压缩列表时,它使用如下的结构存储数据(详见ziplist.c): +---------+------+------+------+------+------+------+------+------+---------+ | ZIPLIST | | | | | | | | | ZIPLIST | | ENTRY |

数据结构和算法篇——散列表

之前讲过博主在某网买了一个数据结构与算法的课程.本篇散列表是其中的三节.散列表应该是 Java 程序员常用并且最先碰到的一个数据结构了吧?Java 的 HashMap 就是对散列表的实现.可以说散列表算是一个比较基础.比较好理解(抛开需要缜密设计的哈希函数不说).比较好用(查询时间复杂度O(1))的一种数据结构.本篇在此分享这三节的总结笔记. 1)散列表开篇介绍:https://www.cnblogs.com/christmad/p/11519055.html 2)如何打造一个工业级的散列表:h