【C语言】动手写一个哈希表

引言

近来无聊,决定动手写点程序练练手,所以从最基础的哈希表数据结构开始,全程参考的此处的GitHub项目
在这里插入图片描述

环境

  • Window10 、nodepad++编辑器 、MinGW 编译器

第一次尝试搭建极简的C语言开发环境(对于编程小白不太友好,不建议),网上教程较多,不赘述,比如
在这里插入图片描述

介绍

哈希表

哈希冲突是不可避免的,常用的解决方法有两种开放地址法、链表法
本文基于开放地址法,开放地址法中有三种方式来寻找其他的位置,分别是**「线性探测」、「二次探测」、「再哈希法」**。本文采用线性探测法,即若插入位置已经有值,则向下顺序寻找空位置进行插入。

哈希表应该具有的API接口

  • search(a, k):从哈希表a返回与 key 关联的值,或者如果该键不存在返回NULL。
  • insert(a, k, v):向哈希结构a里面插入一个键为k,值为v的元素
  • delete(a, k):删除键为k的元素,如果 不存在,则不执行任何操作。

构建数据结构

构建每个元素和和哈希表的数据结构

typedef struct {
    char* key;
    char* value;
} ht_item;
typedef struct {
    int size;
    int count;
    ht_item** items;
} ht_hash_table;

初始化和删除

初始化元素:malloc申请新元素的空间,并将key和value对应赋值。

#include <stdlib.h>
#include <string.h>
static ht_item* ht_new_item(const char* k, const char* v) {
    ht_item* i = malloc(sizeof(ht_item));
    i->key = strdup(k);
    i->value = strdup(v);
    return i;
}

初始化哈希表:malloc申请哈希表的空间,calloc申请存放元素地址的空间(与malloc不同的是申请的区域为初始化为0),并将size和count对应赋值。

ht_hash_table* new_ht() {
    ht_hash_table* h = malloc(sizeof(ht_hash_table)) ;
	h->size = 53;
	h->count = 0;
	h->items = calloc((size_t)h->size, sizeof(ht_item*))
	return h;
}

删除:释放掉申请的内存,防止内存泄漏。注意释放的先后顺序,i和h最后才释放

static void ht_del_item(ht_item* i) {
    free(i->key);
    free(i->value);
    free(i);
}
void ht_del_hash_table(ht_hash_table* h)
{
    for(int i = 0;i <h->size;i++)
	{
		if(h->items[i] != NULL)
			ht_delete_item(h->items[i]);
	}
	free(h->items);
	free(h);
}

使用static关键字修饰的静态函数不能被其它文件调用,作用于仅限于本文件
程序其中用到的基础C语言的库函数:

头文件:#include <stdlib.h>
定义函数:void* malloc (size_t size);
参数说明:size 为需要分配的内存空间的大小,以字节(Byte)计。
函数说明:malloc() 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。如果希望在分配内存的同时进行初始化,请使用 calloc() 函数。
返回值:分配成功返回指向该内存的地址,失败则返回 NULL

头文件:#include <string.h>
定义函数:char * strdup(const char *s);
函数说明:strdup()会先用maolloc()配置与参数s 字符串相同的空间大小,然后将参数s 字符串的内容复制到该内存地址,然后把该地址返回。该地址最后可以利用free()来释放。
返回值:返回一字符串指针,该指针指向复制后的新字符串地址。若返回NULL 表示内存不足。

定义函数:void* calloc (size_t num, size_t size);
函数说明:calloc() 在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是0。
返回值:分配成功返回指向该内存的地址,失败则返回 NULL。

哈希函数

我们将使用一个通用字符串哈希函数
此哈希函数有两个步骤:

  • 将字符串转换为大整数
  • 通过取其余数将整数的大小减小到固定范围mod m
    该变量应为大于字母表大小的质数。我们正在对 ASCII 字符串进行哈希处理,其字母大小为 128,因此我们应该选择一个大于此值的素数a

哈希表的大小为何是素数?

static int ht_hash(const char* s, const int a, const int m)
 {
    long hash = 0;
    const int len_s = strlen(s);
    for (int i = 0; i < len_s; i++) {
        hash += (long)pow(a, len_s - (i+1)) * s[i];
        hash = hash % m;
    }
    return (int)hash;
}

