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)的主要内容,如果未能解决你的问题,请参考以下文章

C函数学习:GLib HashTable函数

C函数学习:GLib HashTable函数

C函数学习:GLib HashTable函数

[译]C语言实现一个简易的Hash table

建立简单的Hash table(哈希表)by C language

HashTable-哈希表/散列表