当前位置:   article > 正文

哈希表总结-C语言版_c语言自带的hash函数

c语言自带的hash函数

目录

 

1、哈希表的原理

2、自己实现的hash表--C语言版

3、C语言开源项目uthash.h 中的hash接口 使用指南

3.1 uthash.h头文件说明

3.2 常见的uthash.h接口以及使用方法

4、实践应用

参考资料:

1、哈希表的原理

哈希表的关键思想是使用哈希函数将键映射到存储桶。更确切地说,

当我们插入一个新的键时,哈希函数将决定该键应该分配到哪个桶中,并将该键存储在相应的桶中;
当我们想要搜索一个键时,哈希表将使用相同的哈希函数来查找对应的桶,并只在特定的桶中进行搜索。

哈希表常用来搜索查找需要的元素, 哈希表的查找时间复杂度通常很低, 这里没有深入研究他的复杂度如何计算。

2、自己实现的hash表--C语言版

实现hash这种数据结构, 主要考虑两点:

(1)哈希函数:能够将集合中任意可能的元素映射到一个固定范围的整数值,并将该元素存储到整数值对应的地址上。
(2)冲突处理:由于不同元素可能映射到相同的整数值,因此需要在整数值出现「冲突」时,需要进行冲突处理。总的来说,有以下几种策略解决冲突:

本文使用链表的方式存储相同hash哈希值的元素, 因此代码中有链表的常见操作, 如链表创建、插入、删除、查找。

基于链表的操作, 完成了hash表的 创建、查找、插入、删除等操作。

代码如下:

  1. typedef struct _ListNode_{
  2. int val;
  3. struct _ListNode_ *next;
  4. } stListNode;
  5. typedef struct {
  6. stListNode *data;
  7. } MyHashSet;
  8. /** Initialize your data structure here. */
  9. //参考官方题解-- 用链表存储每一个key对应的不同的value,也就是解决冲突
  10. /* 先完成链表的基本操作 */
  11. // 在头结点之后插入一个新的节点
  12. void ListPush(stListNode *head, int key)
  13. {
  14. stListNode *tmp = (stListNode *)malloc(sizeof(stListNode));
  15. tmp->val = key;
  16. tmp->next = head->next;
  17. head->next = tmp;
  18. }
  19. void ListDelete(stListNode *head, int key)
  20. {
  21. stListNode *it = head;
  22. stListNode *tmp = NULL;
  23. while(it->next)
  24. {
  25. if(key == it->next->val)
  26. {
  27. tmp = it->next;
  28. it->next = tmp->next;
  29. free(tmp);
  30. break;
  31. }
  32. it = it->next;
  33. }
  34. }
  35. //查找链表中是否存在key, 存在返回1, 不存在返回0
  36. int ListContains(stListNode *head, int x)
  37. {
  38. stListNode *it = head;
  39. //这里必须是it->next开头, 不然的话就会在头一次调用时判断失误, 具体原因不清楚???
  40. while(it->next)
  41. {
  42. if(it->next->val == x)
  43. {
  44. return 1;
  45. }
  46. it = it->next;
  47. }
  48. return 0;
  49. }
  50. //删除链表的所有节点
  51. void listFree(stListNode *head)
  52. {
  53. while(head->next)
  54. {
  55. stListNode *tmp = head->next;
  56. head->next = tmp->next;
  57. free(tmp);
  58. }
  59. }
  60. const int base = 769;
  61. int hash(int key)
  62. {
  63. return key % base;
  64. }
  65. //创建一个hash表, 里面可以存储base个key键值桶
  66. MyHashSet* myHashSetCreate(void)
  67. {
  68. MyHashSet *rethash = (MyHashSet *)malloc(sizeof(MyHashSet));
  69. rethash->data = (stListNode *)malloc(sizeof(stListNode) * base);
  70. for(int i=0; i<base; i++)
  71. {
  72. rethash->data[i].val = 0;
  73. rethash->data[i].next = NULL;
  74. }
  75. return rethash;
  76. }
  77. //插入时先判断是否存在, 不存在时再插入
  78. void myHashSetAdd(MyHashSet* obj, int key)
  79. {
  80. int h = hash(key);
  81. if( !ListContains(&(obj->data[h]), key) )
  82. {
  83. ListPush(&(obj->data[h]), key);
  84. }
  85. }
  86. void myHashSetRemove(MyHashSet* obj, int key)
  87. {
  88. int h = hash(key);
  89. ListDelete(&(obj->data[h]), key);
  90. }
  91. /** Returns true if this set contains the specified element */
  92. bool myHashSetContains(MyHashSet* obj, int key)
  93. {
  94. //找到键值桶, 在链表中查找是否存在
  95. int h = hash(key);
  96. return ListContains (&(obj->data[h]), key );
  97. }
  98. //删除base所有桶的list链表
  99. void myHashSetFree(MyHashSet* obj)
  100. {
  101. for(int i=0; i<base; i++)
  102. {
  103. listFree(&(obj->data[i]));
  104. }
  105. free(obj->data);
  106. }
  107. /**
  108. * Your MyHashSet struct will be instantiated and called as such:
  109. * MyHashSet* obj = myHashSetCreate();
  110. * myHashSetAdd(obj, key);
  111. * myHashSetRemove(obj, key);
  112. * bool param_3 = myHashSetContains(obj, key);
  113. * myHashSetFree(obj);
  114. */

