精华内容
下载资源
问答
  • 哈希函数特征_哈希函数及其特征

    千次阅读 2020-07-06 22:57:28
    哈希函数特征Prerequisite: Hashing data structure 先决条件: 哈希数据结构 The hash function is the component of hashing that maps the keys to some location in the hash table. As part of the hashing...

    哈希函数的特征

    Prerequisite: Hashing data structure

    先决条件: 哈希数据结构

    The hash function is the component of hashing that maps the keys to some location in the hash table. As part of the hashing technique, we need a hash function to map the available keys to the set of indexes in the hash table. Say we have a set of keys ranging from [-1000, 1000] and have a hash table of size 10, index ranging from [0, 10].

    哈希函数是哈希的组件,它将密钥映射到哈希表中的某个位置。 作为哈希技术的一部分,我们需要一个哈希函数将可用键映射到哈希表中的索引集。 假设我们有一组范围从[-1000,1000]的键,并且有一个大小为10的哈希表,索引范围是[0,10]。

    That's why to map the keys we need hash functions. A hash function is termed as the perfect hash function is what is able to map the keys to unique locations. But unfortunately, there is no systematic way to generate hash function as the size of the key list is very large considered to hash table size.

    这就是为什么要映射我们需要哈希函数的键的原因。 哈希函数被称为完美哈希函数,它是能够将密钥映射到唯一位置的函数。 但是遗憾的是,由于键列表的大小对于哈希表的大小而言非常大,因此没有系统的方法来生成哈希函数。

    A popular hash function is a folding method where we fold the integers and sum the partitions and then take mod 10 finally.

    流行的哈希函数是一种折叠方法 ,其中我们折叠整数并将分区求和,然后最终取mod 10。

    For example,

    例如,

    The hash function for the folding method is = 
        sum of the fold of size two %10
    Say we have integer 60784567
    So sum will be 60 + 78 + 45 + 67 = 250
    So final location is 250 % 10 = 0
    
    

    The below program computes the above folding method which is an example of the hash function.

    下面的程序将计算上述折叠方法 ,该方法哈希函数的一个示例。

    #include <bits/stdc++.h>
    using namespace std;
    
    //folding method
    
    int main()
    {
        string s;
      
        cout << "enter number\n";
        cin >> s;
    
        //folding and summing
        int sum = 0;
        for (int i = 0; i < s.length(); i += 2) {
            if (i + 1 < s.length())
                sum += stoi(s.substr(i, 2));
            else
                //when only one digit is left for folding
                sum += stoi(s.substr(i, 1));
        }
    
        cout << s << "->" << sum % 10;
    
        return 0;
    }
    
    

    Output:

    输出:

    enter number
    60784567
    60784567->0
    
    

    Now if some other number also finds location 0, then it's called collision.

    现在,如果其他一些数字也找到位置0,则称为碰撞

    Characteristics of good hash function

    哈希函数良好的特征

    1. Minimum collision

      最小碰撞

    2. High gain factor (distributes keys evenly). Like say we have 10 locations in the hash table, then almost 9 locations should have keys. It should not be the case that 4-5 locations have keys only where the keys collided.

      高增益因子(均匀分布密钥)。 像说在哈希表中有10个位置,那么几乎9个位置应该有键。 并非4-5个位置仅在按键碰撞的地方具有按键。

    3. Have a high load factor. Load factor means the number of keys stored/hash table size

      负载系数高。 负载因子是指存储的密钥数/哈希表大小

    4. Easy to compute

      易于计算

    Exercises on hash function

    哈希函数练习

    Use the below hash function to compute the hashing and comment on the goodness of the hash function.

    使用下面的哈希函数来计算哈希并评论哈希函数的优缺点。

    1) F(key) = number of digits of key

    1)F(key)=密钥位数

    #include <bits/stdc++.h>
    using namespace std;
    
    //hash function 1
    int main()
    {
        string s;
        cout << "enter number\n";
        cin >> s;
    
        //f(s)=no of digit in s=length of s
        cout << s << "->" << s.length();
    
        return 0;
    }
    
    

    Output:

    输出:

    enter number
    123452
    123452->6
    
    

    The above hash function is not good at all. Say we have set of keys all having the same digits, then all the keys will collide and the rest of the locations will remain empty.

    上面的哈希函数根本不好。 假设我们有一组键都具有相同的数字,那么所有键将发生冲突,其余位置将保持为空。

    2) F(key) = (rand()*key) % 10

    2)F(键)=(rand()*键)%10

    #include <bits/stdc++.h>
    using namespace std;
    
    //hash function 2
    int main()
    {
        int n;
        cout << "enter number\n";
        cin >> n;
    
        //f(n)=rand()*n
        cout << n << "->" << (rand() * n) % 10;
    
        return 0;
    }
    
    

    Output:

    输出:

    enter number
    103456
    103456->2
    
    

    The above hash function is good as we are multiplying random integers and bringing randomness, the collision rate will be less.

    上面的哈希函数很好,因为我们要乘以随机整数并带来随机性,则冲突率会更低。

    Comparison of the above hash functions:

    上面的哈希函数比较:

    #include <bits/stdc++.h>
    using namespace std;
    
    //comparing goodness of hash function 1 &  2
    int main()
    {
    
        //set of input numbers
        vector<int> arr{ 12345, 234245, 1223123, 765845, 345234, 234534, 98675, 34523, 123, 3245 };
    
        //using hash function 1
        cout << "using hash function 1\n";
        for (int a : arr) {
            cout << a << "->" << to_string(a).length() % 10 << endl;
        }
    
        //using hash function 2
        cout << "\n\nusing hashh function 2\n";
        for (int a : arr) {
            cout << a << "->" << (rand() * a) % 10 << endl;
        }
    
        return 0;
    }
    
    

    Output:

    输出:

    using hash function 1
    12345->5
    234245->6
    1223123->7
    765845->6
    345234->6
    234534->6
    98675->5
    34523->5
    123->3
    3245->4
    
    
    using hashh function 2
    12345->9
    234245->4
    1223123->3
    765845->-1
    345234->-4
    234534->4
    98675->6
    34523->0
    123->5
    3245->-9
    
    
    

    翻译自: https://www.includehelp.com/data-structure-tutorial/hash-functions-and-its-characteristics.aspx

    哈希函数的特征

    展开全文
  • 哈希函数

    万次阅读 多人点赞 2018-03-01 08:12:14
    在某种程度上,散列是与排序相反的一种操作,排序是将集合中的元素按照某种方式比如字典顺序排列在一起,而散列通过计算哈希值,打破元素之间原有的关系,使集合中的元素按照散列函数的分类进行排列。在介绍一些集合...

    什么是 Hash

    Hash(哈希),又称“散列”。

    散列(hash)英文原意是“混杂”、“拼凑”、“重新表述”的意思。

    在某种程度上,散列是与排序相反的一种操作,排序是将集合中的元素按照某种方式比如字典顺序排列在一起,而散列通过计算哈希值,打破元素之间原有的关系,使集合中的元素按照散列函数的分类进行排列。

    在介绍一些集合时,我们总强调需要重写某个类的 equlas() 方法和 hashCode() 方法,确保唯一性。这里的 hashCode() 表示的是对当前对象的唯一标示。计算 hashCode 的过程就称作 哈希。

    为什么要有 Hash

    我们通常使用数组或者链表来存储元素,一旦存储的内容数量特别多,需要占用很大的空间,而且在查找某个元素是否存在的过程中,数组和链表都需要挨个循环比较,而通过 哈希 计算,可以大大减少比较次数

    这里写图片描述

    举个栗子:

    现在有 4 个数 {2,5,9,13},需要查找 13 是否存在。

    1.使用数组存储,需要新建个数组 new int[]{2,5,9,13},然后需要写个循环遍历查找:

        int[] numbers = new int[]{2,5,9,13};
        for (int i = 0; i < numbers.length; i++) {
            if (numbers[i] == 13){
                System.out.println("find it!");
                return;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这样需要遍历 4 次才能找到,时间复杂度为 O(n)。

    2.而假如存储时先使用哈希函数进行计算,这里我随便用个函数:

     H[key] = key % 3;
    
    • 1
    • 2

    四个数 {2,5,9,13} 对应的哈希值为:

     H[2] = 2 % 3 = 2;
     H[5] = 5 % 3 = 2;
     H[9] = 9 % 3 = 0;
     H[13] = 13 % 3 = 1;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    然后把它们存储到对应的位置。

    当要查找 13 时,只要先使用哈希函数计算它的位置,然后去那个位置查看是否存在就好了,本例中只需查找一次,时间复杂度为 O(1)。

    因此可以发现,哈希 其实是随机存储的一种优化,先进行分类,然后查找时按照这个对象的分类去找。

    哈希通过一次计算大幅度缩小查找范围,自然比从全部数据里查找速度要快。

    比如你和我一样是个剁手族买书狂,家里书一大堆,如果书存放时不分类直接摆到书架上(数组存储),找某本书时可能需要脑袋从左往右从上往下转好几圈才能发现;如果存放时按照类别分开放,技术书、小说、文学等等分开(按照某种哈希函数计算),找书时只要从它对应的分类里找,自然省事多了。

    哈希函数

    哈希的过程中需要使用哈希函数进行计算。

    哈希函数是一种映射关系,根据数据的关键词 key ,通过一定的函数关系,计算出该元素存储位置的函数。

    表示为:

    address = H [key]

    几种常见的哈希函数(散列函数)构造方法

    • 直接定址法 
      • 取关键字或关键字的某个线性函数值为散列地址。
      • 即 H(key) = key 或 H(key) = a*key + b,其中a和b为常数。
      • 比如这里写图片描述
    • 除留余数法 
      • 取关键字被某个不大于散列表长度 m 的数 p 求余,得到的作为散列地址。
      • 即 H(key) = key % p, p < m。 
      • 比如这里写图片描述
    • 数字分析法 
      • 当关键字的位数大于地址的位数,对关键字的各位分布进行分析,选出分布均匀的任意几位作为散列地址。
      • 仅适用于所有关键字都已知的情况下,根据实际应用确定要选取的部分,尽量避免发生冲突。
      • 比如 这里写图片描述
    • 平方取中法 
      • 先计算出关键字值的平方,然后取平方值中间几位作为散列地址。
      • 随机分布的关键字,得到的散列地址也是随机分布的。
      • 比如 这里写图片描述
    • 折叠法(叠加法) 
      • 将关键字分为位数相同的几部分,然后取这几部分的叠加和(舍去进位)作为散列地址。
      • 用于关键字位数较多,并且关键字中每一位上数字分布大致均匀。 
      • 比如 这里写图片描述
    • 随机数法 
      • 选择一个随机函数,把关键字的随机函数值作为它的哈希值。
      • 通常当关键字的长度不等时用这种方法。 

    构造哈希函数的方法很多,实际工作中要根据不同的情况选择合适的方法,总的原则是尽可能少的产生冲突

    通常考虑的因素有关键字的长度分布情况哈希值的范围等。

    如:当关键字是整数类型时就可以用除留余数法;如果关键字是小数类型,选择随机数法会比较好。

    哈希冲突的解决

    选用哈希函数计算哈希值时,可能不同的 key 会得到相同的结果,一个地址怎么存放多个数据呢?这就是冲突。

    常用的主要有两种方法解决冲突:

    1.链接法(拉链法)

    拉链法解决冲突的做法是: 
    将所有关键字为同义词的结点链接在同一个单链表中。

    若选定的散列表长度为 m,则可将散列表定义为一个由 m 个头指针组成的指针数组 T[0..m-1] 。

    凡是散列地址为 i 的结点,均插入到以 T[i] 为头指针的单链表中。 
    T 中各分量的初值均应为空指针。

    在拉链法中,装填因子 α 可以大于 1,但一般均取 α ≤ 1。

    这里写图片描述

    2.开放定址法

    用开放定址法解决冲突的做法是:

    用开放定址法解决冲突的做法是:当冲突发生时,使用某种探测技术在散列表中形成一个探测序列。沿此序列逐个单元地查找,直到找到给定的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。查找时探测到开放的地址则表明表中无待查的关键字,即查找失败。

    简单的说:当冲突发生时,使用某种探查(亦称探测)技术在散列表中寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到

    按照形成探查序列的方法不同,可将开放定址法区分为线性探查法、二次探查法、双重散列法等。

    a.线性探查法

    hi=(h(key)+i) % m ,0 ≤ i ≤ m-1 

    基本思想是: 
    探查时从地址 d 开始,首先探查 T[d],然后依次探查 T[d+1],…,直到 T[m-1],此后又循环到 T[0],T[1],…,直到探查到 有空余地址 或者到 T[d-1]为止。

    b.二次探查法

    hi=(h(key)+i*i) % m,0 ≤ i ≤ m-1 

    基本思想是: 
    探查时从地址 d 开始,首先探查 T[d],然后依次探查 T[d+1^2],T[d+2^2],T[d+3^2],…,等,直到探查到 有空余地址 或者到 T[d-1]为止。

    缺点是无法探查到整个散列空间。

    c.双重散列法

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

    基本思想是: 
    探查时从地址 d 开始,首先探查 T[d],然后依次探查 T[d+h1(d)], T[d + 2*h1(d)],…,等。

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

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

    该方法是开放定址法中最好的方法之一。

    哈希的应用

    • 哈希表
    • 分布式缓存

    哈希表(散列表)

    哈希表(hash table)是哈希函数最主要的应用。

    哈希表是实现关联数组(associative array)的一种数据结构,广泛应用于实现数据的快速查找。

    这里写图片描述

    用哈希函数计算关键字的哈希值(hash value),通过哈希值这个索引就可以找到关键字的存储位置,即桶(bucket)。哈希表不同于二叉树、栈、序列的数据结构一般情况下,在哈希表上的插入、查找、删除等操作的时间复杂度是 O(1)。

    查找过程中,关键字的比较次数,取决于产生冲突的多少,产生的冲突少,查找效率就高,产生的冲突多,查找效率就低。因此,影响产生冲突多少的因素,也就是影响查找效率的因素。 
    影响产生冲突多少有以下三个因素:

    1. 哈希函数是否均匀;
    2. 处理冲突的方法;
    3. 哈希表的加载因子。

    哈希表的加载因子和容量决定了在什么时候桶数(存储位置)不够,需要重新哈希。

    加载因子太大的话桶太多,遍历时效率变低;太大的话频繁 rehash,导致性能降低。所以加载因子的大小需要结合时间和空间效率考虑。

    在 HashMap 中的加载因子为 0.75,即四分之三。

    分布式缓存

    网络环境下的分布式缓存系统一般基于一致性哈希(Consistent hashing)。简单的说,一致性哈希将哈希值取值空间组织成一个虚拟的环,各个服务器与数据关键字K使用相同的哈希函数映射到这个环上,数据会存储在它顺时针“游走”遇到的第一个服务器。可以使每个服务器节点的负载相对均衡,很大程度上避免资源的浪费。

    在动态分布式缓存系统中,哈希算法的设计是关键点。使用分布更合理的算法可以使得多个服务节点间的负载相对均衡,可以很大程度上避免资源的浪费以及部分服务器过载。 使用带虚拟节点的一致性哈希算法,可以有效地降低服务硬件环境变化带来的数据迁移代价和风险,从而使分布式缓存系统更加高效稳定。

    Thanks

    http://www.nowamagic.net/librarys/veda/detail/1273

    http://blog.csdn.net/cywosp/article/details/23397179/

    http://www.cnblogs.com/qiaoshanzi/p/5295554.html

    http://baike.baidu.com/view/549615.htm

    https://books.google.co.jp/books?id=wCWmdhdX1AYC&pg=PA214&lpg=PA214&dq=%E6%95%B0%E5%AD%97%E5%88%86%E6%9E%90%E6%B3%95&source=bl&ots=5ieOT99Dob&sig=UcYbua2lwYocCQr32HF0XDF34h4&hl=zh-CN&sa=X&ved=0ahUKEwj104zw__fPAhUDw7wKHf3cAhIQ6AEISzAJ#v=onepage&q=%E6%95%B0%E5%AD%97%E5%88%86%E6%9E%90%E6%B3%95&f=false

    http://sjjp.tjuci.edu.cn/sjjg/DataStructure/DS/web/chazhao/chazhao9.4.3.3.htm

    http://www.cnblogs.com/qiaoshanzi/p/5295554.html

    (function () { ('pre.prettyprint code').each(function () { var lines =  (this).text().split(\n).length;var numbering = $(' ').addClass('pre-numbering').hide(); (this).addClass(hasnumbering).parent().append( numbering); for (i = 1; i
    展开全文
  • 认识哈希函数

    2019-06-20 12:35:54
    哈希函数的输入域可以是非常大的范围,比如,一字符串,但是它的输出域是固定的范围。并具有以下性质: 典型的哈希函数都有无限的输入值域。 当给哈希函数传入相同的输入值时,返回值一样。 当给哈希函数传入...

    首先,先来介绍一下哈希函数的概念。哈希函数的输入域可以是非常大的范围,比如,一个字符串,但是它的输出域是固定的范围。并具有以下性质:

    1. 典型的哈希函数都有无限的输入值域。
    2. 当给哈希函数传入相同的输入值时,返回值一样。
    3. 当给哈希函数传入不同的输入值时,返回值可能一样,也可能不一样,这是当然的,因为输出域是固定的范围,所以会有不同的输入值对应在输出域的一个元素上,这就涉及到了哈希碰撞的问题。
    4. 最重要的性质是很多不同的输入值所得到的返回值会均匀的分布在输出域上。

    第1~3点性质是哈希函数的基础,第4点性质是评价一个哈希函数优劣的关键,不同输入值所得到的所有返回值越均匀分布与输入值出现的规律无关。

    哈希函数的构造方法

    1)直接定址法:

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

    注意:由于直接定址所得地址集合和关键字集合的大小相同。因此,对于不同的关键字不会发生冲突。但实际中能使用这种哈希函数的情况很少。

    2)相乘取整法:

    首先用关键字key乘上某个常数A(0 < A < 1),并抽取出key.A的小数部分;然后用m乘以该小数后取整。

    注意:该方法最大的优点是m的选取比除余法要求更低。比如,完全可选择它是2的整数次幂。虽然该方法对任何A的值都适用,但对某些值效果会更好。Knuth建议选取 0.61803……。

    3)平方取中法:

    取关键字平方后的中间几位为哈希地址。

    通过平方扩大差别,另外中间几位与乘数的每一位相关,由此产生的散列地址较为均匀。这是一种较常用的构造哈希函数的方法。

    将一组关键字(0100,0110,1010,1001,0111) 
    平方后得(0010000,0012100,1020100,1002001,0012321) 
    若取表长为1000,则可取中间的三位数作为散列地址集:(100,121,201,020,123)。
    

    4)除留余数法:

    取关键字被数p除后所得余数为哈希地址:H(key) = key MOD p (p ≤ m)。

    注意:这是一种最简单,也最常用的构造哈希函数的方法。它不仅可以对关键字直接取模(MOD),也可在折迭、平方取中等运算之后取模。值得注意的是,在使用除留余数法时,对p的选择很重要。一般情况下可以选p为质数或不包含小于20的质因素的合数。

    5)随机数法:

    选择一个随机函数,取关键字的随机函数值为它的哈希地址,即 H(key) = random (key),其中random为随机函数。通常,当关键字长度不等时采用此法构造哈希函数较恰当。

    哈希冲突解决方法

    1)开放定址法:

    就是在发生冲突后,通过某种探测技术,去依次探查其他单元,直到探查到不冲突为止,将元素添加进去。

    假如是在index的位置发生哈希冲突,那么通常有一下几种探测方式:

    • 线性探测法(线性探测再散列)
      向后依次探测index+1,index+2…位置,看是否冲突,直到不冲突为止,将元素添加进去。

    • 平方探测法
      不探测index的后一个位置,而是探测2^i 位置 ,比如探测2^0 位置上时发生冲突,接着探测2^1位置,依此类推,直至冲突解决。

    注意:

    (1)用开放定址法建立散列表时,建表前须将表中所有单元(更严格地说,是指单元中存储的关键字)
    置空。
    (2)两种探测方法的优缺点。
         线性探测法虽然在哈希表未满的情况下,总能保证找到不冲突的地址,但是容易发生二次哈希冲
         突的现象。比如在处理若干次次哈希冲突后k,k+1,k+2位置上的都存储了数据,那下一次存储地
         址在k,k+1,k+2,k+3位置的数据都将存在k+3位置上,这就产生了二次冲突。
         这里引入一个新的概念,堆积现象是指用线性探测法处理哈希冲突时,k,k+1,k+2位置已存有数
         据,下一个数据请求地址如果是k,k+1,k+2,k+3的话,那么这四个数据都会要求填入k+3的位置。
         
         平方探测法可以减少堆积现象的发生,但是前提是哈希表的总容量要是素数4n+3才可以。
    

    2)链地址法(开散列法)

    基本思想:

    链表法就是在发生冲突的地址处,挂一个单向链表,然后所有在该位置冲突的数据,都插入这个链表中。插入数据的方式有多种,可以从链表的尾部向头部依次插入数据,也可以从头部向尾部依次插入数据,也可以依据某种规则在链表的中间插入数据,总之保证链表中的数据的有序性。Java的HashMap类就是采取链表法的处理方案。

    例:已知一组关键字为(19,14,23,01,68,20,84,27,55,11,10,79),则按哈希函数 H(key) = key MOD13 和链地址法处理冲突构造所得的哈希表为:
    在这里插入图片描述

    3)再哈希法:(双散列法)

    在发生哈希冲突后,使用另外一个哈希算法产生一个新的地址,直到不发生冲突为止。这个应该很好理解。

    再哈希法可以有效的避免堆积现象,但是缺点是不能增加了计算时间和哈希算法的数量,而且不能保证在哈希表未满的情况下,总能找到不冲突的地址。

    4)建立一个公共溢出区:

    建立一个基本表,基本表的大小等于哈希表的大小。建立一个溢出表,所有哈希地址的第一个记录都存在基本表中,所有发生冲突的数据,不管哈希算法得到的地址是什么,都放入溢出表中。

    但是有一个缺点就是,必须事先知道哈希表的可能大小,而且溢出表里的数据不能太多,否则影响溢出表的查询效率。实际上就是要尽量减少冲突。

    参考链接:https://blog.csdn.net/m0_37925202/article/details/82015731

    展开全文
  • 哈希原理与常见哈希函数

    千次阅读 2020-01-09 18:11:06
    (1)一致性:同一值每次经过同一个哈希函数计算后得到的哈希值是一致的。 F(x)=rand() :每次返回一随机值,是不好的哈希 (2)散列性:不同的值的哈希值尽量不同,理想情况下每值对应于不同的数字。 F(x)...

    一,什么是哈希

    哈希是将任意长度的数据转换为一个数字的过程。这个数字是在一个固定的范围之内的。
    转换的方法称为哈希函数,原值经过哈希函数计算后得到的值称为哈希值。

    哈希函数

    1.哈希特点

    (1)一致性:同一个值每次经过同一个哈希函数计算后得到的哈希值是一致的。

    F(x)=rand() :每次返回一个随机值,是不好的哈希
    

    (2)散列性:不同的值的哈希值尽量不同,理想情况下每个值对应于不同的数字。

    F(x)=1 : 不管输入什么都返回1,是不好的哈希
    
    2.冲突怎么解决

    把一个大的集合映射到一个固定大小的集合中,肯定是存在冲突的。这个是抽屉原理或者叫鸽巢理论。

    桌上有十个苹果,要把这十个苹果放到九个抽屉里,无论怎样放,我们会发现至少会有一个抽屉里面放不少于两个苹果。这一现象就是我们所说的“抽屉原理”。 抽屉原理的一般含义为:“如果每个抽屉代表一个集合,每一个苹果就可以代表一个元素,假如有n+1个元素放到n个集合中去,其中必定有一个集合里至少有两个元素。” 抽屉原理有时也被称为鸽巢原理。它是组合数学中一个重要的原理。

    (1)拉链法:

    链表地址法是使用一个链表数组来存储相应数据,当hash遇到冲突的时候依次添加到链表的后面进行处理。Java里的HashMap是拉链法解决冲突的典型应用场景。

    Java8 HashMap

    Java8的HashMap中,使用一个链表数组来存储数据,根据元素的哈希值确定存储的数组索引位置,当冲突时,就链接到元素后面形成一个链表,Java8中当链表长度超过8的时候就变成红黑树以优化性能,红黑树也可以视为拉链法的一种变形。

    (2)开放地址法

    开放地址法是指大小为 M 的数组保存 N 个键值对,其中 M >N。我们需要依靠数组中的空位解决碰撞冲突。基于这种策略的所有方法被统称为“开放地址”哈希表。

    线性探测法,就是比较常用的一种“开放地址”哈希表的一种实现方式。线性探测法的核心思想是当冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。简单来说就是:一旦发生冲突,就去寻找下 一个空的散列表地址,只要散列表足够大,空的散列地址总能找到。

    Java8中的HashTable就是用线性探测法来解决冲突的。

        public synchronized V put(K key, V value) {
            // Make sure the value is not null
            if (value == null) {
                throw new NullPointerException();
            }
    
            // Makes sure the key is not already in the hashtable.
            Entry<?,?> tab[] = table;
            int hash = key.hashCode();
            int index = (hash & 0x7FFFFFFF) % tab.length;
            @SuppressWarnings("unchecked")
            Entry<K,V> entry = (Entry<K,V>)tab[index];
            for(; entry != null ; entry = entry.next) {
                if ((entry.hash == hash) && entry.key.equals(key)) {
                    V old = entry.value;
                    entry.value = value;
                    return old;
                }
            }
    
            addEntry(hash, key, value, index);
            return null;
        }
    
        private void addEntry(int hash, K key, V value, int index) {
            modCount++;
    
            Entry<?,?> tab[] = table;
            if (count >= threshold) {
                // Rehash the table if the threshold is exceeded
                rehash();
    
                tab = table;
                hash = key.hashCode();
                index = (hash & 0x7FFFFFFF) % tab.length;
            }
    
            // Creates the new entry.
            @SuppressWarnings("unchecked")
            Entry<K,V> e = (Entry<K,V>) tab[index];
            tab[index] = new Entry<>(hash, key, value, e);
            count++;
        }
    
    

    (2)冲突解决示例

    举个例子,假如散列长度为8,哈希函数是:y=x%7。两种解决冲突的方式如下:

    拉链法解决冲突
    拉链法

    线性探测法解决冲突
    线性探测法

    二,几个常见哈希算法

    1.MD5

    MD5哈希算法是将任意字符散列到一个长度为128位的Bit数组中,得出的结果表示为一个32位的十六进制数字。

    MD5哈希算法有以下几个特点:

    1. 正像快速:原始数据可以快速计算出哈希值
    2. 逆向困难:通过哈希值基本不可能推导出原始数据
    3. 输入敏感:原始数据只要有一点变动,得到的哈希值差别很大
    4. 冲突避免:很难找到不同的原始数据得到相同的哈希值

    算法过程:

    1. 数据填充:

    将原数据的二进制值进行补齐。

    (1)填充数据:使得长度模除512后得到448,留出64个bit来存储原信息的长度。填充规则是填充一个1,后面全部是0。

    (2)填充长度数据:计算原数据的长度数据,填充到最后的64个bit上,如果消息长度数据大于64bit就使用低64位的数据。

    第一步:填充数据

    1. 迭代计算:

    将填充好的数据按照每份512的长度进行切分,对每一份依次进行处理,每份的处理方式是使用四个函数进行依次进行计算,每个函数都有四个输入参数,输出也是四个数字,输出的数字作为下一份数据的输入,所有份数的数据处理完毕,得到的四个数字连接起来就是最终的MD5值。

    以下图片是整个迭代计算的过程示意图,其中四个初始参数和四个函数定义如下:

    //四个初始参数值
    A=0x67452301;
    B=0xefcdab89;
    C=0x98badcfe;
    D=0x10325476;
    
    //四个函数的定义
    // a、b、c、d是每次计算时候的四个参数
    F=(b&c)|((~b)&d);
    F=(d&b)|((~d)&c);
    F=b^c^d;
    F=c^(b|(~d));
    

    第二步:数据计算

    1. md5的java实现
    package com.chybin.algorithm.chapter2;
    
    /**
     * Create By 鸣宇淳 on 2019/12/26
     **/
    public class MD5{
        /*
         *四个链接变量
         */
        private final int A=0x67452301;
        private final int B=0xefcdab89;
        private final int C=0x98badcfe;
        private final int D=0x10325476;
        /*
         *ABCD的临时变量
         */
        private int Atemp,Btemp,Ctemp,Dtemp;
    
        /*
         *常量ti
         *公式:floor(abs(sin(i+1))×(2pow32)
         */
        private final int K[]={
                0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee,
                0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501,0x698098d8,
                0x8b44f7af,0xffff5bb1,0x895cd7be,0x6b901122,0xfd987193,
                0xa679438e,0x49b40821,0xf61e2562,0xc040b340,0x265e5a51,
                0xe9b6c7aa,0xd62f105d,0x02441453,0xd8a1e681,0xe7d3fbc8,
                0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed,0xa9e3e905,
                0xfcefa3f8,0x676f02d9,0x8d2a4c8a,0xfffa3942,0x8771f681,
                0x6d9d6122,0xfde5380c,0xa4beea44,0x4bdecfa9,0xf6bb4b60,
                0xbebfbc70,0x289b7ec6,0xeaa127fa,0xd4ef3085,0x04881d05,
                0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665,0xf4292244,
                0x432aff97,0xab9423a7,0xfc93a039,0x655b59c3,0x8f0ccc92,
                0xffeff47d,0x85845dd1,0x6fa87e4f,0xfe2ce6e0,0xa3014314,
                0x4e0811a1,0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391};
        /*
         *向左位移数,计算方法未知
         */
        private final int s[]={7,12,17,22,7,12,17,22,7,12,17,22,7,
                12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,
                4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,
                15,21,6,10,15,21,6,10,15,21,6,10,15,21};
    
    
        /*
         *初始化函数
         */
        private void init(){
            Atemp=A;
            Btemp=B;
            Ctemp=C;
            Dtemp=D;
        }
        /*
         *移动一定位数
         */
        private    int    shift(int a,int s){
            return(a<<s)|(a>>>(32-s));//右移的时候,高位一定要补零,而不是补充符号位
        }
        /*
         *主循环
         */
        private void MainLoop(int M[]){
            int F,g;
            int a=Atemp;
            int b=Btemp;
            int c=Ctemp;
            int d=Dtemp;
            for(int i = 0; i < 64; i ++){
                if(i<16){
                    F=(b&c)|((~b)&d);
                    g=i;
                }else if(i<32){
                    F=(d&b)|((~d)&c);
                    g=(5*i+1)%16;
                }else if(i<48){
                    F=b^c^d;
                    g=(3*i+5)%16;
                }else{
                    F=c^(b|(~d));
                    g=(7*i)%16;
                }
                int tmp=d;
                d=c;
                c=b;
                b=b+shift(a+F+K[i]+M[g],s[i]);
                a=tmp;
            }
            Atemp=a+Atemp;
            Btemp=b+Btemp;
            Ctemp=c+Ctemp;
            Dtemp=d+Dtemp;
    
        }
        /*
         *填充函数
         *处理后应满足bits≡448(mod512),字节就是bytes≡56(mode64)
         *填充方式为先加一个0,其它位补零
         *最后加上64位的原来长度
         */
        private int[] add(String str){
            int num=((str.length()+8)/64)+1;//以512位,64个字节为一组
            int strByte[]=new int[num*16];//64/4=16,所以有16个整数
            for(int i=0;i<num*16;i++){//全部初始化0
                strByte[i]=0;
            }
            int    i;
            for(i=0;i<str.length();i++){
                strByte[i>>2]|=str.charAt(i)<<((i%4)*8);//一个整数存储四个字节,小端序
            }
            strByte[i>>2]|=0x80<<((i%4)*8);//尾部添加1
            /*
             *添加原长度,长度指位的长度,所以要乘8,然后是小端序,所以放在倒数第二个,这里长度只用了32位
             */
            strByte[num*16-2]=str.length()*8;
            return strByte;
        }
        /*
         *调用函数
         */
        public String getMD5(String source){
            init();
            int strByte[]=add(source);
            for(int i=0;i<strByte.length/16;i++){
                int num[]=new int[16];
                for(int j=0;j<16;j++){
                    num[j]=strByte[i*16+j];
                }
                MainLoop(num);
            }
            return changeHex(Atemp)+changeHex(Btemp)+changeHex(Ctemp)+changeHex(Dtemp);
    
        }
        /*
         *整数变成16进制字符串
         */
        private String changeHex(int a){
            String str="";
            for(int i=0;i<4;i++){
                str+=String.format("%2s", Integer.toHexString(((a>>i*8)%(1<<8))&0xff)).replace(' ', '0');
    
            }
            return str;
        }
        /*
         *单例
         */
        private static MD5 instance;
        public static MD5 getInstance(){
            if(instance==null){
                instance=new MD5();
            }
            return instance;
        }
    
        private MD5(){};
    
        public static void main(String[] args){
            String str=MD5.getInstance().getMD5("123");
            System.out.println(str);
        }
    }
    
    2.SHA

    SHA类似MD5,也是一种信息摘要算法,也是将任意长度的字符串转换为固定长度的数字的算法。SHA算法是一个家族,有五个算法:SHA-1、SHA-224、SHA-256、SHA-384,和SHA-512。这些变体除了生成摘要的长度、循环运行的次数等一些微小差异外,算法的基本结构是一致的。

    SHA-1算法的结果是一个160个bit的数字,比MD5的128个bit要长32位,碰撞几率要低了2^32倍。可是SHA-1和MD5一样已经被人破解,已经不安全了。

    SHA-256从名字上看就表明了它的值存储在长度为256的bit数组中的,SHA-512信息摘要长度是512个bit。

    SHA-224是SHA256的精简版本,SHA-384是SHA-512的精简版本,精简版本主要用在安全等级要求不太高的场景,比如只是验证下文件的完整性。使用什么版本的SHA取决于安全要求和算法速度,毕竟长度越长算法计算时间约长,但是安全等级高。

    在这里插入图片描述

    SHA算法过程:

    SHA算法的底层原理和MD5很相似,只是在摘要分段和处理细节上有少许差别,他们都是第一步将原数据进行填充,填充到512的整数倍,填充的信息包括10数据填充和长度填充,第二步切分为相同大小的块,第三步进行对每一块迭代,每块进行N轮运算,最终得到的值拼接起来就是最终的哈希值。

    以下是MD5、SHA-1、SHA-2系列的算法过程比较:

    MD5算法过程示意图:

    MD5是对每一块数据分为四个部分,用四个函数进行运算。最终生成128位的哈希值。

    MD5算法过程

    SHA-1算法过程示意图:

    SHA-1是将每一块数据分为五个部分。

    SHA-1算法过程

    SHA-2算法过程示意图:

    SHA-2是分为八个部分,算法也更加复杂。

    SHA-2算法过程

    3.SimHash

    SimHash是Google提出的一种判断文档是否重复的哈希算法,他是将文本转换为一个64位的哈希值,然后计算两个哈希值的距离,如果小于n(n一般是3)就认为这两个文本是相似的。

    之所以能够这样判断是否相似是因为SimHash算法不同于MD5之类的算法,SimHash算法是局部敏感的哈希算法,MD5算法是全局敏感的哈希算法。在MD5中原数据只要有一个字符的变化,哈希值就会变化很大,而在SimHash算法中,原数据变化一小部分,哈希值也只有很小一部分的变化,所以只要哈希值很类似,就意味着原数据就很类似。

    算法实现:

    参考这个博客【[Algorithm] 使用SimHash进行海量文本去重】

    (1)第一步:哈希

    1. 分词: 将文本进行分词,并给单词分配权重。
    2. hash: 对每个次进行hash计算,得到哈希值。
    3. 加权: 对每个单词的has进行加权。
    4. 合并: 把上一步加权hash值合并累计起来。
    5. 降维: 把上一步累加起来的值变为01。如果每一位大于0 记为 1,小于0 记为 0。

    (2)第二步:计算海明距离

    两个simhash对应二进制(01串)取值不同的数量称为这两个simhash的海明距离。

    举例如下: 10101 和 00110 从第一位开始依次有第一位、第四、第五位不同,则海明距离为3。

    异或就是如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。两个simhash值进行异或,得出的结果中1的个数就是海明距离。

    simhash计算过程

    判断两个文本是否相似,就计算两个simhash哈希值的海明距离,根据经验,如果海明距离小于3就可以判定两个文本是相似的。

    4.GeoHash

    GeoHash 算法将经纬度哈希为一个数字,然后将数字base32编码为一个字符串。

    比如:北海公园的经纬度是:(39.928167,116.389550),对应的GeoHash值可以为wx4g、wx4g0、wx4g0s、wx4g0s8、wx4g0s8q。GeoHash值代表的是这个经纬度点所在的一个矩形区域,长度越长矩形面积约小,表示的越精确。

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

    在这里插入图片描述

    在这里插入图片描述

    两个位置的GeoHash值前部分一样的位数越多,说明两个位置离得越近,百度地图的查找附近、滴滴打车查找附近的车辆功能就可以使用这个算法。

    GeoHash算法过程

    下面对于北海公园的经纬度(39.928167,116.389550)进行编码,了解下算法过程。

    (1)第一步:纬度编码

    将整个地球从水平方向上进行逐步切分,确定纬度39.928167在哪个区域中。

    纬度范围是-90到90,每次平均分为两份,进行逐步细化地迭代。

    1. 第一次迭代:处于-90到0的标记为0,0到90的标记为1,39.928167处于1的区间,所以最终结果的第一位是1。
    2. 第二次迭代:对上一步标记为1的部分平分,0到45标记为0,45到90标记为1,39.928167标记为1处于0的区间,所以最终结果的第二位是0。
    3. 第三次迭代:对上一步标记为0的部分平分,0到22.5标记为0,22.5到45标记为1,39.928167标记为1处于0的区间,所以最终结果的第三位是0
    4. 第四次迭代:对上一步标记为0的部分平分,22.5到33.75标记为0,33.75到45标记为1,39.928167标记为1处于1的区间,所以最终结果的第三位是1。

    经过N次迭代后,得到一个长度为N的二进制值,比如得到的值为1011100011,这个就是对纬度进行的编码最终值。

    纬度编码示意图

    (2)第二步:经度编码

    对经度的编码过程跟对纬度编码过程十分类似,不同点是经度范围是-180到180,对经度116.389550经过N次迭代后得到编码值。比如得到1101001011。这个就是对经度编码的最终值。

    (3)第三步:合并经纬度

    对纬度编码值、经度编码值进行合并,合并规则是奇数位放纬度、偶数位放经度,合并为一个新的二进制串。

    (4)第四步:转换为字符串

    将上一步合并的二进制11100 11101 00100 01111每5位一段转换为十进制,结果是28、29、4、15,Base32编码后为wx4g。这个就是北海公园的经纬度(39.928167,116.389550)最终的GeoHash编码值。

    以下图表是二进制数字、base32字符对应表:

    Decimal0123456789101112131415
    Base320123456789bcdef
    Decimal16171819202122232425262728293031
    Base32hjkmnpqrstuvwxy
    展开全文
  • 构造哈希函数

    2018-12-27 14:26:25
    设计哈希函数;分别采用线性探测再散列法和链地址法解决冲突 1.线性探测再散列:建立一一维数组,需要计算数组的容量。如果是对12数建立哈希表,则表长通过填满因子,计算为15。线性解决冲突的方法是通过哈希...
  • 密码学哈希函数

    千次阅读 2017-08-22 21:52:24
    哈希函数是一个数学函数,其具有以下三个特性: 输入可以为任意大小的字符串;其产生固定大小的输出;对于特定的输入字符串,能在合理时间计算出结果。对应n位的字符串,其哈希值计算的复杂度为O(n)。 要使...
  • 哈希函数&哈希表

    2018-08-08 23:47:31
    1、哈希函数:传入一字符串返回一哈希码 数字0~9,字母a~f,长度为16或者32; 这就是哈希函数,mD5哈希。16^16范围。 哈希函数又叫散列函数, 性质:1.输入域是无限的;2.输出域是有限的;3.当你输入参数是...
  • Hash(哈希)相关知识(哈希函数、哈希查找)

    万次阅读 多人点赞 2020-05-18 21:04:17
    函数特性1.1 基本的哈希函数1.2 加密的哈希函数2. 常见的哈希函数构造法2.1 直接寻址法2.2 数字分析法2.3 平方取中法2.4 折叠法2.5 随机数法2.6 除留余数法2.7 加密哈希函数3. 哈希函数总结二. 哈希查找1. 操作步骤...
  • 哈希函数 :又名散列函数。       布隆过滤器:1经典结构 要求的失误率 ...2 原理:每个url经过K个哈希函数在对应相应位置描黑,所有...3 三个指标的确定  开辟空间: m=-n*lnp/(ln2)^2 p代表着期望的失误率...
  • 数据结构之哈希函数

    千次阅读 2016-12-07 10:43:43
    哈希表(hashTable)哈希表之前讲过,有需要的可以参考:点击打开哈希表哈希函数哈希函数就是将某一不定长的对象映射为另一定长的对象。能够做到这一点的函数有很多,那什么可以作为哈希函数?这里我们首先要明确...
  • 哈希函数和哈希表

    千次阅读 2019-05-08 16:31:12
    经典哈希函数的性质(散列函数) 1.输入域无穷大 2.输出域是有穷尽的 3.输入参数相同,返回哈希值不变(不是随机函数) 4.输入不同,哈希值亦可能相同,哈希碰撞 5.离散性:举例:input-0~98,out-0、1、2,...
  • 常用哈希函数介绍

    千次阅读 2021-03-31 16:21:32
    哈希函数介绍 什么是哈希?在记录的关键字与记录的存储地址之间建立的一种对应关系叫哈希函数哈希函数就是一种映射,是从关键字到存储地址的映射。 通常,包含哈希函数的算法的算法复杂度都假设为O(1),这就是为...
  • 什么是哈希函数

    千次阅读 2020-09-25 11:45:24
    Hash函数译为哈希函数,又称散列函数。是把任意长度的输入,通过散列算法,变换成固定...出于安全性考虑,对哈希函数三个安全性假设: 对任意给定的散列值h,找到满足H(x)=h的x在计算上是不可行的,称之为单向性...
  • 哈希函数的生成方法

    千次阅读 2017-07-30 21:22:49
    本文阐述了哈希函数的构造方法有很多,但应注意两原则:第一,函数值应在1至记录总数之间;第二,尽可能避免冲突。 设要存放的数据元素有n,存放数据元素的内存单元有m,设计哈希函数的目标就是要使通过哈希...
  • 哈希表与哈希函数 C实现

    千次阅读 2015-08-25 16:03:05
    用C实现哈希函数,并实现其插入与显示方法
  • 重温数据结构:哈希 哈希函数 哈希表

    万次阅读 多人点赞 2016-10-27 00:49:30
    在学习 HashMap 前,我们先来温习下 Hash(哈希) 的概念。 什么是 HashHash(哈希),又称“散列”。散列(hash)英文原意是“混杂”、“拼凑”、“重新表述”的意思。在某种程度上,散列是与排序相反的一种操作,...
  • 哈希函数的一些知识

    2020-12-10 06:44:46
    哈希函数是一个数学函数,其具有以下三个特性: 输入可以为任意大小的字符串; 其产生固定大小的输出; 对于特定的输入字符串,能在合理时间计算出结果。对应n位的字符串,其哈希值计算的复杂度为O(n)。 要使...
  • 哈希函数 哈希表

    千次阅读 2017-01-04 14:25:13
    在学习 HashMap 前,我们先来温习下 Hash(哈希) 的概念。 什么是 HashHash(哈希),又称“散列”。散列(hash)英文原意是“混杂”、“拼凑”、“重新表述”的意思。在某种程度上,散列是与排序相反的一种操作,...
  • 哈希函数的构造方法

    2015-03-26 16:30:43
    设要存放的数据元素有n,存放数据元素的内存单元有m,设计哈希函数的目标就是要使通过哈希函数得到的n数据元素的哈希地址尽可能均匀地分布在m连续内存单元上,同时使计算过程尽可能简单以达到尽可能高的时间...
  • 最小完美哈希函数

    2012-06-28 13:43:40
    哈希函数 任意函数h(x)都可以说哈希函数,一般来说,一良好的哈希函数可以尽量避免重复。x的集合是参数域,h(x)的集合是值域。 完美哈希函数 完美哈希函数,就是完全不会冲突的哈希函数,这要求函数
  • 哈希表与哈希函数

    2010-12-31 11:41:00
    哈希查找因使用哈希 (Hash) 函数而得名,哈希函数又叫散列函数,它是一种能把关键字映射成记录存贮地址的函数。 一.哈希表 ①它是一种能把关键字映射成记录存贮地址的函数。 ②假定数组 HT[0 ~ m-1] 为存贮...
  • 取余与哈希函数

    2018-12-19 15:06:16
    例:(可以通过与2取余来区分奇数,偶数)求余过程就是个哈希函数。 哈希(散列)函数:将任意长度的输入,通过哈希算法,压缩为某一固定长度的输出。 f(x) = x(等待被转换的值) mod(取余操作) size(有限存储...
  • 第18篇 哈希函数

    千次阅读 2020-07-06 08:18:57
    本文试图解释哈希函数的作用、标准、实现方式以及区块链哪些地方用到了它。 本文中的哈希和hash是同一词意,有可能会交叉出现。 本文中的哈希有可能是名词(哈希函数、哈希算法),也有可能是动词(把这段数据...
  • 可以明显的看到,原始数据经过哈希函数的映射后称为了一个个的哈希编码,数据得到压缩。哈希函数是实现哈希表和布隆过滤器基础。 2. 判定一元素是否存在于集合中 如何查看一东西是否在有大量数据的池子里面。 ...
  • 哈希表(散列表)是根据关键码来映射到值的一数据结构,这映射函数叫哈希函数(散列函数)。 哈希表中元素是由哈希函数确定的。将数据元素的关键字K作为自变量,通过一定的函数关系(称为哈希函数),计算出的值...
  • 1.1 密码学哈希函数

    2018-09-12 15:13:01
    我们需要理解的第一个密码学的基础知识是密码学哈希函数哈希函数是一个数学函数,具有以下三个特性:● 其输入可为任意大小的字符串。● 它产生固定大小的输出。为使本章讨论更具体,我们假设输出值大小为256位,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 144,257
精华内容 57,702
关键字:

哈希函数的三个特征