实现接口函数:插入/查询/删除

  • 虽然代码不长,但还是调试了很久,一不小心就访问到空指针。
  • 因为采用线性探测法,所示插入时当索引位置非空,就顺序向下一个位置寻找。
    搜索时,除了判定非NULL还需要比较键值,因为插入的位置是不确定的,需顺序向下寻找。删除同理。
  • 插入和删除时要注意count的判别和修改。
int insert(ht_hash_table* h, const char* k, const char* v)
{
	ht_item* i = ht_new_item(k,v);
	int index = ht_hash(k,151,53);
	if(h->count >= h->size)
		return -1;
	h->count++;
	while(1)
	{
		if((h->items[index])==NULL)
		{
			h->items[index] = i;
			return index;
		}
		index++;
		if(index == h->size)
			index = 0;
	}
}
char* search(ht_hash_table* h, const char* k)
{
	int index = ht_hash(k,151,53);
	for (int i = 0; i < h->size;i++)
	{	
		if(h->items[index]!=NULL)
			if(strcmp(h->items[index]->key,k)==0)
				return h->items[index]->value;
		index++;
		if(index == h->size)
			index = 0;		
	}
	return NULL;	
}
void delete(ht_hash_table* h, const char* k)
{
	int index = ht_hash(k,151,53);
	for (int i = 0; i < h->size;i++)
	{	
		if(h->items[index]!=NULL)
			if(strcmp(h->items[index]->key,k) == 0)
				{
					ht_del_item(h->items[index]);
					h->items[index] = NULL;
					h->count--;
					return ;
				}
		index++;
		if(index == h->size)
			index = 0;
	}
	return ;	
}

主函数

int main(void)
{
	ht_hash_table* ht=new_ht();
	insert(ht,"niuniu","hahaha");
	insert(ht,"shuoshuo","xixixi");
	char* s = search(ht,"shuoshuo");
	if(s==NULL)
		printf("查找失败  %d\n",s);
	else
		printf(s);
	
	delete(ht,"shuoshuo");
	char* c = search(ht,"shuoshuo");
	if(c==NULL)
		printf("查找失败  %d\n",c);
	else
		printf(c);
	
	ht_del_hash_table(ht);
	
	return 0;
}

自动修改桶的大小

未完待续。。。


完整代码

#include <stdlib.h>
#include <string.h>

