哈希表的基本原理?

时间:2008-11-12 01:23:45

标签: java hashtable

我对哈希表的基本概念感到很困惑。如果我要编写哈希代码,我怎么会开始? Hash表和普通数组之间有什么区别?

基本上如果有人回答了这个问题,我认为我的所有问题都会得到解答: 如果我有100个随机生成的数字(作为键),我将如何实现哈希表?为什么这对数组有利?

Psuedo-code或Java将被视为一种学习工具......

11 个答案:

答案 0 :(得分:68)

到目前为止,答案有助于定义哈希表并解释一些理论,但我认为一个例子可以帮助你对他们有更好的感受。

哈希表和普通数组有什么区别?

哈希表和数组都是允许您存储和检索数据的结构。两者都允许您指定索引并检索与其关联的值。正如Daniel Spiewak指出的那样,差异在于数组的索引是顺序,而散列表的索引是基于与它们相关联的数据值。

为什么我会使用哈希表?

哈希表可以提供一种非常有效的方法来搜索大量数据中的项目,尤其是那些无法轻易搜索的数据。 (“大”在这里表示ginormous,因为执行顺序搜索需要很长时间。)

如果我要编写哈希代码,我甚至可以开始?

没问题。最简单的方法是发明一个可以对数据执行的任意数学运算,它返回一个数字N(通常是一个整数)。然后使用该数字作为“存储桶”数组的索引,并将数据存储在存储桶#N中。诀窍在于选择一种操作,该操作倾向于将值放在不同的存储桶中,以便您以后轻松找到它们。

示例:大型购物中心会保留其顾客的汽车和停车位置的数据库,以帮助购物者记住他们停放的位置。数据库存储makecolorlicense plateparking location。在离开商店时,购物者通过输入其品牌和颜色来找到他的汽车。数据库返回(相对较短的)车牌和停车位列表。快速扫描找到购物者的车。

您可以使用SQL查询实现此目的:

SELECT license, location FROM cars WHERE make="$(make)" AND color="$(color)"

如果数据存储在一个基本上只是一个列表的数组中,您可以想象通过扫描数组中的所有匹配条目来实现查询。

另一方面,设想一个哈希规则:

  

添加make和color中所有字母的ASCII字符代码除以100,并使用余数作为哈希值。

此规则会将每个项目转换为0到99之间的数字,基本上将数据排序到100个存储桶中。每次客户需要找到汽车时,您都可以对品牌和颜色进行散列,以找到包含该信息的100个 one 桶。您立即将搜索量减少了100倍!

现在将示例扩展为大量数据,比如根据数十个条件搜索数百万条目的数据库。 “良好”的哈希函数将以最小化任何额外搜索的方式将数据分发到存储桶中,从而节省大量时间。

答案 1 :(得分:46)

首先,您必须了解哈希函数是什么。 哈希函数是一个获取密钥的函数(例如,一串arbritrary长度)并返回一个数字尽可能唯一。相同的密钥必须始终返回相同的哈希。 java中一个非常简单的字符串散列函数可能看起来像

public int stringHash(String s) {
    int h = s.length();
    for(char c : s.toCharArray()) {
        h ^= c;
    }
    return h;
}

您可以在http://www.azillionmonkeys.com/qed/hash.html

学习好的哈希函数

现在,哈希映射使用此哈希值将值放入数组中。简单的java方法:

public void put(String key, Object val) {
    int hash = stringHash(s) % array.length;
    if(array[hash] == null) {
        array[hash] = new LinkedList<Entry<String, Object> >();
    }
    for(Entry e : array[hash]) {
        if(e.key.equals(key)){
            e.value = val;
            return;
        }
    }
    array[hash].add(new Entry<String, Object>(key, val));
}

(此地图强制执行唯一键。并非所有地图都可以。)

两个不同的键可以散列到相同的值,或两个不同的散列映射到同一个数组索引。有许多技术可以解决这个问题。最简单的方法是为每个数组索引使用链表(或二叉树)。如果散列函数足够好,您将永远不需要线性搜索。

现在查找一个键:

public Object get(String key) {
    int hash = stringHash(key) % array.length;
    if(array[hash] != null) {
        for(Entry e : array[hash]) {
            if(e.key.equals(key))
                return e.value;
        }
    }

    return null;
}

答案 2 :(得分:17)

哈希表是关联。这与数组有很大的不同,数组只是线性数据结构。使用数组,您可以执行以下操作:

int[] arr = ...
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i] + 1);
}