有关hash集合和hash表的设计, 详见

LC706-设计哈希映射

LC705-设计哈希集合

3、C语言开源项目uthash.h 中的hash接口 使用指南

3.1 uthash.h头文件说明

       uthash.h 是C语言的开源项目,它实现了常见的hash操作函数,例如查找、插入、删除等。该套开源代码采用宏的方式实现hash函数的相关功能,支持C语言的任意数据结构最为key值,甚至可以采用多个值作为key,无论是自定义的struct还是基本数据类型,需要注意的是不同类型的key其操作接口方式略有不通。     

       使用uthash代码时只需要包含头文件"uthash.h"即可。由于该代码采用宏的方式实现,所有的实现代码都在uthash.h文件中,因此只需要在自己的代码中包含该头文件即可。可以通过下面两种方式获取源代码:

 

3.2 常见的uthash.h接口以及使用方法

能够实现哪些功能?

hash接口查找表
接口含义使用方法&功能备注
UT_hash_handle  hhhh是内部使用的hash处理句柄

使用时只需要在结构体中定义一个字段,不需要对该变量赋值

  1. typedef struct my_struct
  2. {
  3. int key; /* key */
  4. int val;
  5. UT_hash_handle hh;
  6. }stHashTable;

 

 
HASH_FIND_INT

查找键值接口,对应的是int 整型的键值key

HASH_FIND_INT(head, key_ptr, item_ptr)

