你将如何用语言 x 实现哈希表? [关闭]

Posted

技术标签:

【中文标题】你将如何用语言 x 实现哈希表? [关闭]【英文标题】:How would you implement a hashtable in language x? [closed] 【发布时间】:2010-09-06 17:15:46 【问题描述】:

这个问题的重点是收集使用不同语言的数组实现哈希表的示例列表。如果有人能对它们的工作方式以及每个示例发生的情况进行非常详细的概述,那就太好了。

编辑:

为什么不直接使用特定语言的内置哈希函数?

因为我们应该知道哈希表是如何工作的并且能够实现它们。这似乎不是一个超级重要的话题,但了解最常用的数据结构之一如何工作对我来说似乎非常重要。如果这要成为编程的***,那么这些是我将来到这里的一些类型的问题。我不是在寻找要在这里写的 CS 书。我可以将 Intro to Algorithms 下架并阅读有关哈希表的章节并获取此类信息。更具体地说,我正在寻找的是代码示例。不仅对我尤其如此,而且对于可能有一天会搜索类似信息并偶然发现此页面的其他人也是如此。

更具体地说:如果你不得不实现它们,并且不能使用内置函数,你会怎么做?

您无需将代码放在这里。将其放入 pastebin 并链接它。

【问题讨论】:

这个想法是让 SO 有机地 成为编程的***。不要强迫提问;这是业力农业的味道。 【参考方案1】:

哈希表是一种允许在恒定时间内查找项目的数据结构。它通过散列一个值并将该值转换为数组中的偏移量来工作。哈希表的概念相当容易理解,但实现起来显然更难。我没有在这里粘贴整个哈希表,但这里有一些我几周前用 C 语言制作的哈希表的 sn-ps...

创建哈希表的基础之一是拥有良好的哈希函数。我在我的哈希表中使用了 djb2 哈希函数:

int ComputeHash(char* key)

    int hash = 5381;
    while (*key)
    hash = ((hash << 5) + hash) + *(key++);
    return hash % hashTable.totalBuckets;

然后是用于创建和管理表中存储桶的实际代码

typedef struct HashTable
    HashTable* nextEntry;
    char*      key;
    char*      value;
HashBucket;

typedef struct HashTableEntry
    int totalBuckets;       // Total number of buckets allocated for the hash table
    HashTable** hashBucketArray; // Pointer to array of buckets
HashTableEntry;
HashTableEntry hashTable;

bool InitHashTable(int totalBuckets)

    if(totalBuckets > 0)
    
        hashTable.totalBuckets = totalBuckets;
        hashTable.hashBucketArray = (HashTable**)malloc(totalBuckets * sizeof(HashTable));
        if(hashTable.hashBucketArray != NULL)
        
            memset(hashTable.hashBucketArray, 0, sizeof(HashTable) * totalBuckets);
            return true;
        
    
    return false;


bool AddNode(char* key, char* value)

    int offset = ComputeHash(key);
    if(hashTable.hashBucketArray[offset] == NULL)
    
        hashTable.hashBucketArray[offset] = NewNode(key, value);
        if(hashTable.hashBucketArray[offset] != NULL)
            return true;
    

    else
    
        if(AppendLinkedNode(hashTable.hashBucketArray[offset], key, value) != NULL)
            return true;
    
    return false;


HashTable* NewNode(char* key, char* value)

    HashTable* tmpNode = (HashTable*)malloc(sizeof(HashTable));
    if(tmpNode != NULL)
    
        tmpNode->nextEntry = NULL;
        tmpNode->key   = (char*)malloc(strlen(key));
        tmpNode->value = (char*)malloc(strlen(value));

        strcpy(tmpNode->key, key);
        strcpy(tmpNode->value, value);
    
    return tmpNode;

AppendLinkedNode 找到链表中的最后一个节点,并将一个新节点追加到它。

代码会这样使用:

if(InitHashTable(100) == false) return -1;
AddNode("10", "TEN");

找到一个节点很简单:

HashTable* FindNode(char* key)

    int offset = ComputeHash(key);
    HashTable* tmpNode = hashTable.hashBucketArray[offset];
    while(tmpNode != NULL)
    
        if(strcmp(tmpNode->key, key) == 0)
            return tmpNode;
        tmpNode = tmpNode->nextEntry;
    
    return NULL;

和用法如下:

char* value = FindNode("10");

【讨论】:

我不确定你是否可以使用char* value = FindNode("10");,因为FindNode 返回HashTable*。所以你会看到类似的东西:char* value = FindNode("10")-&gt;value;【参考方案2】:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;


