Hash table,国内相当一部分书籍将其直译为哈希表,但博主本人喜欢称其为散列表。
散列表支持任何基于 Key-Value 对的插入,检索,删除操作。
比如在 .NET 1.x 版本下,我们可以这样使用:
10 namespace Lucifer.CSharp.Sample
11 {
12 class Program
13 {
14 public static void Main()
15 {
16 Hashtable table = new Hashtable();
17
18 //插入操作
19 table[1] = "A";
20 table.Add(2, "B");
21 table[3] = "C";
22
23 //检索操作
24 string a = (string)table[1];
25 string b = (string)table[2];
26 string c = (string)table[3];
27
28 //删除操作
29 table.Remove(1);
30 table.Remove(2);
31 table.Remove(3);
32 }
33 }
34 }
而在 .NET 2.0 及以上版本下,我们则这样使用:
10 namespace Lucifer.CSharp.Sample
11 {
12 class Program
13 {
14 public static void Main()
15 {
16 Dictionary<int, string> table =
17 new Dictionary<int, string>();
18
19 //插入操作
20 table[1] = "A";
21 table.Add(2, "B");
22 table[3] = "C";
23
24 //检索操作
25 string a = table[1];
26 string b = table[2];
27 string c;
28 table.TryGetValue(3, out c);
29
30 //删除操作
31 table.Remove(1);
32 table.Remove(2);
33 table.Remove(3);
34 }
35 }
36 }
众所周知,假如在数组中知道了某个索引的话,也就知道了该索引位置上的值。同理,在散列表中,我们所要做的就是根据 Key 来知道 Value 在表中的位置 。 Key 的作用只不过用来指示位置。而通过 Key 来查找位置,意味着查找时间从顺序查找的 O(N),折半查找的 O(lgN) 骤减至 O(1)。
那么我们如何把可能是字符串,数字等的某 Key 转换成表的索引呢?这一步,在 .NET 中由 GetHashCode 方法来完成。当然在散列表内还需要根据 Hash Code 来进一步计算,但我们现在暂且认为通过 Key 的 GetHashCode 方法我们已经可以找到 Value 了。实际上,对于外部开发人员来说的确不需要再做别的工作了。而这也是 Object 类带有 GetHashCode 虚方法的原因。当我们在使用 Stack<T>,List<T>,Queue<T> 等集合时,根本不需要在乎有没有 GetHashCode 方法,但是如果你想使用 Dictionary<TKey, TValue>,HashSet<T>(.NET 3.5新增) 等集合时,则必须正确重写 GetHashCode 方法,否则这些集合不能正常工作。当然,使用.NET基元类型没有任何问题,因为 Microsoft 已经为这些类型实现了良好的重载。
而在讲解数据结构的书籍里,把 GetHashCode 方法完成的工作称为“散列函数(hash function)”。
散列函数
那么散列函数是如何工作的呢?通常来说,它会把某个数字或者能够转换成数字的类型映射成固定位数的数字。比如 .NET 的 GetHashCode 方法返回 32 位有符号整型。当我们把64 位或更多位数字映射成 32 位时,很显然,这带来了一个复杂问题:两个或多个不同的 Key 可能被散列到同一位置,引起碰撞冲突。这种情况很难避免,因为 Key 的个数比位置要多。当然,如果能够避免碰撞冲突,那就完美了。我们把能够完成这种情况的散列函数叫做完全散列函数(perfect hash function)。
从定义和实现来看,散列函数其实就是伪随机数生成器(PRNG)。总的来说,散列函数的性能通常可以接受,而且也可以把散列函数当作 PNRG 来进行比较。理论上,存在一个完全散列函数。它从不会让数据发生碰撞冲突。实际上,要找到这样的散列函数以及应用该散列函数的实际应用程序太困难了。即使是它最低限度的变体,也相当有限。
实践中,有很多种数据排列。有一些非常随机,另外一些则相当的格式化。一种散列函数很难概括所有的数据类型,即使针对某种数据类型也很困难。我们所能做的就是通过不断尝试来寻找最适合我们需要的散列函数。这也是必须重写 GetHashCode 方法的原因之一。
下面是我们分析选择散列函数的两大要素:
1. 数据分布。这是衡量散列函数生成散列值好坏的尺度。分析这个需要知道在数据集内发生碰撞冲突的数量,即非唯一的散列值。
2. 散列函数的效率。这是衡量散列函数生成散列值快慢的尺度。理论上,散列函数非常快。但是也应当注意到,散列函数并不总是保持 O(1) 的时间复杂度。
那么如何来实现散列函数呢?基本上有以下两大方法论:
1. 加法和乘法。这个方法的主要思想是通过遍历数据,然后以某种计算形式来构造散列值。通常情况下是乘以某个素数的乘法形式。如下图所示:
Figure1
目前来说,还没有数学方法能够证明素数和散列函数之间的关系。不过在实践中利用一些素数可以得到很好的结果。
2. 位移。顾名思义,散列值是通过位移处理获得的。每一次的处理结果都累加,最后返回该值。如下图所示:
Figure2
此外,还有很多方法可以用来计算散列值。不过这些方法也不外乎是上述两种的变种或综合运用。老实说,一个良好的散列函数很大程度上是靠经验得来。除此之外,别无良方。幸运的是,前人留下了许多经典的散列函数实现。接下来,我们就来了解下这些经典的散列函数。注意,本文所介绍的散列函数均不能使用在诸如加密,数字签名等领域。
关于整型和浮点类型的散列函数,因为都很简单,在这里就不再详细阐述。有兴趣的可以使用 Reflector 等反编译工具自行查看其 GetHashCode 实现。值得一提的是浮点类型要注意使 +0.0 和 -0.0 的散列值结果一致,还有就是 128 位的 Decimal 类型实现。
接下来将详细介绍几个字符串散列函数。
先看下 Java 的字符串散列函数是什么样。注意,本文代码均以C#写就,下同。代码如下:
8 public int JavaHash(string str)
9 {
10 int hashCode = 0;
11 for (int i = 0; i < str.Length; i++)
12 {
13 hashCode = 31 * hashCode + str[i];
14 }
15 return hashCode;
16 }
上述散列函数,一般来讲已经相当好。不过如果字符串很长,我们可能需要对它进行修改。它实际上来自于 K & R 的《The C Programming Language》。其中我们使用的素数 31 可以替换成 31, 131, 1313, 13131, 131313,... 等。看起来,它跟下面这个散列函数很相似。
18 public int DJBHash(string str)
19 {
20 int hashCode = 5381;
21 for (int i = 0; i < str.Length; i++)
22 {
23 hashCode = ((hashCode << 5) + hashCode)
24 + str[i];
25 }
26 return hashCode;
27 }
该函数最早由 Daniel J. Bernstein 教授展示于新闻组 comp.lang.C 上,是最有效率的散列函数之一。
我们再来看看 .NET 中的字符串散列函数。代码如下:
29 public unsafe int DotNetHash(string str)
30 {
31 fixed(char* charPtr = new String(str.ToCharArray()))
32 {
33 int hashCode = (5381 << 16) + 5381;
34 int numeric = hashCode;
35 int* intPtr = (int*)charPtr;
36
37 for (int i = str.Length; i > 0; i -= 4)
38 {
39 hashCode = ((hashCode << 5) + hashCode +
40 (hashCode >> 27)) ^ intPtr[0];
41 if (i <= 2)
42 {
43 break;
44 }
45 numeric = ((numeric << 5) + numeric +
46 (numeric >> 27)) ^ intPtr[1];
47 intPtr += 2;
48 }
49 return hashCode + numeric * 1566083941;
50 }
51 }
上述代码实际上是大牛唐纳德在他的《The Art Of Computer Programming Volume 3》中的变种。因为老唐的散列函数在某些情况下会有问题,所以 .NET 没有完全采用老唐的办法。老唐提供的散列函数如下:
53 public int DEKHash(string str)
54 {
55 int hashCode = str.Length;
56 for (int i = 0; i < str.Length; i++)
57 {
58 hashCode = ((hashCode << 5) ^ (hashCode >> 27))
59 ^ str[i];
60 }
61 return hashCode;
62 }
此外在Unix平台上还有一种广泛采用的散列函数。代码如下:
64 public int ELFHash(string str)
65 {
66 int hashCode = 0;
67 int numeric = 0;
68 for (int i = 0; i < str.Length; i++)
69 {
70 hashCode = (hashCode << 4) + str[i];
71 if ((numeric = hashCode & 0xF0000000L) != 0)
72 {
73 hashCode ^= (hashCode >> 24);
74 }
75 hashCode &= ~numeric;
76 }
77 return hashCode;
78 }
分享到:
相关推荐
Search function: This function searches the key specified as the parameter in the hash table. If exist, return true, else return false. Note to use the eq which is a member of HashSet to compare ...
根据提供的文件信息,我们可以深入分析C语言中哈希表(Hash Table)的实现方式与具体细节。本篇文章将从以下几个方面展开讨论: 1. **哈希表的基本概念** 2. **哈希函数的设计** 3. **哈希表的创建与管理** 4. **...
"FPGA-based hash table"是指利用FPGA的特性实现的哈希表数据结构。哈希表是一种高效的数据存储和检索结构,它通过将关键字映射到数组的特定位置来快速查找、插入和删除数据。 哈希表的核心是哈希函数,它将输入的...
方便地查看文件的MD5/SHA1值 – HashTab给文件属性窗口添加校验值!
Sorting Hash Table Increase Sorting Searching Elementd in hash table BSTree
在Java编程语言中,哈希表(Hash Table)是一种常用的数据结构,它的实现通常通过`Hashtable`类。这个数据结构提供了快速的插入、删除和查找操作,其平均时间复杂度为O(1)。`Hashtable`是Java集合框架的一部分,位于...
在本实验中,主要涉及了哈希表(Hash Table)这一数据结构的使用,目的是让学生了解哈希表的概念、不同的哈希方式以及如何在程序中应用哈希表进行员工信息的搜索。实验环境包括Windows XP操作系统和VC++ 6.0/WinTC...
this is a hash table using hash function
散列表(Hash Table)从理论到实用 散列表(Hash Table)是一种数据结构,它可以实现快速查找、插入和删除操作。下面是关于散列表的理论和实用知识点的总结。 散列表的定义 散列表是一种数据结构,它使用哈希函数...
分布式哈希表(DHT,Distributed Hash Table)是一种用于分布式环境的数据存储技术,它将数据分布在网络中的多个节点上,以实现高效、可扩展的数据管理和检索。DHT的设计目标是提供一种全局一致性的哈希函数,使得...
哈希表(Hash Table)是一种高效的数据结构
本文探讨了分布式哈希表(Distributed Hash Table, DHT)系统在节点频繁加入与离开(即“churn”现象)情况下查找性能的优化策略。随着互联网规模应用的不断发展,DHT作为基础设施的支持变得尤为重要。然而,节点...
Hash tables are the fundamental data structure for analytical database workloads, such as aggregation, joining, set filtering and records... We designed a new hash table, SAHA, which is tightly integrate
本文档收集了前端大厂最新面试题的 Hash Table 相关知识点,涵盖 Hash Table 的基本概念、 ハッシュ関数、 Collision 解决方法、Hash Table 的实现和应用等方面。 一、Hash Table 基本概念 * Hash Table 是一种...
现行哈希的C++实现代码,具体原理可以参考:Larson, Per-Ake (April 1988),"Dynamic Hash Tables",Communications of the ACM 31: 446–457, doi:10.1145/42404.42410.
(1) 建立一个哈希表,哈希函数为除留余数法,处理冲突的方法为线性探测再散列或二次探测再散列。 (2) 往哈希表中依次存入插入多个单词。 (3) 显示哈希表的存储情况。 (4) 计算哈希表的平均查找长度。...
在IT领域,哈希表(Hash Table)是一种高效的数据结构,用于存储和检索键值对。标题中的"u_hash_table.rar_Table For Two"暗示我们关注的是一个特定的哈希表实现,可能是为处理两个相关对象设计的。描述中的"compare...
`uthash`是一个开源的C宏库,专门用于简化哈希表的实现,它提供了高效且易于使用的接口,使程序员能够快速构建自己的哈希表结构。 哈希表的关键组成部分包括哈希函数、冲突解决策略和存储结构。哈希函数负责将键...
哈希表(Hash Table)是一种数据结构,它通过关联键(Key)与值(Value)实现高效的数据存储和检索。在“所有哈希ids_hash_Table_”这个主题中,我们聚焦于利用哈希表来创建作弊表,这可能是为了在游戏中快速查找...