哈希(1) - 介绍

假设设计一个员工信息存储系统,用他们的电话号码做为key,并且要让下面的这些查询操作尽可能的高效:

  1. 插入一个电话号码以及相关的信息.
  2. 搜索一个电话号码以及相关的信息.
  3. 删除一个电话号码以及相关的信息.

一般都可以考虑使用下面的数据结构来存储不同电话号码的信息。

  1. 电话号码和记录的数组。
  2. 电话号码和记录的链表。
  3. 电话号码做为key的平衡二叉树。
  4. 直接访问数据表。

对于数组和链表,我们需要花费线性时间来搜索,在实际应用中比较耗时。我们使用数组并将数据排好序,利用二分查找搜索电话号码,需要O(Logn)的时间,但是插入和删除会变得耗时,因为要保持排序。

使用平衡二叉树,可以得到比较稳定的搜索,插入以及删除时间。所有的这些操作可以确保在O(Logn) 时间之内。

另一种方法是是使用直接访问数据表,建立一个大的数组且使用电话号码做为索引。如果没有提供号码,则这个数组元素为NIL,否则数组元素会保存一个指向电话记录的指针。这个方案的时间复杂度是前面所有方案中最小的,可以在O(1)的时间内完成所有操作。例如,如果要插入一个电话号码,首先使用给定的号码创建一条记录,然后使用号码做为索引,将指向这个记录的指针保存下来。

但是,这个方案有很多的实际操作限制。首先的问题是需要大量的内存空间。例如,如果号码由n个数字组成,则需要O(m * 10n)的空间,其中m是记录指针的大小。另一个问题是编程语言中的整形,可能没有n位数这么大。

基于上述限制,直接访问表并不常使用。在所有的这些数据结构中,哈希是最佳方案。使用哈希,可以得到O(1)的平均查找时间以及O(n)的最坏查找时间。

哈希是基于直接访问表的一个改进。方案是使用哈希函数,将给定的电话号码或者其它key,转换为一个小的数值,并将此小数值做为称为哈希表中的索引。

哈希函数: 将一个很大的电话号码数值转换为小数值的函数。映射得到的小数值做为哈希表的索引。

一个好的哈希函数应该具备下面特征:

1) 有效可计算性(Efficiently computable)。

2) 必须均匀地映射key(即每个key对应每个表位置)。

例如,对于电话号码,一个坏的哈希函数可能使用前3位数字,而可能更好的函数应该是考虑后3位数字。注意,这并不一定是最佳哈希函数,可能存在更好的方法。

哈希表: 即一个数组,元素存储的是指向相关电话号码记录的指针。如果没有号码能映射到某个索引,则这个索引代表的元素为NIL。

冲突处理: 因为哈希函数处理的是一个很大的整数或者字符串,而映射到的范围是一个比较小的数值,则很有可能两个key拥有相同的映射值。对于新插入的key,映射到了一个已存在的位置上时,这种情况称之为冲突,并且必须使用一些冲突检测技术来进行处理。下面是两种处理冲突的方法:

  • 链接法(Chaining):主要思想是将哈希表中的每一项指向一个记录链表,这个链表拥有相同的哈希值。链表法相对比较简单,但是需要额外的内存。
  • 开放定址法(Open Addressing): 这个方法的基本思想是:当发生地址冲突时,按照某种方法继续探测哈希表中的其他存储单元,直到找到空位置为止。

下面是对这两种方法的具体介绍:

1. 链接法

a. 链接法解决冲突的方法