public class HashTable 

    class KeyValuePair 

        Object key;

        Object value;

        public KeyValuePair(Object key, Object value) 
            this.key = key;
            this.value = value;
        
    

    private Object[] values;

    private int capacity;

    public HashTable(int capacity) 
        values = new Object[capacity];
        this.capacity = capacity;
    

    private int hash(Object key) 
        return Math.abs(key.hashCode()) % capacity;
    

    public void add(Object key, Object value) throws IllegalArgumentException 

        if (key == null || value == null)
            throw new IllegalArgumentException("key or value is null");

        int index = hash(key);

        List<KeyValuePair> list;
        if (values[index] == null) 
            list = new ArrayList<KeyValuePair>();
            values[index] = list;

         else 
            // collision
            list = (List<KeyValuePair>) values[index];
        

        list.add(new KeyValuePair(key, value));
    

    public Object get(Object key) 
        List<KeyValuePair> list = (List<KeyValuePair>) values[hash(key)];
        if (list == null) 
            return null;
        
        for (KeyValuePair kvp : list) 
            if (kvp.key.equals(key)) 
                return kvp.value;
            
        
        return null;
    

    /**
     * Test
     */
    public static void main(String[] args) 

        HashTable ht = new HashTable(100);

        for (int i = 1; i <= 1000; i++) 
            ht.add("key" + i, "value" + i);
        

        Random random = new Random();
        for (int i = 1; i <= 10; i++) 
            String key = "key" + random.nextInt(1000);
            System.out.println("ht.get(\"" + key + "\") = " + ht.get(key));
           
    

【讨论】:

不确定list.add(new KeyValuePair(key, value));add 函数结束时在做什么? 我真的不明白这个问题。它将键 -> 值映射添加到内部数组,以便稍后在 get() 调用中检索它。 抱歉,我没有想清楚。我在想它是在那个方法中定义的,所以它只会被垃圾收集,但你将它用作values 成员变量中元素的句柄。【参考方案3】:

HashTable 是一个非常简单的概念:它是一个数组,其中键和值对通过以下方案放入其中(如关联数组):

散列函数将(希望)未使用的索引的键散列到数组中。然后将该值放入该特定索引处的数组中。

数据检索很容易,因为可以通过哈希函数计算数组的索引,因此查找时间约为 O(1)。

当哈希函数将 2 个不同的键映射到同一个索引时会出现问题...有很多方法可以处理这个问题,我不会在这里详细说明...

哈希表是一种快速存储和检索数据的基本方式,并且几乎“内置”在所有编程语言库中。

【讨论】:

这与问题有什么关系? 同意这可能无法回答问题,并且问题可能没有建设性,但至少我终于明白为什么哈希表检索值如此之快!很尴尬,直到现在我还以为是巫毒。【参考方案4】:

我一直在寻找一种完全可移植的 C 哈希表实现,并开始对如何自己实现一个感兴趣。经过一番搜索,我发现: Julienne Walker's The Art of Hashing 有一些关于散列和散列表的很棒的教程。实现它们比我想象的要复杂一些,但它是一本很棒的书。

【讨论】:

【参考方案5】:

这是我的哈希表代码,用 Java 实现。遭受小故障 - 键和值字段不一样。将来可能会编辑它。

public class HashTable

    private LinkedList[] hashArr=new LinkedList[128];
    public static int HFunc(int key)
    
        return key%128;
    


    public boolean Put(Val V)
    

        int hashval = HFunc(V.getKey());
        LinkedNode ln = new LinkedNode(V,null);
        hashArr[hashval].Insert(ln);
        System.out.println("Inserted!");
        return true;            
    

    public boolean Find(Val V)
    
        int hashval = HFunc(V.getKey());
        if (hashArr[hashval].getInitial()!=null && hashArr[hashval].search(V)==true)
        
            System.out.println("Found!!");
            return true;
        
        else
        
            System.out.println("Not Found!!");
            return false;
        

    
    public boolean delete(Val v)
    
        int hashval = HFunc(v.getKey());
        if (hashArr[hashval].getInitial()!=null && hashArr[hashval].delete(v)==true)
        
            System.out.println("Deleted!!");
            return true;
        
        else 
        
            System.out.println("Could not be found. How can it be deleted?");
            return false;
        
    

    public HashTable()
    
        for(int i=0; i<hashArr.length;i++)
            hashArr[i]=new LinkedList();
    



class Val

    private int key;
    private int val;
    public int getKey()
    
        return key;
    
    public void setKey(int k)
    
        this.key=k;
    
    public int getVal()
    
        return val;
    
    public void setVal(int v)
    
        this.val=v;
    
    public Val(int key,int value)
    
        this.key=key;
        this.val=value;
    
    public boolean equals(Val v1)
    
        if (v1.getVal()==this.val)
        
            //System.out.println("hello im here");
            return true;
        
        else 
            return false;
    