注意如何通过指定精确的内存偏移量(i)从数组中获取元素。这与哈希表形成对比,哈希表允许您存储键/值对,稍后根据键检索值:

Hashtable<String, Integer> table = new Hashtable<String, Integer>();
table.put("Daniel", 20);
table.put("Chris", 18);
table.put("Joseph", 16);

通过上表,我们可以进行以下调用:

int n = table.get("Chris");

...并确保n的价值为18

我认为这可能会回答你的大部分问题。哈希表的实现是一个相当有趣的主题,一个which Wikipedia addresses passably well

答案 3 :(得分:8)

“我对Hash Tables查找密钥以及如何生成密钥的方式更感兴趣。”

  1. 哈希将密钥对象转换为数字。这称为“哈希” - 它从对象中产生哈希。见Hash Function。例如,对字符串的字节求和是标准散列技术。您计算模2&sup> 32 的总和,以使散列保持可管理的大小。哈希总是给出相同的答案。这是 O (1)。

  2. 该数字为您提供HashTable中的“插槽”。给定任意密钥对象,哈希值计算哈希值。哈希值然后为您提供表中的插槽。通常是mod( hash, table size )。这也是 O (1),

  3. 这是一般解决方案。两个数值计算,你已经从任意对象作为任意对象的键作为值。很少有东西可以快。

    从对象到哈希值的转换以这些常见方式之一发生。

    1. 如果它是4字节的“原始”对象,那么对象的原生值就是一个数字。

    2. 对象的地址是4个字节,然后对象的地址可以用作哈希值。

    3. 简单的hash function(MD5,SHA1,无论如何)累积对象的字节以创建一个4字节的数字。高级哈希值不是简单的字节总和,简单的和不足以反映所有原始输入位。

    4. 哈希表中的槽是mod(数量,表的大小)。

      如果该插槽具有所需的值,则表示您已完成。如果这不是所需的值,则需要查看其他位置。有几种流行的探测算法可以在表格中查找空闲点。线性是对下一个免费点的简单搜索。 Quadratic是一种寻找空闲时隙的非线性跳跃。随机数生成器(带有固定种子)可用于生成一系列探针,这些探针将均匀但任意地传播数据。

      探测算法不是 O (1)。如果表格足够大,碰撞的几率很低,探测无关紧要。如果表太小,则会发生冲突并发生探测。此时,它将成为“调整和调整”以平衡探测和表大小以优化性能的问题。通常我们只是把桌子做得更大。

      请参阅Hash Table

答案 4 :(得分:6)

我没有特别注意到的东西:

在数组上使用哈希表的关键是性能。

遍历数组通常需要从O(1)到O(x)的任何位置,其中x是数组中的项数。但是,找到你的项目的时间将非常变量,特别是如果我们在谈论数组中的数十万个项目。

正确加权的哈希表通常具有几乎超过O(1)的常量访问时间,无论哈希表中有多少项。

答案 5 :(得分:4)

您不希望对100个随机生成的数字使用哈希表。

考虑哈希表的一个好方法是考虑价值对。让我们使用学生,并说每个人都有一个学生证号码。在您的程序中,您可以存储学生的信息(姓名,电话号码,账单等)。您只想使用基本信息(例如姓名或学生ID)找到有关学生的所有信息。

假设您有10,000名学生。如果将它们全部存储在数组中,则必须遍历整个数组,将每个条目的学生ID与您要查找的学生ID进行比较。

相反,如果您将他们的学生ID号“哈希”(见下文)到数组中的某个位置,那么您只需要搜索学生的数字具有相同的哈希值。找到你想要的东西要少得多。

在这个例子中,假设学生ID只是6位数字。我们的哈希函数可以仅使用数字的底部3位数作为“哈希键”。因此232145被散列到数组位置145.那么你只需要一个999元素的数组(每个元素都是学生列表)。

这对你来说应该是一个好的开始。当然,您应该阅读教科书或维基百科来获取此类信息。但我认为你已经这样做了,并且厌倦了阅读。

答案 6 :(得分:3)

简而言之,就是哈希表的工作原理。

想象一下,你有一个满是书的图书馆。如果你要将书籍存放在一个阵列中,你可以将每本书放在书架上的某个位置,然后当有人要求你找一本书时,你会看到所有书架 - 非常慢。如果有人说“书#12345”,你可以很容易地找到它。

让我们说,如果书名以'A'开头,则在第1行。如果第二个字母是'B',则在第1行,第2行。如果第三个字母是'C ',它在第1排,第2排,第3层......等等,直到您确定书的位置。然后,根据书的标题,你可以准确地知道它应该在哪里。