#include "hash_table.h"
void ht_delete_item(ht_hash_table* h){}
static ht_item* ht_new_item(const char* k, const char* v) 
{
    ht_item* i = malloc(sizeof(ht_item));
    i->key = strdup(k);
    i->value = strdup(v);
    return i;
}
static void ht_del_item(ht_item* i)
 {
    free(i->key);
    free(i->value);
	free(i);
}
ht_hash_table* new_ht() 
{
    ht_hash_table* h = malloc(sizeof(ht_hash_table)) ;
	h->size = 53;
	h->count = 0;
	h->items = calloc((size_t)h->size, sizeof(ht_item*));
	return h;
}
void ht_del_hash_table(ht_hash_table* h)
{
    for(int i = 0;i <h->size;i++)
	{
		if(h->items[i] != NULL)
			ht_delete_item(h->items[i]);
	}
	free(h->items);
	free(h);
}
static int ht_hash(const char* s, const int a, const int m)
 {
    long hash = 0;
    const int len_s = strlen(s);
    for (int i = 0; i < len_s; i++) {
        hash += (long)pow(a, len_s - (i+1)) * s[i];
        hash = hash % m;
    }
    return (int)hash;
}
int insert(ht_hash_table* h, const char* k, const char* v)
{
	ht_item* i = ht_new_item(k,v);
	int index = ht_hash(k,151,53);
	if(h->count >= h->size)
		return -1;
	h->count++;
	while(1)
	{
		if((h->items[index])==NULL)
		{
			h->items[index] = i;
			return index;
		}
		index++;
		if(index == h->size)
			index = 0;
	}
}
char* search(ht_hash_table* h, const char* k)
{
	int index = ht_hash(k,151,53);
	for (int i = 0; i < h->size;i++)
	{	
		if(h->items[index]!=NULL)
			if(strcmp(h->items[index]->key,k)==0)
				return h->items[index]->value;
		index++;
		if(index == h->size)
			index = 0;
		
	}
	return NULL;
	
}
void delete(ht_hash_table* h, const char* k)
{
	int index = ht_hash(k,151,53);
	for (int i = 0; i < h->size;i++)
	{	
		if(h->items[index]!=NULL)
			if(strcmp(h->items[index]->key,k) == 0)
				{
					ht_del_item(h->items[index]);
					h->items[index] = NULL;
					h->count--;
					return ;
				}
		index++;
		if(index == h->size)
			index = 0;
	}
	return ;	
}
int main(void)
{
	ht_hash_table* ht=new_ht();
	insert(ht,"niuniu","hahaha");
	insert(ht,"shuoshuo","xixixi");
	char* s = search(ht,"shuoshuo");
	if(s==NULL)
		printf("查找失败  %d\n",s);
	else
		printf(s);
	
	delete(ht,"shuoshuo");
	char* c = search(ht,"shuoshuo");
	if(c==NULL)
		printf("查找失败  %d\n",c);
	else
		printf(c);
	
	ht_del_hash_table(ht);
	
	return 0;
}
  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个简单的C语言实现哈希表(链地址法)的例子: ```c #include <stdio.h> #include <stdlib.h> #define SIZE 10 // 哈希表大小 // 哈希表节点结构体 typedef struct Node { int data; struct Node *next; } Node; // 哈希表结构体 typedef struct HashTable { Node *head[SIZE]; } HashTable; // 哈希函数 int hash(int data) { return data % SIZE; } // 初始化哈希表 HashTable* init() { HashTable *hashTable = (HashTable*)malloc(sizeof(HashTable)); for (int i = 0; i < SIZE; i++) { hashTable->head[i] = NULL; } return hashTable; } // 插入数据 void insert(HashTable *hashTable, int data) { int key = hash(data); Node *node = (Node*)malloc(sizeof(Node)); node->data = data; node->next = NULL; if (hashTable->head[key] == NULL) { hashTable->head[key] = node; } else { Node *p = hashTable->head[key]; while (p->next != NULL) { p = p->next; } p->next = node; } } // 查找数据 int search(HashTable *hashTable, int data) { int key = hash(data); Node *p = hashTable->head[key]; while (p != NULL) { if (p->data == data) { return 1; } p = p->next; } return 0; } // 删除数据 void delete(HashTable *hashTable, int data) { int key = hash(data); Node *p = hashTable->head[key]; Node *prev = NULL; while (p != NULL) { if (p->data == data) { if (prev == NULL) { hashTable->head[key] = p->next; } else { prev->next = p->next; } free(p); return; } prev = p; p = p->next; } } // 打印哈希表 void print(HashTable *hashTable) { for (int i = 0; i < SIZE; i++) { printf("%d: ", i); Node *p = hashTable->head[i]; while (p != NULL) { printf("%d ", p->data); p = p->next; } printf("\n"); } } int main() { HashTable *hashTable = init(); insert(hashTable, 1); insert(hashTable, 11); insert(hashTable, 21); insert(hashTable, 31); insert(hashTable, 41); print(hashTable); printf("search %d: %d\n", 11, search(hashTable, 11)); printf("search %d: %d\n", 12, search(hashTable, 12)); delete(hashTable, 11); print(hashTable); return 0; } ``` 以上代码中,哈希表使用链表法解决哈希冲突。其中,哈希表结构体中包含一个指向节点链表头的指针数组,节点结构体包含一个数据域和一个指向下一个节点的指针。初始化哈希表时,将每个链表头初始化为NULL。插入数据时,先计算数据的哈希值,然后将节点插入到对应的链表尾部。查找数据时,先计算数据的哈希值,然后遍历对应链表查找数据。删除数据时,也是先计算数据的哈希值,然后遍历对应链表查找数据,找到后删除节点并释放空间。最后,打印哈希表时,遍历指针数组中的每个链表,输出链表中的数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值