精华内容
下载资源
问答
  • 哈希表原理

    2020-08-14 11:14:36
    哈希表原理1. Hash函数2. 处理冲突的几种方法2.1 外部拉链法2.2 开放定址法2.3 公共溢出区2.4 再Hash法3. 实现哈希表的操作方法 哈希表是最常用的数据结构之一,对于其用法,大家都非常熟悉,这里详细探讨一下其...


    哈希表是最常用的数据结构之一,对于其用法,大家都非常熟悉,这里详细探讨一下其原理。

    哈希表的底层实际上是基于数组来存储的,

    • 当插入键值对时,并不是直接插入该数组中,而是通过对键进行Hash运算得到Hash值,然后和数组容量取模,得到在数组中的位置后再插入。
    • 取值时,先对指定的键求Hash值,再和容量取模得到底层数组中对应的位置,如果指定的键值与存贮的键相匹配,则返回该键值对,如果不匹配,则表示哈希表中没有对应的键值对。

    这样做的好处是在查找、插入、删除等操作可以做到O(1),最坏的情况是O(n),当然这种是最极端的情况,极少遇到。

    在这里插入图片描述
    不管哪门语言,实现一个HashMap的过程均可分为三大步骤:

    1. 实现一个Hash函数
    2. 合理解决Hash冲突
    3. 实现HashMap的操作方法

    1. Hash函数

    Hash函数非常重要,一个好的Hash函数不仅性能优越,而且还会让存储于底层数组中的值分配的更加均匀,减少冲突发生。
    之所以是减少冲突,是因为取Hash的过程,实际上是将输入键(定义域)映射到一个非常小的空间中,所以冲突是无法避免的,能做的只是减少Hash碰撞发生的概率。

    具体实现时,哈希函数算法可能不同,在Rust及很多语言的实现中,默认选择SipHash哈希算法。

    默认情况下,Rust的HashMap使用SipHash哈希算法,其旨在防止哈希表碰撞攻击,同时在各种工作负载上提供合理的性能。虽然 SipHash 在许多情况下表现出竞争优势,但其中一个比其它哈希算法要慢的情况是使用短键,例如整数。这就是为什么 Rust 程序员经常观察到 HashMap 表现不佳的原因。在这些情况下,经常推荐 FNV 哈希,但请注意,
    它不具备与 SipHash 相同的防碰撞性。

    影响Hash碰撞(冲突)发生的除了Hash函数本身意外,底层数组容量也是一个重要原因。
    很明显,极端情况下如果数组容量为1,哪必然发生碰撞,如果数组容量无限大,哪碰撞的概率非常之低。

    所以,哈希碰撞还取决于负载因子。
    负载因子是存储的键值对数目与数组容量的比值,比如数组容量100,当前存贮了90个键值对,负载因子为0.9。
    负载因子决定了哈希表什么时候扩容,如果负载因子的值太大,说明存储的键值对接近容量,增加碰撞的风险,如果值太小,则浪费空间。

    所以,既然冲突无法避免,就必须要有解决Hash冲突的机制方法。

    2. 处理冲突的几种方法

    主要有四类处理冲突的方法:

    • 外部拉链法(常用)
    • 开放定址法(常用)
    • 公共溢出区(不常用)
    • 再Hash法(不常用)

    2.1 外部拉链法

    主要思想是基于数组和链表的组合来解决冲突,桶(Bucket)中不直接存储键值对,每个Bucket都链接一个链表,当发生冲突时,将冲突的键值对插入链表中。

    外部拉链法的有点在于方法简单,非同义词之间也不会产生聚集现象(相比于开放定址法),并且其空间结构是动态申请的,所以比较适合无法确定表长的情况

    缺点是链表指针需要额外的空间,遇到碰撞拒绝服务时会退化为单链表。

    同义词:两个元素通过Hash函数得到了相同的索引地址,这两个元素就叫做同义词。

    下面是外部拉链法的两种实现方法,主要区别在于桶(Bucket)中是否存储数据。

    在这里插入图片描述
    在这里插入图片描述


    2.2 开放定址法

    主要思想是发生冲突时,直接去寻找下一个空的地址,只要底层的表足够大,就总能找到空的地址。这个寻找下一个地址的行为,叫做探测。

    在这里插入图片描述
    其中hash(key)为哈希函数,mm为哈希表长,di为增量序列,i为已发生冲突的次数。

    根据增量序列取法的不同有多种探测方法:

    • di =1,2,3…(m−1)
      称为线性探测(Linear Probing);即 di=i 或者为其他线性函数。相当于逐个探测存放地址的表,直到查找到一个空单元,把散列地址存放在该空单元。

    • di = ±12,±22 ,±32 … ±k2 ( k ≤ m/2 )
      称为平方探测(Quadratic Probing)。相对线性探测,相当于发生冲突时探测间隔$ d_{i}=i^{2}$个单元的位置是否为空,
      如果为空,将地址存放进去。

    • di=伪随机数序列,称为伪随机探测。

    下图为线性探测:
    在这里插入图片描述


    2.3 公共溢出区

    主要思想是建立一个独立的公共区,把冲突的键值对都放在其中。不常用,这里不再细述。


    2.4 再Hash法

    主要思想是有冲突时,换另外一个Hash函数来算Hash值。不常用,这里不再细述。



    3. 实现哈希表的操作方法

    主要是:

    • insert
    • remove
    • get
    • contains_key
    • …等等…

    其中最重要的是插入、查找、删除这三个操作。

    本文学自:
    哈希表原理
    Hash table

    展开全文
  • Hash哈希表原理

    2020-10-27 17:45:17
    哈希表原理:如果说每一个数据它都对应着一个固定的位置,那我们查找特定一个数据时,就可以直接查看这个数据对应的位置是否存在数据。一个形象的例子就是学生在教室中的位置,开学的时候,老师会给学

    数组:索引固定域。
    哈希表:映射对关系。
    哈希表是一种数据结构,其根据关键字实现数据内容的快速查询(映射关系)。

    1. 哈希表哪些优点?
      不论哈希Hash表中存储的数据量有多少,通通采用映射访问的机制一步找到目标数据,所以对于数据的增加/删除/改写,基于时间参考基准下的时间复杂度平均都是O(1),效率非常高。
    2. 哈希表实现底层码
      哈希表原理:如果说每一个数据它都对应着一个固定的位置,那我们查找特定一个数据时,就可以直接查看这个数据对应的位置是否存在数据。一个形象的例子就是学生在教室中的位置,开学的时候,老师会给学生每一个人分配一个位置,而且不允许学生随便乱坐位置,以后老师要查看今天李刚同学有没有上课,直接看李刚同学的位置是不是有人就可以判断,没必要点了全班同学的名才可以知道李刚同学来了没有。
      实现简单的哈希表
      根据上面的原理,首先,我们要分配一片空间用来存储我们数据,比如是一个空的数组
      在这里插入图片描述
      然后,有数据存进来的时候,按照特定规则得出这个数据在数组中的位置,将数据存进这个位置
      我们就以存进一个整型数据为例,特定规则就是取余
      在这里插入图片描述
      根据计算出来的值,将这些数据放入对应的位置,我们的数组变为
      在这里插入图片描述
      我们已经把数据插入到了哈希表中,现在,我们要查找一个数据,只要按照取余规则计算出这个数据在数组中对应的位置,然后查看数组的这个位置,就可以取出这个数据了,比如我们要从哈希表中取出52,根据取余规则,52的计算出来的位置是8,数组中8这个位置是空的,52不在哈希表中,找不到52的数据;从哈希表中取出77,77计算出来的位置是0,数组中0这个位置有值,而且值就是77,从哈希表中取出77的值。
      至此,我们知道实现了一个很简单的哈希表的原理,其实还存在很多问题,这个我们接下来讨论,这儿先把我们前面的一些概念用专业的术语替换一下,前面我们所说的特定规则,我们称之为哈希函数,用特定股则计算出来的值称之为哈希值。
    3. 还存在哪些问题?
        1. 有可能两个数据通过哈希函数计算出来的哈希值有可能相等,比如77,88计算出来的位置值都是0
        2. 如果哈希表满了,该怎么扩容
      第一个问题就是如何解决这种冲突
      有开放定址法,链定址法,我们说一下开放定址法,就是将这个冲突的数据再重新计算一个空的位置,将其存进去,比如我们要存放88,哈希值是0,数组这个位置已经有值了,那我们再获取一个哈希值,比如在原哈希值的基础上加1,得到1,1的位置是空,我将88放进去。有人会问,1这个位置被占了,那下一个数据是1这个位置怎么办,这时候,我们还是同样的做法,给这个数据再计算一个哈希值。
      插入88后的数组变为
      在这里插入图片描述
      冲突解决了,但我们读取数据的时候,好像又出现问题了,88的哈希值是0,发现数组0位置不是空的,那我们确定88在哈希表中?肯定不行,0这个位置存储的是77,不是88。我们的解决方法是判断0这个位置的值是不是88,不是的话,再计算88的哈希值是1,判断是1这个位置是否为空,为空,则88不在哈希表中;不为空,判断值是否为88,若是88,确定在哈希表中;如果值不是88,我们则继续计算哈希值是2,依次下去,直到找到88或者值为空的位置。
      第二个问题,哈希表扩容
      一个简单的解决办法是,当插入数据时,发现所有的位置都满了,我们就再分配一个大于原先空间的一片空间,把原来空间中的值重新哈希到新的空间中。
      哈希表的python实现
      python中的字典就是哈希表,下面代码实现了一个简单的字典
    class Dict:
        def __init__(self, size=10):
            self.size = size
            self.key = [None] * self.size
            self.data = [None] * self.size
        def __setitem__(self, key, value):
            assert isinstance(key, int)
            index = self.hash(key)
            if not self.key[index]:
                self.key[index] = key
                self.data[index] = value
            elif self.key[index] == key:
                self.data[index] = value
            else:
                start = index
                while self.key[index] and self.key[index] != key:
                    index = self.re_hash(index)
                    if index == start:
                        raise Exception('dict is full')
                if self.key[index]:
                    self.data[index] = value
                else:
                    self.key[index] = key
                    self.data[index] = value
        def __getitem__(self, item):
            assert isinstance(item, int)
            index = self.hash(item)
            if not self.key[index]:
                raise KeyError(item)
            else:
                if self.key[index] == item:
                    return self.data[index]
                else:
                    start = index
                    while self.key[index] and self.key[index] != item:
                        index = self.re_hash(index)
                        if start == index:
                            raise KeyError(item)
    
                    if self.key[index] == item:
                        return self.data[index]
                    else:
                        raise KeyError(item)
        def __contains__(self, item):
            assert isinstance(item, int)
            index = self.hash(item)
            if not self.key[index]:
                return False
            else:
                if self.key[index] == item:
                    return True
                else:
                    start = index
                    while self.key[index] and self.key[index] != item:
                        index = self.re_hash(index)
                        if start == index:
                            break
                    if self.key[index] == item:
                        return True
                    else:
                        return False
        def hash(self, key):
            index = key % self.size
            return int(index)
        def re_hash(self, index):
            return index+1
    a = Dict()
    a[1]='3'
    a[2]='4'
    print(a[5])
    

    总结:
    哈希表对于数据的插入/删除/更新操作复杂度平均是O(1),效率非常高。如果不关心数据的存储顺序,我们可以选取这种数据结构。

    展开全文
  • 哈希表原理解析

    2019-03-25 23:22:49
    一、哈希表概念 哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构,它通过映射函数...二、哈希表原理 根据上述的概念,我们可以理解:所有的key都会根据一个方法计算出对应...

    一、哈希表概念

    哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构,它通过映射函数把关键码值映射到表中一个位置来访问记录,以加快查找的速度。关键码值(Key value)也可以当成是key的hash值,这个映射函数叫做散列函数。而存放记录的数组叫做散列表

    二、哈希表原理

    根据上述的概念,我们可以理解:所有的key都会根据一个方法计算出对应的Hash值,而Hash值都会存储在一个数组中。数组是一个内存连续的内存块,查询速度快。如果我们得到一个key,就可以快速定位到数组的某个位置。而key存储到一个节点中,这个节点的前一个节点地址就是数组中存储hash的内存地址。所以,每个key的hash都存在数组中,而key则存在以数组对应的某个元素为头节点的链中。

    现在我有一组key值[23,45,8,49,99,56],其中每一个key都会根据某函数计算出hash,具体的存储方式如下图:

     

    这样会存在一个问题,那就是hash冲突。也就是不同的key根据某函数计算出的hash是一样,那该怎么办呢?看过HashMap源码的小伙伴都知道,HashMap是采用链地址的方法来解决hash冲突的。如下图,如果存在一个key=17的hash和key=49的hash值是一样的。

    由上图可以看到,冲突的key值会连接到重复hash的key值所在的链中,这样就解决了hash冲突的问题。而链表对于增删改查来说,效率是非常高。

    对于存储key的hash数组来说,当hash值的数量达到一定限度时,会自动进行扩容。而这个限度叫做装填因子。但是数组的扩容是非常耗性能的操作,所以在实际的应用中,这个数组的容量是固定的,不进行扩容的操作。比如手机电话薄的容量,微信好友容量等。

    三、JDK1.8之后哈希表源码的不同

    在JDK1.8之后,HashMap在处理Hash冲突时的细节操作会有不同。当添加相同hash的key时,数量达到一定限度就会采用树的结构来进行数据的存储。因为涉及千万级的数据时,链的长度会非常的长,非常不利于数据的查询。而树的结构会大大减少查询的时间。在下一篇博客,我会介绍树状这个数据结构。

     

    欢迎提问,欢迎纠错!

    展开全文
  • 哈希表原理 其实map是一种HashMap,表面上看它只有键值对结构,实际上在存储键值对的过程中涉及到了数组和链表。HashMap之所以高效,是因为其结合了顺序存储(数组)和链式存储(链表)两种存储结构。数组是HashMap的...

    哈希表原理

    其实map是一种HashMap,表面上看它只有键值对结构,实际上在存储键值对的过程中涉及到了数组和链表。HashMap之所以高效,是因为其结合了顺序存储(数组)和链式存储(链表)两种存储结构。数组是HashMap的主干,在数组下有有一个类型为链表的元素。

    在这里插入图片描述
    当我们存储一个键值对时,HashMap会首先通过一个哈希函数将key转换为数组下标,真正的key-value是存储在该数组对应的链表里。

    当发生哈希碰撞时,键值对就会存储在该数组对应链表的下一个节点上。

    HashMap的操作效率也是很高的。当不存在哈希碰撞时查找复杂度为O(1),存在哈希碰撞时复杂度为O(N)

    计算key的哈希值

    哈希函数的选取应尽可能使新增的键值对均匀地分布在数组里。

    golang map

    go中map结构

    map的底层结构是hmap(即hashmap的缩写),核心元素是一个由若干个桶(bucket,结构为bmap)组成的数组,每个bucket可以存放若干元素(通常是8个),key通过哈希算法被归入不同的bucket中。当超过8个元素需要存入某个bucket时,hmap会使用extra中的overflow来拓展该bucket

    hmap的结构体如下:

    type hmap struct {
    	count     int // # 元素个数
    	flags     uint8
    	B         uint8  // 说明包含2^B个bucket
    	noverflow uint16 // 溢出的bucket的个数
    	hash0     uint32 // hash种子
     
    	buckets    unsafe.Pointer // buckets的数组指针
    	oldbuckets unsafe.Pointer // 结构扩容的时候用于复制的buckets数组
    	nevacuate  uintptr        // 搬迁进度(已经搬迁的buckets数量)
     
    	extra *mapextra
    }
    
    

    bucket(bmap)的结构如下

    type bmap struct {
    	tophash [bucketCnt]uint8
    }
    
    • tophash用于记录8个key哈希值的高8位,这样在寻找对应key的时候可以更快,不必每次都对key做全等判断。
    • bucket并非只有一个tophash,而是后面紧跟8组kv对和一个overflow的指针,这样才能使overflow成为一个链表的结构。但是这两个结构体并不是显示定义的,而是直接通过指针运算进行访问的。
    • kv的存储形式为key0key1key2key3…key7val1val2val3…val7,这样做的好处是:在keyvalue的长度不同的时候,节省padding空间。如上面的例子,在map[int64]int8中,4个相邻的int8可以存储在同一个内存单元中。如果使用kv交错存储的话,每个int8都会被padding占用单独的内存单元(为了提高寻址速度)。

    在这里插入图片描述

    map存储值

    首先用key的hash值低8位找到bucket,然后在bucket内部比对tophash和高8位与其对应的key值与入参key是否相等,若找到则更新这个值。若key不存在,则key优先存入在查找的过程中遇到的空的tophash数组位置。若当前的bucket已满则需要另外分配空间给这个key,新分配的bucket将挂在overflow链表后。

    map的增长

    随着元素的增加,在一个bucket链中寻找特定的key会变得效率低下,所以在插入的元素个数/bucket个数达到某个阈值(当前设置为6.5,实验得来的值)时,map会进行扩容,代码中详见 hashGrow函数。首先创建bucket数组,长度为原长度的两倍

    newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger)
    

    ,然后替换原有的bucket,原有的bucket被移动到oldbucket指针下。

    扩容完成后,每个hash对应两个bucket(一个新的一个旧的)。oldbucket不会立即被转移到新的bucket下,而是当访问到该bucket时,会调用growWork方法进行迁移,growWork方法会将oldbucket下的元素rehash到新的bucket中。随着访问的进行,所有oldbucket会被逐渐移动到bucket中。

    但是这里有个问题:如果需要进行扩容的时候,上一次扩容后的迁移还没结束,怎么办?在代码中我们可以看到很多again标记,会不断进行迁移,知道迁移完成后才会进行下一次扩容。

    但这个迁移并没有在扩容之后一次性完成,而是逐步完成的,每一次insertremove时迁移1到2个pair,即增量扩容。增量扩容的原因主要是缩短map容器的响应时间。若hashmap很大扩容时很容易导致系统停顿无响应。增量扩容本质上就是将总的扩容时间分摊到了每一次hash操作上。由于这个工作是逐渐完成的,导致数据一部分在old table中一部分在new table中。oldbucket不会删除,只是加上一个已删除的标记。只有当所有的bucket都从old table里迁移后才会将其释放掉。

    map中注意点

    1. 删除掉map中的元素是否会释放内存?

      不会,删除操作仅仅将对应的tophash[i]设置为empty,并非释放内存。若要释放内存只能等待指针无引用后被系统gc
      
    2. 如何并发地使用map?

      map不是goroutine安全的,所以在有多个gorountine对map进行写操作是会panic。多gorountine读写map是应加锁(RWMutex),或使用sync.Map
      
    3. map的iterator是否安全?

      map的delete并非真的delete,所以对迭代器是没有影响的,是安全的。
      

    转自:<https://blog.yiz96.com/golang-map/>

    展开全文
  • 哈希表原理详解

    2019-10-11 17:24:28
    介绍哈希表(散列表)相关原理。 1. 哈希表介绍 哈希表由来 在哈希表出现之前,已经存在了两种数据结构–数组和链表,但是各有优缺点,适用场景也不同: 类型 优缺点 数组 寻址容易,插入和...
  • 哈希存储、哈希表原理

    万次阅读 2018-06-29 11:15:24
    哈希表的定义哈希存储的基本思想是以关键字Key为自变量,通过一定的函数关系(散列函数或哈希函数),计算出对应的函数值(哈希地址),以这个值作为数据元素的地址,并将数据元素存入到相应地址的存储单元中。...
  • 主要讲解哈希表原理、冲突、扩容的相关知识
  • 数据结构-哈希表原理详解

    千次阅读 2017-08-05 14:05:35
    1.什么是哈希表 (摘自百度百科):散列表(Hash table,也叫哈希表),是根据关键码值(Key ...2.哈希表原理 (摘自百度百科)给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关...
  • 假设有一个数组An= {81, 62, 55, 43, 97, 76, 49, 98, 64};...提出哈希表的人, 确实很了不起, 因为他没有采用正常人的思路。我们来看看哈希表人的思路: 哈希的思想是:你给我一个key和value...
  • 哈希表原理+题目总结

    2020-03-13 13:29:24
    哈希表是一种数据结构,它使用哈希函数组织数据,以支持快速插入和搜索。 哈希表的关键思想是使用哈希函数将键映射到存储桶 哈希表使用 O(N) 空间复杂度存储数据,并且以 O(1) 时间复杂度求解问题。 哈希函数/散列...
  • 哈希表原理(直观易懂解释)

    万次阅读 多人点赞 2018-07-15 23:19:07
    理解哈希表原理,其核心是理解所谓的哈希函数(散列函数)。即如何构建一个确定的映射,它能把关键字映射到一个唯一的存储位置。这种映射应该是我们可以进行计算的。已知关键字,我们应该能算出其地址;反之,已知...
  • 哈希表原理及简单设计

    千次阅读 2020-03-09 01:04:44
    哈希表 是一种使用哈希函数组织数据,以支持快速插入和搜索的数据结构。 有两种不同类型的哈希表:哈希集合 和 哈希映射。 哈希集合是 集合 数据结构的实现之一,用于存储 非重复值。 哈希映射是 映射 数据结构的...
  • 概述 上篇博客我简单整理了 redis 中链表的实现原理。本篇博客我打算就哈希类型简单整理一下。 redis 数据类型 redis 有以下五种常用的数据类型: String:字符串类型 Hash:哈希类型 ... // 哈希表大小
  • 下面的内容来自于一位老司机 martin的源码,博主在这里借用一下,目的是突出哈希表原理,明天博主就周末了,也能腾出时间来给上传自己的哈希表的应用。 这个是可以插入字符串的哈希表,一般的都是对数字的操作,所以这...
  • 哈希表是一种有效的动态集合结构,插入、删除、查找平均运行时间为O(1)。 1、直接寻址表 如果集合的全域有限且表中元素个数与全域个数接近,直接寻址表是一种高效的数据结构。插入、删除、平均运行时间均为O(1)。...
  • 哈希表原理与c++例题

    2019-07-17 03:00:04
    实现方法,建立两个相反的哈希表,即倒排索引的概念,删除时,用最后一项替换删除项,以保持哈希值的连续性。 位图 位图通过其他的数据类型实现,通过其他数组来拼成,对于仅需要两种状态的数组,可以极大的省空间。...
  • 哈希表原理及hashmap简单实现

    千次阅读 2016-04-07 19:46:49
    哈希表也叫做散列表。在各种语言中都有hashmap的实现。其最突出的优点是查找和插入以及删除具有常数的时间复杂度   我们可以把哈希表理解为数组+链表 数组具有常数复杂度的查找,为什么呢,因为数组是在内存中...
  • hash 哈希表原理

    千次阅读 2008-08-25 23:29:00
    言归正传,哈希表又名散列表,其主要目的是用于解决数据的快速定位问题。考虑如下一个场景。 一列键值对数据,存储在一个table中,如何通过数据的关键字快速查找相应值呢?不要告诉我一个个拿出来比较key啊,呵呵。 ...
  • 散列表 哈希表 原理 python实现

    千次阅读 2016-08-26 09:44:02
    参考算法导论引文 散列表(hash table)是实现字典操作的一种有效的数据结构。...冲突和解决利用哈希函数h(k),可以把关键字映射到一个小的数组中,但是会发生冲突。解决方法有链接法和开放寻址法。链接法以链
  • 带内存池的哈希表原理

    千次阅读 2014-03-31 09:14:24
  • 哈希表(散列表)原理详解

    万次阅读 多人点赞 2018-07-03 19:40:58
    什么是哈希表哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数...
  • 哈希表原理不再冗述,以下只记录关键原理。 哈希表的长度一般是定长的,在存储数据之前我们应该知道我们存储的数据规模是多大,应该尽可能地避免频繁地让哈希表扩容。但是如果设计的太大,那么就会浪费空间,因为...
  • 哈希表

    2019-07-10 17:12:27
    1.哈希表原理讲解 2 哈希表leetcode求和
  • 哈希表原理

    2021-03-06 16:15:52
    哈希表原理 引入哈希表 在无序数组中按照内容查找,效率低下,时间复杂度为O(n): 在有序数组中按照内容查找,可以使用折半查找,时间复杂度O(log2n): 在二叉平衡树中按照内容查找,时间复杂度O(log2n) : ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 102,953
精华内容 41,181
关键字:

哈希表的原理