class LinkedNode

    private LinkedNode next;
    private Val obj;
    public LinkedNode(Val v,LinkedNode next)
    
        this.obj=v;
        this.next=next;
    
    public LinkedNode()
    
        this.obj=null;
        this.next=null;
    
    public Val getObj()
    
        return this.obj;    
    
    public void setNext(LinkedNode ln)
    
        this.next = ln;
    

    public LinkedNode getNext()
    
        return this.next;
    
    public boolean equals(LinkedNode ln1, LinkedNode ln2)
    
        if (ln1.getObj().equals(ln2.getObj()))
        
            return true;
        
        else 
            return false;

    



class LinkedList

    private LinkedNode initial;
    public LinkedList()
    
        this.initial=null;
    
    public LinkedList(LinkedNode initial)
    
        this.initial = initial;
    
    public LinkedNode getInitial()
    
        return this.initial;
    
    public void Insert(LinkedNode ln)
    
        LinkedNode temp = this.initial;
        this.initial = ln;
        ln.setNext(temp);
    
    public boolean search(Val v)
    
        if (this.initial==null)
            return false;
        else 
        
            LinkedNode temp = this.initial;
            while (temp!=null)
            
                //System.out.println("encountered one!");
                if (temp.getObj().equals(v))
                    return true;
                else 
                    temp=temp.getNext();
            
            return false;
        

    
    public boolean delete(Val v)
    
        if (this.initial==null)
            return false;
        else 
        
            LinkedNode prev = this.initial;
            if (prev.getObj().equals(v))
            
                this.initial = null;
                return true;
            
            else
            
                LinkedNode temp = this.initial.getNext();
            while (temp!=null)
            
                if (temp.getObj().equals(v))
                
                    prev.setNext(temp.getNext());
                    return true;
                
                else
                
                    prev=temp;
                    temp=temp.getNext();
                
            
            return false;
            
        
    

【讨论】:

【参考方案6】:

我认为你需要更具体一点。关于以下选项,哈希表有几种变体

哈希表是固定大小的还是动态的? 使用什么类型的哈希函数? 调整哈希表大小时是否存在任何性能限制?

列表可以继续。这些约束中的每一个都可能导致任何语言的多个实现。

就我个人而言,我只会使用以我选择的语言提供的内置哈希表。我什至会考虑实现自己的唯一原因是性能问题,即便如此,也很难击败大多数现有的实现。

【讨论】:

【参考方案7】:

可能会使用上面代码的人,注意内存泄漏:

tmpNode->key   = (char*)malloc(strlen(key)+1);   //must have +1 for '\0'
tmpNode->value = (char*)malloc(strlen(value)+1); //must have +1
strcpy(tmpNode->key, key);
strcpy(tmpNode->value, value);

并完成代码:

HashNode* AppendLinkedNode(HashNode* ptr, char* key, char* value)

    //follow pointer till end
    while (ptr->nextEntry!=NULL)
    
        if (strcmp(ptr->value,value)==0) return NULL;
        ptr=ptr->nextEntry;
    
    ptr->nextEntry=newNode(key,value);
    return ptr->nextEntry;

【讨论】:

【参考方案8】:

F# 中作为函数的最小实现,它从给定的键值对序列构建哈希表并返回一个函数,该函数在哈希表中搜索与给定键关联的值:

> let hashtbl xs =
    let spine = [|for _ in xs -> ResizeArray()|]
    let f k = spine.[abs(k.GetHashCode()) % spine.Length]
    Seq.iter (fun (k, v) -> (f k).Add (k, v)) xs
    fun k -> Seq.pick (fun (k', v) -> if k=k' then Some v else None) (f k);;
val hashtbl : seq<'a * 'b> -> ('a -> 'b) when 'a : equality

【讨论】:

在上面的 sn-p 的第 3 行需要一个小修正:k.GetHashCode() 可能返回一个负数(例如,"Key with negative hashcode".GetHashCode() 返回 -648767821),这反过来会通过函数 f 计算此类键的存储桶编号时导致 System.IndexOutOfRangeException。【参考方案9】:

我去阅读了一些关于散列的***页面:http://en.wikipedia.org/wiki/Hash_table。在这里为哈希表编写代码似乎需要做很多工作,特别是因为我使用的大多数语言都已经内置了它们。为什么要在这里实现?这些东西确实属于语言库。

请详细说明您预期的解决方案应包括哪些内容:

散列函数 可变桶数 碰撞行为

还要说明在这里收集它们的目的是什么。任何严肃的实现都会很容易让人满嘴=这将导致很长的答案(每个可能有几页长)。你也可能引诱人们从库中复制代码...

【讨论】:

以上是关于你将如何用语言 x 实现哈希表? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

C语言哈希表

什么是哈希表以及如何在 C 中创建它? [关闭]

哈希表与平衡二叉树[关闭]

哈希表

[Codevs 1230]元素查找(手写哈希表)

学习 Go 语言数据结构:实现哈希表