常见的hash函数 :-D 非常经典!!

常见Hash算法解析
本文介绍了哈希函数的基本原理,探讨了哈希方法学、素数在哈希中的应用及位偏向问题。此外还列举并解释了八种常用的哈希算法,包括RS、JS、PJW等。
 

常见的Hash算法

  1999人阅读  评论(6)  收藏  举报

简介

哈稀函数按照定义可以实现一个伪随机数生成器(PRNG),从这个角度可以得到一个公认的结论:哈希函数之间性能的比较可以通过比较其在伪随机生成方面的比较来衡量。
一些常用的分析技术,例如 泊松分布可用于分析不同的哈希函数对不同的数据的碰撞率(collision rate)。一般来说,对任意一类的数据存在一个理论上完美的哈希函数这个完美的哈希函数定义是没有发生任何碰撞,这意味着没有出现重复的散列在现实中很难找到一个完美的哈希散列函数,而且这种完美函数的趋近变种在实际应用中的作用是相当有限的。在实践中人们普遍认识到,一个完美哈希函数的哈希函数,就是在一个特定的数据集上产生的的碰撞最少哈希的函数。
现在的问题是有各种类型的数据,有一些是高度随机的,有一些有包含高纬度的图形结构,这些都使得找到一个通用的哈希函数变得十分困难,即使是某一特定类型的数据,找到一个比较好的哈希函数也不是意见容易的事。我们所能做的就是通过试错方法来找到满足我们要求的哈希函数。可以从下面两个角度来选择哈希函数:
1.数据分布
 一个衡量的措施是考虑一个哈希函数是否能将一组数据的哈希值进行很好的分布。要进行这种分析,需要知道碰撞的哈希值的个数,如果用链表来处理碰撞,则可以分析链表的平均长度,也可以分析散列值的分组数目。
2.哈希函数的效率
另个一个衡量的标准是哈希函数得到哈希值的效率。通常,包含哈希函数的算法的算法复杂度都假设为O(1),这就是为什么在哈希表中搜索数据的时间复杂度会被认为是"平均为O(1)的复杂度",而在另外一些常用的数据结构,比如图(通常被实现为红黑树),则被认为是O(logn)的复杂度。
一个好的哈希函数必修在理论上非常的快、稳定并且是可确定的。通常哈希函数不可能达到O(1)的复杂度,但是哈希函数在字符串哈希的线性的搜索中确实是非常快的,并且通常哈希函数的对象是较小的主键标识符,这样整个过程应该是非常快的,并且在某种程度上是稳定的。
在这篇文章中介绍的哈希函数被称为简单的哈希函数。它们通常用于散列(哈希字符串)数据它们被用来产生一种在诸如哈希表的关联容器使用的key这些哈希函数不是密码安全的很容易通过颠倒和组合不同数据的方式产生完全相同的哈希值。


哈希方法学

哈希函数通常是由他们产生哈希值的方法来定义的,有两种主要的方法:
1.基于加法和乘法的散列
这种方式是通过遍历数据中的元素然后每次对某个初始值进行加操作,其中加的值和这个数据的一个元素相关。通常这对某个元素值的计算要乘以一个素数。

基于加法和乘法的散列

 

2.基于移位的散列

和加法散列类似,基于移位的散列也要利用字符串数据中的每个元素,但是和加法不同的是,后者更多的而是进行位的移位操作。通常是结合了左移和右移,移的位数的也是一个素数。每个移位过程的结果只是增加了一些积累计算,最后移位的结果作为最终结果。


基于移位的散列

 

 


哈希函数和素数

没有人可以证明素数和伪随机数生成器之间的关系,但是目前来说最好的结果使用了素数。伪随机数生成器现在是一个统计学上的东西,不是一个确定的实体,所以对其的分析只能对整个的结果有一些认识,而不能知道这些结果是怎么产生的。如果能进行更具体的研究,也许我们能更好的理解哪些数值比较有效,为什么素数比其他数更有效,为什么有些素数就不行,如果能用可再现的证明来回答这些问题,那么我们就能设计出更好的伪随机数生成器,也可能得到更好的哈希函数。

围绕着哈希函数中的素数的使用的基本的概念是,利用一个素质来改变处理的哈希函数的状态值,而不是使用其他类型的数。处理这个词的意思就是对哈希值进行一些简单的操作,比如乘法和加法。这样得到的一个新的哈希值一定要在统计学上具有更高的熵,也就是说不能有为偏向。简单的说,当你用一个素数去乘一堆随机数的时候,得到的数在bit这个层次上是1的概率应该接近0.5。没有具体的证明这种不便向的现象只出现在使用素数的情况下,这看上去只是一个自我宣称的直觉上的理论,并被一些业内人士所遵循。