现在,我所描述的简单化“散列”算法存在一些问题 - 一些架子会过载,而其他架子会空着,一些书籍将被分配到同一个槽..所以真正的散列函数是精心构造以尽量避免此类问题。

但这是基本的想法。

答案 7 :(得分:0)

我将回答有关哈希表和数组之间差异的部分...但由于我之前从未实现过任何导入的哈希算法,我会将其留给更有见识的人:)

数组只是一个有序的对象列表。对象本身并不重要......重要的是,如果要按插入顺序列出对象,它总是相同的(意味着第一个元素总是的索引为0)。

至于哈希表,它是由键索引的,而不是命令...我认为哈希算法的基本搜索会给你提供比我更多的洞察力...维基百科有一个非常体面的...确定密钥进入的“桶”,以快速检索用作密钥的任意对象。

关于优点:如果插入顺序很重要,则需要一个数组或某种有序列表。如果通过任意键快速查找(由各种哈希函数键入)很重要,那么哈希表是有意义的。

答案 8 :(得分:0)

[这是me.yahoo.com/a上述评论的回复]

这取决于你的哈希函数。让我们假设您的哈希函数按照单词的长度哈希一个单词,克里斯的键将是5.同样,雅虎的键也将是5.现在,两个值(克里斯和雅虎)将低于5(即在一个'桶'键入5)。这样,您就不必使数组等于数据大小。

答案 9 :(得分:0)

我相信,这个问题现在已经以很多不同的方式得到了很清楚的回答。

我只想添加另一个视角(也可能会让新读者感到困惑)

在抽象程度最低的情况下,数组只是连续的内存块。给定单个元素的起始地址(startAddress),大小(sizeOfElement)和index,元素的地址计算如下:

elementAddress = startAddress + sizeOfElement * index

这里需要注意的有趣的事情是,数组可以被抽象/查看为哈希表,其中index为关键,上述函数为哈希函数,用于计算 O(1)中值的位置)

答案 10 :(得分:0)

哈希表是为快速查找而创建的数据结构。

当条目数非常小时,哈希表无效。

reference

一些例子:

    import java.util.Collection;
    import java.util.Enumeration;
    import java.util.Hashtable;
    import java.util.Set;

    public class HashtableDemo {

    public static void main(String args[]) {

// Creating Hashtable for example

     Hashtable companies = new Hashtable();


// Java Hashtable example to put object into Hashtable
// put(key, value) is used to insert object into map

     companies.put("Google", "United States");
     companies.put("Nokia", "Finland");
     companies.put("Sony", "Japan");


// Java Hashtable example to get Object from Hashtable
// get(key) method is used to retrieve Objects from Hashtable

     companies.get("Google");


// Hashtable containsKey Example
// Use containsKey(Object) method to check if an Object exits as key in
// hashtable

     System.out.println("Does hashtable contains Google as key: "+companies.containsKey("Google"));


// Hashtable containsValue Example
// just like containsKey(), containsValue returns true if hashtable
// contains specified object as value

      System.out.println("Does hashtable contains Japan as value: "+companies.containsValue("Japan"));


// Hashtable enumeration Example
// hashtabl.elements() return enumeration of all hashtable values

      Enumeration enumeration = companies.elements();

      while (enumeration.hasMoreElements()) {
      System.out.println("hashtable values: "+enumeration.nextElement());
      }


// How to check if Hashtable is empty in Java
// use isEmpty method of hashtable to check emptiness of hashtable in
// Java

       System.out.println("Is companies hashtable empty: "+companies.isEmpty());


// How to find size of Hashtable in Java
// use hashtable.size() method to find size of hashtable in Java

      System.out.println("Size of hashtable in Java: " + companies.size());


// How to get all values form hashtable in Java
// you can use keySet() method to get a Set of all the keys of hashtable
// in Java

      Set hashtableKeys = companies.keySet();


// you can also get enumeration of all keys by using method keys()

      Enumeration hashtableKeysEnum = companies.keys();


// How to get all keys from hashtable in Java
// There are two ways to get all values form hashtalbe first by using
// Enumeration and second getting values ad Collection

      Enumeration hashtableValuesEnum = companies.elements();


      Collection hashtableValues = companies.values();


// Hashtable clear example
// by using clear() we can reuse an existing hashtable, it clears all
// mappings.

       companies.clear();
      }
     }

输出:

Does hashtable contains Google as key: true

Does hashtable contains Japan as value: true

hashtable values: Finland

hashtable values: United States

hashtable values: Japan

Is companies hashtable empty: false

Size of hashtable in Java: 3