hash算法

hash基础

hash: 一般翻译做“散列”,也有直接音译为“哈希”的

  1. Hash算法(哈希算法、散列算法)

    • 就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。
    • 也可以理解为空间映射函数,是从一个非常大的取值空间映射到一个非常小的取值空间,是一种压缩映射,即散列值的空间通常远小于输入的空间。
      (ps:y = x^2 )
    • 不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值- -> 输入与输出存在多对一的关系,只能单向加密。
    • 它是一种单向密码体制,即它是一个从明文到密文的不可逆的映射,只有加密过程,没有解密过程。
  2. 主要思想

    根据结点的关键码值来确定其存储地址:以关键码值为自变量,通过一定的函数关系h(K)(称为散列函数),计算出对应的函数值来,把这个值解释为结点的存储地址,将结点存入到此存储单元(哈希表)中。

    检索时,用同样的方法计算地址,然后到相应的单元里去取要找的结点(值和地址一对一关系,set不可存放可变类型)。

    这就实现了:通过散列方法可以对结点进行快速检索。

hash图解

数据集合U={d1,d2…di},每个数据di值为datai,根据data选择相应的关键值key进行了哈希函数运算得出的[0,m]的整数H(key),放入对应的哈希表A[key]位置

  1. dict与set

    在python中,dict与set实现原理是一样的,都是将实际的值放到list中。

    唯一不同的是hash函数操作的对象,对于dict,hash函数操作的对象是key:

    而对set是直接操作它的元素,假设操作内容为x,其作为因变量,放入hash函数,通过运算后取list的余数,转化为list的一个下标,此下标位置对于set而言用来放其本身 ;
    而对于dict则是创建了两个list,一个list该下标放次key,另一个list中该下标放对应的value

常用的构造散列函数的方法

通常可利用除留余数、移位、循环哈希、平方取中等方法

  1. 除留余数法

    取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p, p<=m。

    不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。

     KEYS = (12, 6554, 12345, 34234, 234234, 6456456, 34534, 67645, 2343432, 23423, 1343324)
     hash_table_len = len(KEYS) + 2
     hash_table = {}
     
     #  哈希函数
     def hash_func(k):
         return k % hash_table_len
     
     def hash_insert(k):
         k_tno = hash_func(k)
         if hash_table[k_tno] == None:
             hash_table[k_tno] = k
         else:
             for i in range(k_tno+1, hash_table_len):
                 if hash_table[i] == None:
                     break
             hash_table[i] = k
         return k_tno
     
     def hash_search(k):
         j = hash_func(k)
         if k == hash_table[j]:
             return j
         else:
             for i in range(j+1, hash_table_len):
                 if k == hash_table[i]:
                     break
             return i
     
     if __name__ == '__main__':
         for i in range(hash_table_len):
             hash_table[i] = None
         for k in KEYS:
             hash_insert(k)
         print("构造哈希表如下:")
         print(hash_table)
     
         print("查询:")
         for k in KEYS:
             print("{0} 所在哈希表的位置是:{1}".format(k, hash_search(k)))
    
  2. 直接寻址法

    取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a*key + b,其中a和b为常数(这种散列函数叫做自身函数)

     def hash_func(k):
         return a * key + b
    
  3. 数字分析法

    数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。
    (分析一组数据,比如一组员工的出生年月日,这时我们发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。)

  4. 平均取中法

    取关键字平方后的中间几位作为散列地址,这种方法的原理是通过取平方扩大差别,平方值的中间几位和这个数的每一位都相关,则对不同的关键字得到的哈希函数值不易产生冲突,由此产生的哈希地址也较为均匀。
    如44平方后得1936,取中93再取list的余 。

     KEYS = (12, 6554, 12345, 34234, 234234, 6456456, 34534, 67645, 2343432, 23423, 1343324)
     #  哈希函数
     def hash_func(k):
         k2 = str(k**2)
         k2_len = len(k2)//2
         return int(k2[k2_len-1:k2_len+2])
     
     if __name__ == '__main__':
         se = set()
         for k in KEYS:
             print(k,'对应的hash值为:',hash_func(k))
             se.add(hash_func(k))
         print(len(se),se)
    
  5. 折叠法

    将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。
    如项目是436-555-4601,以2为分组,分成了(43,65,55,46,01),全部加起来:43+65+55+46+01=210.
    假设list有11个元素,则210%11=1,所以将436-555-4601放在下标为1的地方。

  6. 随机数法

    H(key) = random(key),选择一随机函数,取关键字的随机值作为散列地址,通常用于关键字长度不同的场合。

hash碰撞及其解决方法

哈希函数是以数据的关键值作为自变量x,输出的y就是数据对于的散列值

会存在两个数据通过哈希函数产生相同输出的情况,即两个元素通过散列函数H得到的地址相同:
即 H(keyi)= H(keyj),这就叫hash碰撞(或hash冲突)

  1. 冲突主要取决于

    • 散列函数,一个好的散列函数的值应尽可能平均分布。
    • 处理冲突方法。
    • 负载因子的大小。太大不一定就好,而且浪费空间严重,负载因子和散列函数是联动的。
  2. 解决冲突的办法:

    • 线性探查法:冲突后,线性向前试探,找到最近的一个空位置。缺点是会出现堆积现象。存取时,可能不是同义词的词也位于探查序列,影响效率。
    • 向后探测:假设一个元素算出来下标为5,另一个元素算出来下标也为5,从开头开始探测第0第1位是否为空,当看到为空的就放入,不过这样相邻探测的不好之处在于容易发生聚集,所以最好是跳跃着进行探测,定义一个skip的值,比如3,用方程rehash(pos) = (pos + skip)%sizeoftable,即使查看0,3,6这样跳跃着来
    • 双散列函数法:在位置d冲突后,再次使用另一个散列函数产生一个与散列表桶容量m互质的数c,依次试探(d+n*c)%m,使探查序列跳跃式分布。
    • 链式存储:将发生冲突的元素放到同一位置,然后通过“指针”来串联起来。