决定什么是正确的,甚至更好的方法和散列素数的使用最好的组合仍然是一个很有黑色艺术没有单一的方法可以宣称自己是最终的通用散列函数。最好的所能做的就是通过试错演进和获得适当的散列算法,以满足其需要的统计分析方法。



位偏向

位序列发生器是纯粹随机的或者说在某种程度上确定性的,可以按照一定的概率产生某种状态或相反状态的比特,这个概率就是位偏向。在纯粹随机的情况下,产生高位或者低位的位偏向应该是50%。

然后在伪随机产生器中,算法将决定在产生器在最小输出模块的位偏向。


位偏向

 

假设一个PRNG的产生8作为其输出块。出于某种原因,MSB始终是设置为高,MSB的位偏向将是100%的概率置高这一结论是,即使有256个PRNG的产生可能的值,小于128将永远不会产生。简单起见,假设其他位正在生成纯粹是随机的,那么平等的机会,128和255之间的任何值将产生,但是在同一时间,有0%的机会,一个小于128的值会产生

所有PRNGs,无论是杂凑函数,密码,msequences或其他任何产生比特流的产生器都会有这样一个位偏向大多数PRNGs他们将尝试收敛位偏向到一个确定值流密码就是一个例子,而其他产生器在不确定的位偏向下效果更好。

混合位序列加扰是一种产生在一个共同的平等流位偏向的方法。虽然我们必须要小心,以确保他们不会混合至发散偏向。密码学中的一个混合使用的形式被称为雪崩,这就是一个位块使用另一个块来替换或置换混合在一起,而另一块产生与其他快混合的输出。

正如下图中显示的,雪崩过程始于一个或多个二进制数据块。对数据中的某些位操作通常是一些输入敏感位减少位逻辑)生产第i数据。然后重复这个过程是第i数据,以生成一个i+1层数据,是当前层的位数小于或等于的位数

这一反复的过程导致一个依靠之前数据所有位的位应该指出的是,下图是一个单纯概括,雪崩过程不一定这一进程的唯一形式。

雪崩过程

 

 


 

各种形式的哈希

 

哈希是一个在现实世界中将数据映射到一个标识符的工具,下面是哈希函数的一些常用领域:

1.字符串哈希

在数据存储领域,主要是数据的索引和对容器的结构化支持,比如哈希表。

2.加密哈希

用于数据/用户核查和验证。一个强大的加密哈希函数很难从结果再得到原始数据加密哈希函数用于哈希用户的密码,用来代替密码本身存在某个服务器撒很难过加密哈希函数也被视为不可逆的压缩功能,能够代表一个信号标识大量数据,可以非常有用的判断当前的数据是否已经被篡改(比如MD5),也可以作为一个数据标志使用,以证明通过其他手段加密文件的真实性

3.几何哈希

这个哈希表用于计算机视觉领域,为任意场景分类物体的探测。

最初选择过程涉及一个地区或感兴趣的对象。从那里使用,如Harris角检测器(HCD的),尺度不变特征变换(SIFT)或速成的强大功能(冲浪组功能仿射提取被视为代表仿射不变特征检测算法表示对象或地区。这一套有时被称为宏观功能或功能的星座。发现的功能的性质类型的对象或地区被列为它可能仍然是可能的匹配两个星座的特点,即使可能有轻微的差异如丢失或异常特征)两集。星座,然后说是功能分类设置。
哈希值是计算星座的特性这通常是由最初定义一个地方的哈希是为了居住空间中完成- 在这种情况下,散列值一个多层面的价值,定义的空间正常化。再加上计算的哈希值另一个进程,决定了两个哈希值之间的距离是必要的过程-一个距离测量是必需的,而不是一个确定性的平等经营者由于星座哈希值计算可能的差距问题也因为简单欧氏距离度量本质上是无效的,其结果是自动确定特定空间的距离度量已成为学术界研究的活跃领域处理这类空间的非线性性质。
几何散列包括各种汽车分类的重新检测任意场景的目的,典型的例子。检测水平可以多种多样,从检测是否是车辆,到特定型号车辆,在特定的某个车辆。
4.布隆过滤器