键值查找,

 

  1. void find(int ikey)
  2. {
  3. struct my_struct *s;  
  4. HASH_FIND_INT(g_users, &ikey, s );  
  5. return s;  

入参1一般是一个全局的hash表

入参2一般是待查找的键值的地址,入参3是输出参数,将查找得到的结果赋值到这里

 
HASH_ADD_INT

插入键值的接口,对应的是int 整型的键值key

 

HASH_ADD_INT(head, keyfield_name, item_ptr)

键值插入到hash表中,

 HASH_ADD_INT(g_users, key, s );  

/* 这里必须明确告诉插入函数,自己定义的hash结构体中键变量的名字 */

插入之前要想find查找一下该元素是否已经存在
HASH_DEL

删除hash键值的接口

HASH_DEL(head, item_ptr)

需要告诉该接口要释放哪个hash表(这里是g_users)里的哪个节点(这里是s), 删除之前可以先通过键值查找一下对应的hash元素。

  1. void delete_user(int ikey) 
  2. {  
  3.     struct my_struct *= NULL;  
  4.     HASH_FIND_INT(g_users, &ikey, s);  
  5.     if (s!=NULL) {  
  6.       HASH_DEL(g_users, s);   
  7.       free(s);              
  8.     }  
  9. }  

 

删除所有节点,可以调用遍历节点的接口,遍历到之后依次删除

  1. void delete_all() 
  2. {  
  3.    struct my_struct *current_user, *tmp;  
  4. HASH_ITER(hh, users, current_user, tmp) 
  5. {  
  6.      HASH_DEL(g_users,current_user);    
  7. free(current_user);              
  8.    }  
  9. }  

 

HASH_FIND_PTR

使用地址void *类型作为键值时的查找接口

HASH_FIND_PTR(head, key_ptr, item_ptr)

 

 
  1. typedef struct _stHashTable_
  2. {
  3. struct ListNode *key;
  4. UT_hash_handle hh;
  5. }stHashTable;

可以用在结构体指针作为key的时候, 比如链表节点结构体作为key

 
HASH_ADD_PTR

使用地址void *类型作为键值时的插入接口

HASH_ADD_PTR(head, keyfield_name, item_ptr)

可以用在结构体指针作为key的时候, 比如链表节点结构体作为key 
HASH_FIND_STR

查找接口, key键值是一个数组

HASH_FIND_STR(head, key_ptr, item_ptr)

  1. typedef struct _HashSet_{
  2. char str[MAX_SIZE];
  3. ... ...
  4. UT_hash_handle hh;
  5. }stHashSet;

HASH_FIND_STR(g_strHash, s, tmp);   s是数组的首地址

具体可以参考

LC49--字符数组的哈希查找法题目

HASH_ADD_STR

插入接口, key键值是一个数组

HASH_ADD_STR(head, keyfield_name, item_ptr)

HASH_ADD_STR(g_strHash, str, it);   str是结构体定义中的数字名字

 
HASH_COUNT

统计hash表中的已经存在的元素数

HASH_COUNT(head)

  1. unsigned int num_users;
  2. num_users = HASH_COUNT(g_users);

 

 
HASH_ITER

遍历所有hash元素

HASH_ITER(hh_name, head, item_ptr, tmp_item_ptr)

struct my_struct *current_user, *tmp;

HASH_ITER(hh, users, current_user, tmp){

  .......

}

遍历得到的元素存放在current_user变量中

用于依次删除元素,或者打印所有元素

 
HASH_SORT

哈希排序

HASH_SORT(head, cmp)

  
HASH_FIND

通用查找接口

HASH_FIND(hh_name, head, key_ptr, key_len, item_ptr)

  
HASH_ADD

通用添加接口

HASH_ADD(hh_name, head, keyfield_name, key_len, item_ptr)

  

4、实践应用

看一道LeetCode上的典型题目, 求两个数组的交集。  链接  LC349. 两个数组的交集

  1. /**
  2. * Note: The returned array must be malloced, assume caller calls free().
  3. */
  4. //哈希集合法----这次写的封装接口, 把全局的hash表当做入参了
  5. typedef struct _HashSet_{
  6. int key;
  7. UT_hash_handle hh;
  8. }stHashSet;
  9. stHashSet *find(stHashSet** hashtable, int ikey) {
  10. stHashSet* tmp;
  11. HASH_FIND_INT(*hashtable, &ikey, tmp);
  12. return tmp;
  13. }
  14. void insert(stHashSet** hashtable, int ikey)
  15. {
  16. stHashSet* tmp = find(hashtable, ikey);
  17. if (tmp != NULL) return;
  18. tmp = malloc(sizeof(stHashSet));
  19. tmp->key = ikey;
  20. HASH_ADD_INT(*hashtable, key, tmp);
  21. }
  22. int *func(stHashSet** hashSet1, stHashSet** hashSet2, int* returnSize)
  23. {
  24. if(HASH_COUNT(*hashSet1) > HASH_COUNT(*hashSet2))
  25. {
  26. func(hashSet2, hashSet1, returnSize);
  27. }
  28. //储存结果的地方申请内存
  29. int *retArr = (int *)malloc(sizeof(int)*HASH_COUNT(*hashSet2));
  30. *returnSize = 0;
  31. //遍历元素少的那个hash表, 在元素多的表中查找是否存在相同的key
  32. stHashSet *tmp, *it;
  33. HASH_ITER(hh, *hashSet1, tmp, it)
  34. {
  35. if( find(hashSet2, tmp->key) )
  36. {
  37. retArr[*returnSize] = tmp->key;
  38. (*returnSize)++;
  39. }
  40. }
  41. return retArr;
  42. }
  43. int* intersection(int* nums1, int nums1Size, int* nums2, int nums2Size, int* returnSize)
  44. {
  45. int i = 0;
  46. stHashSet *hSet1 = NULL;
  47. stHashSet *hSet2 = NULL;
  48. /*插入元素、构造两个数组的hash集合*/
  49. for(i=0; i<nums1Size; i++)
  50. {
  51. insert(&hSet1, nums1[i]);
  52. }
  53. for(i=0; i<nums2Size; i++)
  54. {
  55. insert(&hSet2, nums2[i]);
  56. }
  57. return func(&hSet1, &hSet2, returnSize);
  58. }

参考资料:

LeetCode链接:LeetCode哈希表book
c开源hash项目 uthash的用法总结

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/449861
推荐阅读
相关标签
  

闽ICP备14008679号