将所有关键字为同义词的结点链接在同一个单链表中。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数组T[0..m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在链接法中,装填因子α可以大于1,但一般均取α≤1。

b.链接法的优点

(1)链接法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;

(2)由于链接法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;

(3)开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而链接法中可取α≥1,且结点较大时,链接法中增加的指针域可忽略不计,因此节省空间;

(4)在用链接法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。而对开放地址法构造的散列表,删除结点不能简单地将被删结点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在用开放地址法处理冲突的哈希表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。

c. 链接法的缺点

指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大哈希表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。

2. 开放定址法

它的过程可用下式描述:

H i ( key ) = ( H ( key )+ d i ) mod m ( i = 1,2,…… , k ( k ≤ m – 1))

其中: H ( key ) 为关键字 key 的直接哈希地址, m 为哈希表的长度, di 为每次再探测时的地址增量。

采用这种方法时,首先计算出元素的直接哈希地址 H ( key ) ,如果该存储单元已被其他元素占用,则继续查看地址为 H ( key ) + d 2 的存储单元,如此重复直至找到某个存储单元为空时,将关键字为 key 的数据元素存放到该单元。

增量 d 可以有不同的取法,并根据其取法有不同的称呼:

( 1 ) d i = 1 , 2 , 3 , …… 线性探测再散列;

( 2 ) d i = 1^2 ,- 1^2 , 2^2 ,- 2^2 , k^2, -k^2…… 二次探测再散列;

( 3 ) d i = 伪随机序列/伪随机再散列;

a.线性探查法(Linear Probing)

该方法的基本思想是:

     将散列表T[0..m-1]看成是一个循环向量,若初始探查的地址为d(即h(key)=d),则最长的探查序列为:

d,d+l,d+2,…,m-1,0,1,…,d-1

 即:探查时从地址d开始,首先探查T[d],然后依次探查T[d+1],…,直到T[m-1],此后又循环到T[0],T[1],…,直到探查到T[d-1]为止。

探查过程终止于三种情况:

 (1)若当前探查的单元为空,则表示查找失败(若是插入则将key写入其中);

     (2)若当前探查的单元中含有key,则查找成功,但对于插入意味着失败;

 (3)若探查到T[d-1]时仍未发现空单元也未找到key,则无论是查找还是插入均意味着失败(此时表满)。

聚集或堆积现象

     用线性探查法解决冲突时,当表中i,i+1,…,i+k的位置上已有结点时,一个散列地址为i,i+1,…,i+k+1的结点都将插入在位置i+k+1上。把这种散列地址不同的结点争夺同一个后继散列地址的现象称为聚集或堆积(Clustering)。这将造成不是同义词的结点也处在同一个探查序列之中,从而增加了探查序列的长度,即增加了查找时间。若散列函数不好或装填因子过大,都会使堆积现象加剧。

b.二次探查法(Quadratic Probing)

二次探查法的探查序列是:

hi=(h(key)+i*i)%m 0≤i≤m-1 //即di=i2

   即探查序列为d=h(key),d+12,d+22,…,等。

 该方法的缺陷是不易探查到整个散列空间。

c.双重散列法(Double Hashing)

该方法是开放定址法中最好的方法之一,它的探查序列是:

hi=(h(key)+i*h1(key))%m 0≤i≤m-1 //即di=i*h1(key)

 即探查序列为:

d=h(key),(d+h1(key))%m,(d+2h1(key))%m,…,等。

 该方法使用了两个散列函数h(key)和h1(key),故也称为双散列函数探查法。

注意:定义h1(key)的方法较多,但无论采用什么方法定义,都必须使h1(key)的值和m互素,才能使发生冲突的同义词地址均匀地分布在整个表中,否则可能造成同义词地址的循环计算。

  【例】 若m为素数,则h1(key)取1到m-1之间的任何数均与m互素,因此,我们可以简单地将它定义为:

h1(key)=key%(m-2)+1

  【例】对例9.1,我们可取h(key)=key%13,而h1(key)=key%11+1。

  【例】若m是2的方幂,则h1(key)可取1到m-1之间的任何奇数。

时间: 2024-10-10 19:19:06

哈希(1) - 介绍的相关文章

一致性哈希算法介绍

一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CARP使用的简 单哈希算法带来的问题,使得分布式哈希(DHT)可以在P2P环境中真正得到应用. 一致性hash算法提出了在动态变化的Cache环境中,判定哈希算法好坏的四个定义: 1.平衡性(Balance):平衡性是指哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利用.很多哈希算法都能够满

.NET平台开源项目速览(12)哈希算法集合类库HashLib

.NET的System.Security.Cryptography命名空间本身是提供加密服务,散列函数,对称与非对称加密算法等功能.实际上,大部分情况下已经满足了需求,而且.NET实现的都是目前国际上比较权威的,标准化的算法,所以还是安全可靠的.但也有一些场合,需要自己实现一些安全散列算法.不仅仅是学习,也可以进行测试以及相关研究.而今天要介绍的正式这样一个包括了目前几乎所有散列函数算法实现的.NET开源组件,大家可以实际使用,查看或者修改等.满足更多不同人,不同层次的需求.那就看看相关基础知识

一致性哈希算法在分布缓存中的应用

一.简介 关于一致性哈希算法介绍有许多类似文章,需要把一些理论转为为自己的知识,所以有了这篇文章,本文部分实现也参照了原有的一些方法. 该算法在分布缓存的主机选择中很常用,详见 http://en.wikipedia.org/wiki/Consistent_hashing . 二.算法诞生缘由 现在许多大型系统都离不开缓存(K/V)(由于高并发等因素照成的数据库压力(或磁盘IO等)超负荷,需要缓存缓解压力),为了获得良好的水平扩展性, 缓存主机互相不通信(如Mencached),通过客户端计算K

一致哈希算法Java实现

一致哈希算法(Consistent Hashing Algorithms)是一个分布式系统中经常使用的算法. 传统的Hash算法当槽位(Slot)增减时,面临全部数据又一次部署的问题.而一致哈希算法确可以保证,仅仅须要移动K/n份数据(K为数据总量, n为槽位数量),且仅仅影响现有的当中一个槽位. 这使得分布式系统中面对新增或者删除机器时.可以更高速的处理更改请求. 本文将用Java实现一个简单版本号的一致哈希算法,仅仅为说明一致哈希算法的核心思想. 一致哈希算法介绍 一致哈希算法的介绍非常多.

整数哈希

整数哈希介绍 为什么要整数哈希 很多时候,可以直接用整数作为键,比如QQ号码,手机号码,但这些号码的分布性不是均匀的(比如高位的变化更少,低位变化更多).   分布均匀指的是每位为0或1的概率都是一样的. 理论基础 整数哈希的目标 1. 函数要是可逆的(1对1的映射)     2. 雪崩效应(输入中1bit的变化 影响 输出中1/4 到 1/2的bits变化) 可逆操作 key + const_value 加法是可逆     key - const_value 减法是可逆     key ^ c

自己动手实现java数据结构(五)哈希表

1.哈希表介绍 前面我们已经介绍了许多类型的数据结构.在想要查询容器内特定元素时,有序向量使得我们能使用二分查找法进行精确的查询((O(logN)对数复杂度,很高效). 可人类总是不知满足,依然在寻求一种更高效的特定元素查询的数据结构,哈希表/散列表(hash table)就应运而生啦.哈希表在特定元素的插入,删除和查询时都能够达到O(1)常数的时间复杂度,十分高效. 1.1 哈希算法 哈希算法的定义:把任意长度的输入通过哈希算法转换映射为固定长度的输出,所得到的输出被称为哈希值(hashCod

凡露希商城APP开发(系统开发)

------------恢复内容开始------------ 凡露希商城系统开发找[吴悠:I8O||2854||8832微可电],凡露希APP开发系统,凡露希软件开发,凡露希模式开发定制APP,凡露希奖励机制详解,凡露希分销商城开发APP,凡露希平台搭建源码. 凡露希商城介绍: 平凡之露,希望之源,全球消费者轻创业平台------凡露希是一家生产.研发 .销售为一体的高端化妆品平台!凡露希隶属于广州雅盛生物科技有限公司旗下品牌,覆盖护肤.洗护.彩妆系列产品的研发级销售. 自产自销平台,无任何中间

走进MongoDB(五)---- 分片

本文从以下几个方面对MongoDB进行介绍 一.分片键组件 二.分片键 三.哈希分片 四.范围分片 五.区间 六.分片部署实例 Sharding概述 是分片.或者分区的意思.分片是一个数据库架构,可以通过key 范围拆分数据并且把拆分后的数据分散的存储到两个或多个数据库实例.分片提供了水平扩展的功能. MongoDB使用分片来支持超大数据集和高操作性能的部署要求.我们可以使用两种方法来支持数据量的大量增加和高性能操作要求:垂直扩展和水平扩展 1.垂直扩展: 通常是增加单机容量,例如.使用性能更高

17. Dubbo原理解析-集群&容错之负载均衡

LoadBalance负载均衡, 负责从多个 Invokers中选出具体的一个Invoker用于本次调用,调用过程中包含了负载均衡的算法,调用失败后需要重新选择 LoadBalance接口定义 @SPI(RandomLoadBalance.NAME) public interface LoadBalance{ @Adaptive("loadbalance") <T> Invoker<T> select(List<Invoker<T>> i