Python自带的hash–常用hash算法介绍

常见 Hash 算法有 MD5 和 SHA 系列,目前 MD5 和 SHA1 已经被破解,一般推荐至少使用 SHA2-256 算法。

  1. 内置函数hash()

    在python中有内置的哈希函数hash(),返回一个对象(数字、字符串,不能直接用于 list、set、dictionary)的哈希值。

     print(type(13742),'hash值为:',hash(13742)) # <class 'int'> hash值为: 13742
     print(type(13742.0),'hash值为:',hash(13742.0)) # <class 'float'> hash值为: 13742
    

    相同的数值,不同类型,哈希值是一样的

     print(type('13742'),'hash值为:',hash('13742'))  # <class 'str'> hash值为: 8985398967764240863
     print(type('13742'),'hash值为:',hash('13742'))  # <class 'str'> hash值为: 8985398967764240863
    

    相同字符串在同一次运行时的哈希值是相同的,但是不同次运行的哈希值不同
    因为Python的字符串hash算法有一个启动时随机生成secret prefix/suffix的机制,存在随机化现象:对同一个字符串输入,不同解释器进程得到的hash结果可能不同。

  2. hashlib模块:实现了对同一字符串data,两次运行结果相同

    hashlib提供了常见的摘要算法,如MD5,SHA1等等;而且对字符串当需要做可重现可跨进程保持一致性的hash,需要用到hashlib模块。

    使用hashilib模块一般分为4步:

    • 在python中引用hashlib模块。
    • 创建一个hash对象,使用hash算法命名的构造函数,或者通用构造函数。
    • 使用hash对象调用update(arg)方法填充对象:将字节对象arg填充到hashlib对象中,arg通常为要加密的字符串
    • 调用digest()或者hexdigest()方法来获取摘要(加密结果):
      hash对象.digest()返回加密结果 ,它是一个字节对象,长度为md5.digest_size
      hash对象.hexdigest() 返回加密结果,它是一个字符串对象,长度为md5.digest_size*2,只包含16进制数字
    1. MD4算法

      基于 32位操作数的位操作来实现的

    2. MD5算法

      对输入仍以512位分组,其输出是4个32位字的级联,与 MD4 相同。MD5比MD4来得复杂,并且速度较之要慢一点,但更安全,在抗分析和抗差分方面表现更好。

    3. SHA-1

      对长度小于264的输入,产生长度为160bit(16进制:长度40)的散列值,因此抗穷举(brute-force)性更好。SHA-1 设计时基于和MD4相同原理,并且模仿了该算法。

    4. SHA-256

      输入是最大长度小于264位的消息,输出是256bit(16进制:长度64)的消息摘要

    5. SHA-384

      最大计算明文长度为2^128bit,属于分组算法,分组长度为1024bit,产生的信息摘要长度为6*64bit(长度6*64/4=96)

    6. SHA-512

      整个格式化的信息基本上有三个部分:原始消息、填充位、原始消息的大小。它们的组合大小应该是1024位的整数倍。这是因为格式化的消息将被处理为每个1024位的块,因此每个块应该有1024位可以使用。
      输出是8*64bit(长度8*64/4=128)的消息摘要

    常用算法详解参考:http://www.sohu.com/a/232586831_100078137

         import hashlib
         data = 'hello world'
         
         #1.MD5
         md5 = hashlib.md5()
         md5.update(data.encode('utf-8'))
         print('md5 拼接前:{0},长度为:{1}'.format(md5.hexdigest(),len(md5.hexdigest())))
         md5.update(data.encode('utf-8'))
         print('mad5 拼接后:{0},长度为:{1}'.format(md5.hexdigest(),len(md5.hexdigest())))
        
         #2.SHA-1
         sha1 = hashlib.sha1()
         sha1.update(data.encode('utf-8'))
         print('SHA-1:{0},长度为:{1}'.format(sha1.hexdigest(),len(sha1.hexdigest())))
         
         #3.SHA-256
         sha256 = hashlib.sha256()
         sha256.update(data.encode('utf-8'))
         print('SHA-256:{0},长度为:{1}'.format(sha256.hexdigest(),len(sha256.hexdigest())))
       
         #4.SHA-384
         sha384 = hashlib.sha384()
         sha384.update(data.encode('utf-8'))
         print('SHA-384:{0},长度为:{1}'.format(sha384.hexdigest(),len(sha384.hexdigest())))
         
         #5.SHA-512
         sha512 = hashlib.sha512()
         sha512.update(data.encode('utf-8'))
         print('SHA-512:{0},长度为:{1}'.format(sha512.hexdigest(),len(sha512.hexdigest())))
    

    需要注意:update会进行字符串拼接,即拼接前后运行结果不同,需要重新md5 = hashlib.md5()

       sha512.update(data.encode('utf-8'))
         print('SHA-512:{0},长度为:{1}'.format(sha512.hexdigest(),len(sha512.hexdigest())))
    

    需要注意:update会进行字符串拼接,即拼接前后运行结果不同,需要重新md5 = hashlib.md5()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值