C语言编程技巧-实现一个hash表

实现hash表

一、如何理解hash?

  1. C语言中,如果数据被存放到数组中 。可以使用 arr[3] 这种方法直接访问arr数组的第四个元素。

  2. 这是一种非常快的访问方法。使用下标访问。

  3. 把数组理解成hash表,而这个3 理解成key值。那么可以得到:

    int hash_arr_get_value(int *arr,int key)
    {
        return arr[key];
    }
    
  4. 那如果这个key值不是一个数字呢?比如说是一个字符串。我需要通过一个字符串,得到对应的值。比如,我需要通过名字得到手机号码:

    char *hash_arr_get_number(char **arr,char *str_key)
    {
        /*需要先把number转成一个key值。*/
        unsigned int key = hash(str_key);
        /*然后访问真正的数据*/
        return arr[key];
    }
    
  5. 4中的 arr,就是一个hash表。而把key从别的数据转成一个整数下标的函数,就是hash函数。

  6. 使用这种hash表存储,当要查询数据的时候,不用每次遍历查找,只需要通过固定计算,使用下标查找到想要的结果。算法时间复杂度为O(1).

二、使用C语言实现一个hash

  1. 具体代码参考这里:hash表实现;

  2. 简单的讲解:

    1. 我写的这个hash依赖了 value 的模块。这是为了方便管理hash表的key和值。如果写死了key是string,value也是string,后续的扩展就变得十分困难。

      /*
      将value抽象一下,以后不管是字符串,还是函数指针,还是别的什么值,都可以通过修改抽象的成员解决。
      */
      typedef int (*on_destory_cb)(void *);
      typedef struct value_t
      {
          void *p;
          int size;
          on_destory_cb on_destroy;
      }value_t;
      
      
    2. value_create 和 value_destroy是对应的。如果创建了,就要销毁。因为C语言需要考虑分配空间和释放空间。

    3. hash表操作函数。hash表也是一种存储方式。也是需要增删改查的操作。

      /**
       * 创建一个hash表。输入 表的大小和hash函数。对于不同的场景,使用不同的hash函数。
      */
      fv_hash_t *hash_create(int size,hash_calculate_cb hash_fn,hash_cmp_cb hash_cmp);
      
      /**
       * 向hash表中添加元素。如果key值已经存在,会修改原来的元素。
      */
      int hash_update(fv_hash_t *hash,value_t *key,value_t *data);
      
      /**
       * 从hash表中删除key值对应的元素。如果元素不存在,返回-1.删除失败返回-1.
      */
      int hash_remove(fv_hash_t * hash,value_t *key);
      
      /**
       * 获取hash表中key值对应的data数据的指针。
      */
      value_t *hash_get(fv_hash_t *hash,value_t *key);
      
      /**
       * 遍历hash表中的元素。key值和data的值。还有参数。
      */
      int hash_foreach(fv_hash_t *hash,hash_visit_cb on_visit,void *param);
      
      /**
       * 计算hash表中有多少元素
      */
      int hash_get_size(fv_hash_t *hash);
      
      /**
       * 销毁整个hash表。
      */
      int hash_destory(fv_hash_t *hash);
      

三、如何解决hash冲突

  1. 使用外部拉链法解决hash冲突。也就是,如果新插入一个数据到A槽,A槽已经被占用了。怎么办?就弄个链表,把新的数据插入到原来数据所在的来链表中。

  2. 简单看看hash_update函数

    /**
     * 向hash表中添加元素。如果key值已经存在,会修改原来的元素。
     * 如果key值已经存在,就是用头插法,插到最前面。这样用户检查的时候永远得到的是最前面的数据。
     * 这里的实现需要完善。因为如果用户一直插一样的值,会浪费内存。我不想加删除的代码,是因为担心一直循环查找会不会降低性能。
    */
    int hash_update(fv_hash_t *hash,value_t *key,value_t *data)
    {
        return_value_if_fail(hash != NULL && key != NULL && data != NULL,-1);
        fv_hash_elem_t *hash_elem = NULL;
        /*hash函数非空*/
        if(hash->hash_fn)
        {
            /*计算key值*/
            unsigned int key_index = hash->hash_fn(key->p)%hash->capacity;
            /*创建 fv_hash_elem_t 对象。*/
            hash_elem = hash_elem_create(key,data);
            return_value_if_fail(hash_elem,-1);
            /*如果当前hash槽已经被占用*/
            if(hash->table[key_index] != NULL)
            {
                hash_elem->next = hash->table[key_index];
                hash->table[key_index] = hash_elem;
            }
            else 
            {
                hash->table[key_index] = hash_elem;
            }
            hash->size++;
        }
        
        return 0;
    }
    

四、如何保证hash的性能

  1. 创建大小合理的散列表。

    1. 负载 因子:是当前插入到表的元素个数和可用槽的总数的比,得到的结果。

      a = n/table_size

    2. 如果负载因子变大,性能会下降。如果小于0.2,再让它变小,对于性能的影响特别小。

    3. 我觉得可能0.5-1会好些吧。

  2. 确保散列表的槽数是一个素数。

  3. 需要使用代表性数据测试。最好只使用一个除法运算,就是最后n%table_size的模运算。

  4. 考虑冲突,在可能的地方使用外部拉链法。

五、使用

  1. 参考 示例代码
  2. 使用场景:
    1. 频繁的查找,而且对性能有要求的地方。需要使用hash表。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值