zoukankan      html  css  js  c++  java
  • 哈希表的实现

    在php的底层实现中,hash表是最常见的一种使用。那hash表具体是怎样实现的呢?

    哈希表的实现

    在了解到哈希表的原理之后要实现一个哈希表也很容易,主要需要完成的工作只有三点:

    1. 实现哈希函数
    2. 冲突的解决
    3. 操作接口的实现

    数据结构

    首先我们需要一个容器来保存我们的哈希表,哈希表需要保存的内容主要是保存进来的的数据, 同时为了方便的得知哈希表中存储的元素个数,需要保存一个大小字段, 第二个需要的就是保存数据的容器了。作为实例,下面将实现一个简易的哈希表。基本的数据结构主要有两个, 一个用于保存哈希表本身,另外一个就是用于实际保存数据的单链表了,定义如下:

    typedef struct _Bucket
    {
        char *key;
        void *value;
        struct _Bucket *next;
    } Bucket;
     
    typedef struct _HashTable
    {
        int size;
        int elem_num;
        Bucket** buckets;
    } HashTable;

    上面的定义和PHP中的实现类似,为了便于理解裁剪了大部分无关的细节,在本节中为了简化, key的数据类型为字符串,而存储的数据类型可以为任意类型。

    Bucket结构体是一个单链表,这是为了解决多个key哈希冲突的问题,也就是前面所提到的的链接法。 当多个key映射到同一个index的时候将冲突的元素链接起来。

    哈希函数实现

    哈希函数需要尽可能的将不同的key映射到不同的槽(slot或者bucket)中,首先我们采用一种最为简单的哈希算法实现: 将key字符串的所有字符加起来,然后以结果对哈希表的大小取模,这样索引就能落在数组索引的范围之内了。

    static int hash_str(char *key)
    {
        int hash = 0;
     
        char *cur = key;
     
        while(*cur != '') {
            hash += *cur;
            ++cur;
        }
     
        return hash;
    }
     
    // 使用这个宏来求得key在哈希表中的索引
    #define HASH_INDEX(ht, key) (hash_str((key)) % (ht)->size)

    这个哈希算法比较简单,它的效果并不好,在实际场景下不会使用这种哈希算法, 例如PHP中使用的是称为DJBX33A算法, 这里列举了Mysql,OpenSSL等开源软件使用的哈希算法, 有兴趣的读者可以前往参考。

    有兴趣的读者可以运行本小节实现的哈希表实现,在输出日志中将看到很多的哈希冲突, 这是本例中使用的哈希算法过于简单造成的.

    操作接口的实现

    为了操作哈希表,实现了如下几个操作接口函数:

    int hash_init(HashTable *ht);                               // 初始化哈希表
    int hash_lookup(HashTable *ht, char *key, void **result);   // 根据key查找内容
    int hash_insert(HashTable *ht, char *key, void *value);     // 将内容插入到哈希表中
    int hash_remove(HashTable *ht, char *key);                  // 删除key所指向的内容
    int hash_destroy(HashTable *ht);

    下面以初始化、插入和获取操作函数为例:

    int hash_init(HashTable *ht)
    {
        ht->size        = HASH_TABLE_INIT_SIZE;
        ht->elem_num    = 0;
        ht->buckets     = (Bucket **)calloc(ht->size, sizeof(Bucket *));
     
        if(ht->buckets == NULL) return FAILED;
     
        LOG_MSG("[init]	size: %i
    ", ht->size);
     
        return SUCCESS;
    }

    初始化的主要工作是为哈希表申请存储空间,函数中使用calloc函数的目的是确保 数据存储的槽为都初始化为0,以便后续在插入和查找时确认该槽为是否被占用。

    int hash_insert(HashTable *ht, char *key, void *value)
    {
        // check if we need to resize the hashtable
        resize_hash_table_if_needed(ht);
     
        int index = HASH_INDEX(ht, key);
     
        Bucket *org_bucket = ht->buckets[index];
        Bucket *tmp_bucket = org_bucket;
     
        // check if the key exits already
        while(tmp_bucket)
        {
            if(strcmp(key, tmp_bucket->key) == 0)
            {
                LOG_MSG("[update]	key: %s
    ", key);
                tmp_bucket->value = value;
     
                return SUCCESS;
            }
     
            tmp_bucket = tmp_bucket->next;
        }
     
        Bucket *bucket = (Bucket *)malloc(sizeof(Bucket));
     
        bucket->key   = key;
        bucket->value = value;
        bucket->next  = NULL;
     
        ht->elem_num += 1;
     
        if(org_bucket != NULL)
        {
            LOG_MSG("[collision]	index:%d key:%s
    ", index, key);
            bucket->next = org_bucket;
        }
     
        ht->buckets[index]= bucket;
     
        LOG_MSG("[insert]	index:%d key:%s	ht(num:%d)
    ",
            index, key, ht->elem_num);
     
        return SUCCESS;
    }

    上面这个哈希表的插入操作比较简单,简单的以key做哈希,找到元素应该存储的位置,并检查该位置是否已经有了内容, 如果发生碰撞则将新元素链接到原有元素链表头部。

    由于在插入过程中可能会导致哈希表的元素个数比较多,如果超过了哈希表的容量, 则说明肯定会出现碰撞,出现碰撞则会导致哈希表的性能下降,为此如果出现元素容量达到容量则需要进行扩容。 由于所有的key都进行了哈希,扩容后哈希表不能简单的扩容,而需要重新将原有已插入的预算插入到新的容器中。

    static void resize_hash_table_if_needed(HashTable *ht)
    {
        if(ht->size - ht->elem_num < 1)
        {
            hash_resize(ht);
        }
    }
     
    static int hash_resize(HashTable *ht)
    {
        // double the size
        int org_size = ht->size;
        ht->size = ht->size * 2;
        ht->elem_num = 0;
     
        LOG_MSG("[resize]	org size: %i	new size: %i
    ", org_size, ht->size);
     
        Bucket **buckets = (Bucket **)calloc(ht->size, sizeof(Bucket *));
     
        Bucket **org_buckets = ht->buckets;
        ht->buckets = buckets;
     
        int i = 0;
        for(i=0; i < org_size; ++i)
        {
            Bucket *cur = org_buckets[i];
            Bucket *tmp;
            while(cur)
            {
                // rehash: insert again
                hash_insert(ht, cur->key, cur->value);
     
                // free the org bucket, but not the element
                tmp = cur;
                cur = cur->next;
                free(tmp);
            }
        }
        free(org_buckets);
     
        LOG_MSG("[resize] done
    ");
     
        return SUCCESS;
    }

    哈希表的扩容首先申请一块新的内存,大小为原来的2倍,然后重新将元素插入到哈希表中, 读者会发现扩容的操作的代价为O(n),不过这个问题不大,因为只有在到达哈希表容量的时候才会进行。

    在查找时也使用插入同样的策略,找到元素所在的位置,如果存在元素, 则将该链表的所有元素的key和要查找的key依次对比, 直到找到一致的元素,否则说明该值没有匹配的内容。

    int hash_lookup(HashTable *ht, char *key, void **result)
    {
        int index = HASH_INDEX(ht, key);
        Bucket *bucket = ht->buckets[index];
     
        if(bucket == NULL) goto failed;
     
        while(bucket)
        {
            if(strcmp(bucket->key, key) == 0)
            { 
                LOG_MSG("[lookup]	 found %s	index:%i value: %p
    ",
                    key, index, bucket->value);
                *result = bucket->value;
     
                return SUCCESS;
            } 
     
            bucket = bucket->next;
        }
     
    failed:
        LOG_MSG("[lookup]	 key:%s	failed	
    ", key);
        return FAILED;
    }

    PHP中数组是基于哈希表实现的,依次给数组添加元素时,元素之间是有先后顺序的, 而这里的哈希表在物理位置上显然是接近平均分布的,这样是无法根据插入的先后顺序获取到这些元素的, 在PHP的实现中Bucket结构体还维护了另一个指针字段来维护元素之间的关系。 具体内容在后一小节PHP中的HashTable中进行详细说明。上面的例子就是PHP中实现的一个精简版。

  • 相关阅读:
    几种常用的曲线
    0188. Best Time to Buy and Sell Stock IV (H)
    0074. Search a 2D Matrix (M)
    0189. Rotate Array (E)
    0148. Sort List (M)
    0859. Buddy Strings (E)
    0316. Remove Duplicate Letters (M)
    0452. Minimum Number of Arrows to Burst Balloons (M)
    0449. Serialize and Deserialize BST (M)
    0704. Binary Search (E)
  • 原文地址:https://www.cnblogs.com/tangchuanyang/p/6186196.html
Copyright © 2011-2022 走看看