布隆过滤器允许一个非常大范围内的值被一个小很多的内存锁代表在计算机科学,这是众所周知的关联查询,并在关联容器的核心理念。
Bloom Filter的实现通过多种不同的hash函数使用,也可通过允许一个特定值的存在一定的误差概率会员查询结果的。布隆过滤器保证提供的是,对于任何会员国的查询就永远不会再假阴性,但有可能是假阳性。假阳性的概率可以通过改变控制布隆过滤器,并通过不同的hash函数的数量所使用的表的大小。
随后的研究工作集中在散列函数和哈希表以及Mitzenmacher的布隆过滤器领域。建议对这种结构,在数据散列最实用的用法有助于哈希函数熵,理论成果缔结一项最佳的布隆过滤器(一个提供给定一个最低的进一步导致假阳性的可能性表的大小反之亦然)提供假阳性的概率定义用户可以建造最多也作为两种截然不同的两两独立的哈希散列函数已知功能,大大提高了查询效率的成员
布隆过滤器通常存在于诸如拼写检查器,字符串匹配算法,网络数据包分析工具和网络/ Internet缓存的应用程序。

常用的哈希函数

通用的哈希函数库有下面这些混合了加法和一位操作的字符串哈希算法。下面的这些算法在用法和功能方面各有不同,但是都可以作为学习哈希算法的实现的例子。(其他版本代码实现见下载

1.RS 
从Robert  Sedgwicks的  Algorithms in C一书中得到了。 我(原文作者)已经 添加了一些 简单的优化 算法,以 加快 散列 过程。
[java]  view plain copy print ?
  1. public long RSHash(String str)  
  2.    {  
  3.       int b     = 378551;  
  4.       int a     = 63689;  
  5.       long hash = 0;  
  6.       for(int i = 0; i < str.length(); i++)  
  7.       {  
  8.          hash = hash * a + str.charAt(i);  
  9.          a    = a * b;  
  10.       }  
  11.       return hash;  
  12.    }  
 
2.JS
Justin Sobel写的一个位操作的哈希函数。
[c-sharp]  view plain copy print ?
  1. public long JSHash(String str)  
  2.    {  
  3.       long hash = 1315423911;  
  4.       for(int i = 0; i < str.length(); i++)  
  5.       {  
  6.          hash ^= ((hash << 5) + str.charAt(i) + (hash >> 2));  
  7.       }  
  8.       return hash;  
  9.    }  
 
3.PJW 
该散列 算法是基于贝尔实验室的 彼得J 温伯格 的的研究 。在Compilers一书中 (原则, 技术 和工具) 建议 采用这个算法的 散列 函数 的哈希 方法。
[java]  view plain copy print ?
  1. public long PJWHash(String str)  
  2.    {  
  3.       long BitsInUnsignedInt = (long)(4 * 8);  
  4.       long ThreeQuarters     = (long)((BitsInUnsignedInt  * 3) / 4);  
  5.       long OneEighth         = (long)(BitsInUnsignedInt / 8);  
  6.       long HighBits          = (long)(0xFFFFFFFF) << (BitsInUnsignedInt - OneEighth);  
  7.       long hash              = 0;  
  8.       long test              = 0;  
  9.       for(int i = 0; i < str.length(); i++)  
  10.       {  
  11.          hash = (hash << OneEighth) + str.charAt(i);  
  12.          if((test = hash & HighBits)  != 0)  
  13.          {  
  14.             hash = (( hash ^ (test >> ThreeQuarters)) & (~HighBits));  
  15.          }  
  16.       }  
  17.       return hash;  
  18.    }  
 
4.ELF 
和PJW很相似,在Unix系统中使用的较多。
[java]  view plain copy print ?
  1. public long ELFHash(String str)  
  2.    {  
  3.       long hash = 0;  
  4.       long x    = 0;  
  5.       for(int i = 0; i < str.length(); i++)  
  6.       {  
  7.          hash = (hash << 4) + str.charAt(i);  
  8.          if((x = hash & 0xF0000000L) != 0)  
  9.          {  
  10.             hash ^= (x >> 24);  
  11.          }  
  12.          hash &= ~x;  
  13.       }  
  14.       return hash;  
  15.    }  
 
5.BKDR
这个算法来自Brian Kernighan 和 Dennis Ritchie的 The C Programming Language。这是一个很简单的哈希算法,使用了一系列奇怪的数字,形式如31,3131,31...31,看上去和DJB算法很相似。(参照我之前一篇博客,这个就是Java的字符串哈希函数)
[java]  view plain copy print ?
  1. public long BKDRHash(String str)  
  2.    {  
  3.       long seed = 131// 31 131 1313 13131 131313 etc..  
  4.       long hash = 0;  
  5.       for(int i = 0; i < str.length(); i++)  
  6.       {  
  7.          hash = (hash * seed) + str.charAt(i);  
  8.       }  
  9.       return hash;  
  10.    }  
 
6.SDBM
这个算法在开源的SDBM中使用,似乎对很多不同类型的数据都能得到不错的分布。
[java]  view plain copy print ?
  1. public long SDBMHash(String str)  
  2.    {  
  3.       long hash = 0;  
  4.       for(int i = 0; i < str.length(); i++)  
  5.       {  
  6.          hash = str.charAt(i) + (hash << 6) + (hash << 16) - hash;  
  7.       }  
  8.       return hash;  
  9.    }  
 
7.DJB
这个算法是Daniel J.Bernstein 教授发明的,是目前公布的最有效的哈希函数。
[java]  view plain copy print ?
  1. public long DJBHash(String str)  
  2.    {  
  3.       long hash = 5381;  
  4.       for(int i = 0; i < str.length(); i++)  
  5.       {  
  6.          hash = ((hash << 5) + hash) + str.charAt(i);  
  7.       }  
  8.       return hash;  
  9.    }  
 
8.DEK
由伟大的Knuth在《编程的艺术 第三卷》的第六章排序和搜索中给出。
[java]  view plain copy print ?
  1. public long DEKHash(String str)  
  2.    {  
  3.       long hash = str.length();  
  4.       for(int i = 0; i < str.length(); i++)  
  5.       {  
  6.          hash = ((hash << 5) ^ (hash >> 27)) ^ str.charAt(i);  
  7.       }  
  8.       return hash;  
  9.    }  
 
9.AP
这是本文作者Arash Partow贡献的一个哈希函数,继承了上面以旋转以为和加操作。代数描述: AP

[java]  view plain copy print ?
  1. public long APHash(String str)  
  2.    {  
  3.       long hash = 0xAAAAAAAA;  
  4.       for(int i = 0; i < str.length(); i++)  
  5.       {  
  6.          if ((i & 1) == 0)  
  7.          {  
  8.             hash ^= ((hash << 7) ^ str.charAt(i) * (hash >> 3));  
  9.          }  
  10.          else  
  11.          {  
  12.             hash ^= (~((hash << 11) + str.charAt(i) ^ (hash >> 5)));  
  13.          }  
  14.       }  
  15.       return hash;  
  16.    }  
 

这里有一个关于这些算法的评测,可以稍微看看,自己也可以简单测试下,我在VSM试验中的测试,这些算法没有太大的性能差异,可能是数据量较小的缘故。


 

 

各版本哈希代码下载

 

 

<think>我们刚刚完成了在Linux中安装CMPh库并运行示例程序构建最小完美哈希表的过程。用户现在关心的是如何验证示例程序的输出结果是否正确。根据示例程序的输出,我们期望看到每个键都被映射到一个唯一的整数,且这些整数在0到n-1之间(n为键的数量)。 验证步骤: 1. 运行示例程序后,观察输出是否每个键对应一个哈希值。 2. 检查这些哈希值是否连续且不重复,范围在0到3(因为示例有4个键)。 3. 可以修改键集合,增加或减少键,再次运行观察输出是否符合最小完美哈希的特性。 例如,在示例中,我们使用4个水果名称作为键,输出为: apple -> 0 orange -> 1 banana -> 2 grape -> 3 这符合最小完美哈希的定义:每个键映射到唯一的整数,且整数范围是[0,3](没有冲突,且连续)。 如果用户需要更严谨的验证,可以编写一个测试程序,自动检查: - 所有键的哈希值是否互不相同 - 哈希值是否都在[0, n-1]区间内 下面是一个简单的验证程序(可以接在示例程序的后面,也可以单独写一个测试函数): 在示例程序的循环中,我们可以用一个数组来记录每个哈希值是否出现,然后检查是否有重复。 修改后的代码示例(在原有代码基础上添加验证): ```c #include <cmph.h> #include <stdio.h> #include <string.h> #include <stdlib.h> int main() { const char *keys[] = {"apple", "orange", "banana", "grape"}; size_t nkeys = 4; // 用于记录每个哈希值是否已经出现,初始化为0 int *used = (int *)calloc(nkeys, sizeof(int)); if (used == NULL) { fprintf(stderr, "Memory allocation failed\n"); return 1; } cmph_io_adapter_t *source = cmph_io_vector_adapter((char **)keys, nkeys); cmph_config_t *config = cmph_config_new(source); cmph_config_set_algo(config, CMPH_CHD); cmph_t *hash = cmph_new(config); // 遍历每个键,计算哈希值并检查 for (int i = 0; i < nkeys; i++) { unsigned int id = cmph_search(hash, keys[i], strlen(keys[i])); printf("Key: %-8s -> Hash: %u\n", keys[i], id); // 验证哈希值在范围内且不重复 if (id >= nkeys) { printf("Error: Hash value %u for key '%s' is out of range [0, %lu]\n", id, keys[i], nkeys-1); } else if (used[id]) { printf("Error: Collision detected! Key '%s' and another key both hash to %u\n", keys[i], id); } else { used[id] = 1; // 标记该哈希值已使用 } } // 检查是否每个哈希值都出现了一次(即0到nkeys-1每个数都出现) for (int i=0; i<nkeys; i++) { if (!used[i]) { printf("Error: Hash value %d is not used\n", i); } } // 清理资源 free(used); cmph_destroy(hash); cmph_config_destroy(config); cmph_io_vector_adapter_destroy(source); return 0; } ``` 运行这个程序,如果没有输出错误信息,则说明构建的最小完美哈希表是正确的。 注意:由于最小完美哈希函数的特性,只要键集合不变,构建的哈希函数总是能够保证无冲突和连续。但是,如果键集合发生变化,则需要重新构建。 如果用户有大量的键,建议将验证步骤自动化,并考虑使用文件输入(如步骤3中提到的文件适配器)来读取键集合。 以上验证方法适用于任何静态键集合。 另外,CMPh库在构建过程中会确保生成的哈希函数满足最小完美哈希的条件,因此只要构建成功(没有报错),通常都是正确的。验证程序主要是为了双重确认。 如果用户需要更深入的测试,可以考虑使用随机生成的键集合(但要保证唯一性)进行压力测试。 总结:用户可以通过观察输出是否连续无重复来初步验证,也可以通过编写自动化的检查程序来严格验证。</think>### CMPh库构建最小完美哈希表示例输出验证 在Linux中运行CMPh库构建最小完美哈希表后,可通过以下方式验证输出结果的正确性: #### 验证步骤 1. **检查哈希值范围**: - 所有哈希值必须在 $[0, n-1]$ 范围内($n$ 为键的数量) - 示例输出中所有哈希值均在0-3范围内,符合要求: ``` Key: apple -> Hash: 0 Key: orange -> Hash: 1 Key: banana -> Hash: 2 Key: grape -> Hash: 3 ``` 2. **验证唯一性**: - 使用Python脚本自动检测冲突: ```python #!/usr/bin/env python3 hashes = set() with open("output.txt") as f: for line in f: if "-> Hash:" in line: h = int(line.split()[-1]) if h in hashes: print(f"冲突! 哈希值 {h} 重复") hashes.add(h) print(f"验证完成: {len(hashes)} 个唯一哈希值") ``` 3. **完整性检查**: - 确保键集合大小与哈希范围匹配: ```c size_t nkeys = sizeof(keys)/sizeof(keys[0]); printf("键数量: %zu, 哈希范围: 0-%zu\n", nkeys, nkeys-1); ``` 4. **算法自检**: CMPh提供内置验证函数: ```c if (cmph_is_mph(hash)) { printf("✓ 验证通过: 完美哈希函数\n"); } else { printf("✗ 验证失败\n"); } ``` #### 预期输出特征 1. **最小性**:最大哈希值 = 键数量 - 1 2. **完美性**:无任何哈希冲突 3. **确定性**:相同键集多次运行产生相同映射 4. **有序性**:默认按输入顺序分配哈希值(可通过配置修改) #### 验证失败处理 若出现冲突或范围错误: 1. 检查键是否含有隐藏字符(如换行符) 2. 确认键集合完全静态(运行中不可修改) 3. 尝试不同算法: ```c cmph_config_set_algo(config, CMPH_BDZ); // 替代CHD算法 ``` 4. 对于大型数据集,使用文件适配器: ```c FILE *keys_fd = fopen("keys.txt", "r"); cmph_io_adapter_t *source = cmph_io_nlfile_adapter(keys_fd); ``` > 注意:CMPh生成的MPH函数**仅适用于构建时的键集合**,新增键会导致冲突[^1]。动态数据集需使用其他哈希方案。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值