C语言手撕一个Hash表(HashTable)
Posted LyaJpunov
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言手撕一个Hash表(HashTable)相关的知识,希望对你有一定的参考价值。
什么是Hash Table
散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一种扩展,由数组演化而来。可以说,如果没有数组,就没有散列表。
散列函数
散列函数是将我们想插入的节点散列成一个数值的函数。它是一个函数。我们可以把它定义成 hash(key),要想找到一个不同的 key 对应的散列值都不一样的散列函数,几乎是不可能的。即便像业界著名的MD5、SHA、CRC等哈希算法,也无法完全避免这种散列冲突。而且,因为数组的存储空间有限,也会加大散列冲突的概率。
所以我们几乎无法找到一个完美的无冲突的散列函数,即便能找到,付出的时间成本、计算成本也是很大的,所以针对散列冲突问题,我们需要通过其他途径来解决。
散列冲突
开放寻址法
开放寻址法的核心思想是,如果出现了散列冲突,我们就重新探测一个空闲位置,将其插入。那如何重新探测新的位置呢?我先讲一个比较简单的探测方法,线性探测(Linear Probing)。当我们往散列表中插入数据时,如果某个数据经过散列函数散列之后,存储位置已经被占用了,我们就从当前位置开始,依次往后查找,看是否有空闲位置,直到找到为止。
链表法
链表法是一种更加常用的散列冲突解决办法,相比开放寻址法,它要简单很多。我们来看这个图,在散列表中,每个“桶(bucket)”或者“槽(slot)”会对应一条链表,所有散列值相同的元素我们都放到相同槽位对应的链表中。
HashMap 底层采用链表法来解决冲突。即使负载因子和散列函数设计得再合理,也免不了会出现拉链过长的情况,一旦出现拉链过长,则会严重影响 HashMap 的性能。
于是,在 JDK1.8 版本中,为了对 HashMap 做进一步优化,我们引入了红黑树。而当链表长度太长(默认超过 8)时,链表就转换为红黑树。我们可以利用红黑树快速增删改查的特点,提高 HashMap 的性能。当红黑树结点个数少于 8 个的时候,又会将红黑树转化为链表。因为在数据量较小的情况下,红黑树要维护平衡,比起链表来,性能上的优势并不明显。
装载因子
装载因子越大,说明散列表中的元素越多,空闲位置越少,散列冲突的概率就越大。不仅插入数据的过程要多次寻址或者拉很长的链,查找的过程也会因此变得很慢。
最大装载因子默认是 0.75,当 HashMap 中元素个数超过 0.75*capacity(capacity 表示散列表的容量)的时候,就会启动扩容,每次扩容都会扩容为原来的两倍大小。
代码
这里我们给出C语言的HashTable代码,我们使用的是链表法,而且也没有在链表过长的时候将其转化为红黑树,只是单纯的链表。
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
typedef struct Node
int key;
int val;
struct Node *next;
Node;
typedef struct HashMap
int size;
int count;
struct Node **hashmap;
HashMap;
// 定义哈希函数
unsigned int hash(int n)
return (unsigned int) n;
// 创建一个节点
Node *creatNode(int key, int val)
Node *node = (Node *) malloc(sizeof(Node));
node->key = key;
node->val = val;
node->next = NULL;
return node;
// 创建一个hash表
HashMap *createHashMap()
HashMap *H = (HashMap *) malloc(sizeof(HashMap));
H->size = 8;
H->count = 0;
H->hashmap = (Node **) calloc(H->size, sizeof(Node *));
return H;
// 扩容,以2倍的形式扩容
static void extend(HashMap *map)
int newsize = map->size * 2;
Node **newhashmap = (Node **) calloc(newsize, sizeof(Node *));
for (int i = 0; i < map->size; i++)
Node *node = map->hashmap[i];
Node *head1 = (Node *) malloc(sizeof(Node));
Node *head2 = (Node *) malloc(sizeof(Node));
Node *temp1 = head1;
Node *temp2 = head2;
while (node)
if (hash(node->key) % newsize != i)
temp2->next = node;
temp2 = temp2->next;
else
temp1->next = node;
temp1 = temp1->next;
node = node->next;
temp1->next = NULL;
temp2->next = NULL;
newhashmap[i] = head1->next;
newhashmap[i + map->size] = head2->next;
free(head1);
free(head2);
map->size = newsize;
free(map->hashmap);
map->hashmap = newhashmap;
// 插入某个结点
bool insert(HashMap *map, int key, int val)
int hash_key = hash(key) % map->size;
// 要插入的哈希值未产生碰撞
if (map->hashmap[hash_key] == NULL)
map->count++;
map->hashmap[hash_key] = creatNode(key, val);
else
Node *head = map->hashmap[hash_key];
if (head->key == key) return false;
while (head->next && head->next->key != key) head = head->next;
if (head->next == NULL)
map->count++;
head->next = creatNode(key, val);
else if (head->next->key == key) return false;
// 装载因子过高就开始扩容
if (map->count / map->size > 0.75) extend(map);
return true;
// 寻找某个结点
bool find(HashMap *map, int key, int *v)
int hash_key = hash(key) % map->size;
if (map->hashmap[hash_key] == NULL)
return false;
else
Node *head = map->hashmap[hash_key];
if (head->key == key)
*v = head->val;
return true;
while (head->next && head->next->key != key) head = head->next;
if (head->next == NULL) return false;
if (head->next->key == key)
*v = head->next->val;
return true;
return false;
// 删除某个结点
void delete(HashMap *map, int key)
int hash_key = hash(key) % map->size;
if (map->hashmap[hash_key] == NULL) return;
Node *head = map->hashmap[hash_key];
if (head->key == key)
map->hashmap[hash_key] = head->next;
map->count--;
free(head);
return;
while (head->next && head->next->key != key) head = head->next;
if (head->next == NULL) return;
if (head->next->key == key)
Node *temp = head->next;
head->next = head->next->next;
map->count--;
free(temp);
return;
int main()
HashMap *H = createHashMap();
for (int i = 0; i < 1024; i++)
insert(H, i, i);
printf("H size is %d\\n",H->size);
printf("H count is %d\\n",H->count);
delete(H, 0);
int v = 0;
if (find(H, 0, &v)) printf("%d\\n", v);
else printf("not find \\n");
if (find(H, 4, &v)) printf("%d\\n", v);
else printf("not find \\n");
return 0;
C函数学习:GLib HashTable函数
文章目录
参考链接:
1.源码
https://gitlab.gnome.org/GNOME/glib/
2.官网介绍
https://docs.gtk.org/glib/struct.HashTable.html
1 哈希表简介
哈希表是一种基于key-value进行访问的数据结构,可以加快查找的速度,因此在一些需要快速查找是否有重复元素的场景中,常常使用哈希表来实现。
2 Glib HashTable
Glib提供了很多方便的功能,封装了我们经常使用的链表、数据结构等内容。本文简单介绍下Glib中哈希表相关的函数,方便以后使用。
2.0 函数总览
函数 | 说明 |
---|---|
g_hash_table_new | 创建 |
g_hash_table_new_full | 创建 |
g_hash_table_new_similar | 创建 |
g_hash_table_destroy | 删除全部key |
g_hash_table_remove | 删除指定key |
g_hash_table_remove_all | 删除全部key |
g_hash_table_steal | 从GHashTable中删除指定的key |
g_hash_table_steal_all | 从GHashTable中删除所有的key |
g_hash_table_steal_extended | 从GHashTable中删除指定的key |
g_hash_table_add | 添加 |
g_hash_table_replace | 修改 |
g_hash_table_insert | 插入 |
g_hash_table_lookup | 查找key对应的value |
g_hash_table_lookup_extended | 查找key对应的value |
g_hash_table_find | 自定义查找规则进行查找 |
g_hash_table_get_keys | 查找哈希表中的全部key |
g_hash_table_get_keys_as_array | 以数组的形式检索哈希表中的每个key |
g_hash_table_get_values | 查找哈希表中的全部value |
g_hash_table_foreach | 循环遍历哈希表中的每一个key/value |
g_hash_table_foreach_remove | 循环遍历哈希表中的每一个key/value,并删除符合func的key/value |
g_hash_table_foreach_steal | 循环遍历哈希表中的每一个key/value,并删除符合func的key/value |
g_hash_table_contains | 检查key是否存在于哈希表中 |
g_hash_table_size | 哈希表中的key/value数量 |
2.1 创建哈希表
glib中提供了三个不同的函数用于创建哈希表:g_hash_table_new、g_hash_table_new_full、g_hash_table_new_similar。
2.1.1 g_hash_table_new
GHashTable *
g_hash_table_new (GHashFunc hash_func,
GEqualFunc key_equal_func);
创建一个引用计数为1的新HashTable。
hash_func将key值转换为key_hash,一般使用g_str_hash函数来作为入参;
key_equal_func用来比较key值,在插入,删除等操作时会调用此函数,一般使用g_str_equal函数来作为入参。
2.1.2 g_hash_table_new_full
GHashTable *
g_hash_table_new_full (GHashFunc hash_func,
GEqualFunc key_equal_func,
GDestroyNotify key_destroy_func,
GDestroyNotify value_destroy_func);
创建一个引用计数为1的新HashTable,并且允许指定函数来释放当GHashTable中删除条目时调用的key和value分配的内存。
hash_func和key_equal_func的作用在上面已经介绍,不再赘述;
key_destroy_func 是key值内存的释放,一般使用g_free函数来作为入参;
value_destroy_func 是value值内存的释放。
2.1.3 g_hash_table_new_similar
GHashTable *
g_hash_table_new_similar (GHashTable *other_hash_table);
创建一个引用计数为1的新HashTable,其中hash_func、key_equal_func、key_destroy_func、 value_destroy_func等函数来自于other_hash_table。
2.2 删除哈希表
删除哈希表提供了几个不同的函数:g_hash_table_destroy、g_hash_table_remove、g_hash_table_remove_all。
2.2.1 g_hash_table_destroy
void
g_hash_table_destroy (GHashTable *hash_table);
销毁GHashTable中的所有的key和value。如果key和value是动态分配的,则需要先释放他们或者此函数会使用在创建时调用g_hash_table_new_full函数时候的key_destroy_func和value_destroy_func入参函数来自动销毁。
2.2.2 g_hash_table_remove
gboolean
g_hash_table_remove (GHashTable *hash_table,
gconstpointer key);
从GHashTable中删除指定的key。如果key是动态分配的,则需要先释放内存或者此函数会使用在创建时调用g_hash_table_new_full函数时候的key_destroy_func和value_destroy_func入参函数来自动销毁。
2.2.3 g_hash_table_remove_all
void
g_hash_table_remove_all (GHashTable *hash_table);
从GHashTable中删除所有的key。如果key是动态分配的,则需要先释放内存或者此函数会使用在创建时调用g_hash_table_new_full函数时候的key_destroy_func和value_destroy_func入参函数来自动销毁。
2.2.4 g_hash_table_steal
gboolean
g_hash_table_steal (GHashTable *hash_table,
gconstpointer key);
从GHashTable中删除指定的key。但是不会调用函数释放key/value内存。
2.2.5 g_hash_table_steal_all
void
g_hash_table_steal_all (GHashTable *hash_table);
从GHashTable中删除所有的key。但是不会调用函数释放key/value内存。
2.2.6 g_hash_table_steal_extended
gboolean
g_hash_table_steal_extended (GHashTable *hash_table,
gconstpointer lookup_key,
gpointer *stolen_key,
gpointer *stolen_value);
从GHashTable中删除指定的key。但是不会调用函数释放key/value内存。同时会输出stolen_key、stolen_value表示查找到的key/value;
2.3 添加
添加哈希表提供了几个不同的函数:g_hash_table_add、g_hash_table_replace、g_hash_table_insert。
2.3.1 g_hash_table_add
gboolean
g_hash_table_add (GHashTable *hash_table,
gpointer key);
添加key,如果此key已经存在于哈希表中,则释放旧的key,使用新的key来覆盖。
2.3.2 g_hash_table_replace
gboolean
g_hash_table_replace (GHashTable *hash_table,
gpointer key,
gpointer value);
插入一个新的key和value到GHashTable中,如果有旧的key存在GHashTable中,则会被新的key替代,而旧的key和value则会被自动销毁,此函数会使用在创建时调用g_hash_table_new_full函数时候的key_destroy_func和value_destroy_func入参函数来自动销毁。
2.3.3 g_hash_table_insert
gboolean
g_hash_table_insert (GHashTable *hash_table,
gpointer key,
gpointer value);
插入一个新的key和value到GHashTable中,如果有旧的key存在,则value值会被覆盖为新的value值。
2.4 查找
glib中提供了几个不同的函数用于查找:g_hash_table_lookup_extended、g_hash_table_lookup、g_hash_table_find。
2.4.1 g_hash_table_lookup
gpointer
g_hash_table_lookup (GHashTable *hash_table,
gconstpointer key);
在 GHashTable 中查找一个键。此函数无法区分不存在的key和key存在,但是vlaue为空这种情况,因为这两种情况返回值都为NULL。
2.4.2 g_hash_table_lookup_extended
gboolean
g_hash_table_lookup_extended (GHashTable *hash_table,
gconstpointer lookup_key,
gpointer *orig_key,
gpointer *value);
orig_key为输出,即查找到的key;
value为输出,即查找到的value;
返回值为TRUE表示已经找到原始的key。
2.4.3 g_hash_table_find
gpointer
g_hash_table_find (GHashTable *hash_table,
GHRFunc predicate,
gpointer user_data);
根据用于自定义的查找规则函数predicate来进行查找,如果predicate返回TRUE,则表示查找到对应的key,则g_hash_table_find就会返回对应的value;如果predicate一直返回FALSE,则表示一直没有查找到。
2.4.4 g_hash_table_get_keys
GList *
g_hash_table_get_keys (GHashTable *hash_table);
查找哈希表中的全部key。
2.4.5 g_hash_table_get_keys_as_array
gpointer *
g_hash_table_get_keys_as_array (GHashTable *hash_table,
guint *length);
以数组的形式检索哈希表中的每个key,length表示数组的长度。
2.4.6 g_hash_table_get_values
GList *
g_hash_table_get_values (GHashTable *hash_table);
查找哈希表中的全部key对应的value。
2.5 遍历
2.5.1 g_hash_table_foreach
void
g_hash_table_foreach (GHashTable *hash_table,
GHFunc func,
gpointer user_data);
循环遍历哈希表中的每一个key/value。
2.5.2 g_hash_table_foreach_remove
guint
g_hash_table_foreach_remove (GHashTable *hash_table,
GHRFunc func,
gpointer user_data);
循环遍历哈希表中的每一个key/value,并删除符合func的key/value。符合即func返回TRUE,此函数再删除符合的key/value会使用在创建时调用g_hash_table_new_full函数时候的key_destroy_func和value_destroy_func入参函数来自动销毁。
2.5.3 g_hash_table_foreach_steal
guint
g_hash_table_foreach_steal (GHashTable *hash_table,
GHRFunc func,
gpointer user_data);
循环遍历哈希表中的每一个key/value,并删除符合func的key/value。符合即func返回TRUE,此函数不会调用函数释放key/value。
3 示例
一个简单的示例程序。
#include <stdio.h>
#include <glib.h>
#include <stdlib.h>
#include <string.h>
static GHashTable *gpHash;
void iterator(gpointer key, gpointer value ,gpointer user_data)
printf("%s, key:%s, value:%s", user_data, key, value);
int main()
//创建
gpHash = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, NULL);
//插入
g_hash_table_insert(gpHash, "1","test1");
g_hash_table_insert(gpHash, "2","test2");
g_hash_table_insert(gpHash, "3","test3");
g_hash_table_insert(gpHash, "4","test4");
g_hash_table_insert(gpHash, "5","test5");
//获取大小
printf("hash num:%d\\n", ,g_hash_table_size(gpHash);
//查找指定key对应的value
g_printf("key:%s, value:%s\\n", "1", g_hash_table_lookup(gpHash, "1");
//删除指定key
gboolean found = g_hash_table_remove(gpHash, "1");
g_printf("del:%s, state:%d\\n", "1", found);
//遍历全部的key
g_hash_table_foreach(gpHash, (GHFunc)iterator, "test");
//删除全部key
g_hash_table_destroy(gpHash);
return 0;
以上是关于C语言手撕一个Hash表(HashTable)的主要内容,如果未能解决你的问题,请参考以下文章