哈希表的C语言实现

哈希表的具体原理,参考:

算法数据结构基础——哈希表(Hash Table)-CSDN博客

接下来是我的c语言编程实现:

首先,定义哈希表的节点这个结构体:

// 哈希表的节点结构
typedef struct HashNode {
    int key;
    int value;
    struct HashNode* next;
} HashNode;

        这个结构体的名字是HashNode,结构体成员,key用于存储节点的键;value用于存储与键关联的值;struct HashNode* next;这条指令定义了一个指向 HashNode 类型结构体的指针,名为 next。
        这个指针的作用是:用链地址法来处理哈希冲突,next指针用于链接下一个节点,形成一个链表。这样,当多个键key被哈希函数映射到同一个位置时,可以将这些键值对存储在同一个链表中。


定义哈希表的结构体:

typedef struct {
    HashNode** table;
    int size;
} HashTable;

        这段代码中用typedef关键字定义了哈希表的结构体。结构体的名称是: HashTable

        代码:HashNode** table;定义了一个指针数组table,用于存储哈希表中各个桶的头指针。每个桶可以看作是一个链表的头节点,存储哈希表中所有散列到该桶中的键值对。

        size是一个整数,表示哈希表的大小,即哈希表中的桶的数量。


创建哈希表:

//创建哈希表
HashTable* createHashTable(int size)
{
	HashTable* hashTable = (HashTable*)malloc(sizeof(HashTable));
	hashTable->size = size;
	hashTable->table = (HashNode**)malloc(sizeof(HashNode*) * size);
	for (int i = 0; i < size; i++)
	{
		hashTable->table[i] = NULL;
	}
	return hashTable;
}

        上述代码用于创建并初始化一个哈希表。首先,这个函数的返回类型是HashTable*,表示要求返回一个指向HashTable结构体的指针。size表示哈希表的大小,也就是哈希表中桶的数量。

        后续代码作用就是:分配哈希表结构体的内存,并且设置哈希表的大小,分配桶数组内存,初始化桶数组,每一个桶初始指向为NULL。


哈希函数:

// 哈希函数
int hashFunction(HashTable* hashTable, int key)
{
	return abs(key) % hashTable->size;
}

        这段代码的作用是编写了一个哈希哈数,也就是哈希表的映射函数。这个哈希函数的映射方式是:abs(key) % hashTable。将键的绝对值映射到哈希表的索引范围内。通过取模运算,可以确保返回的哈希值在 0hashTable->size - 1 的范围内。

        这个哈希函数用于将一个整数键映射到哈希表中的一个桶(bucket)索引。哈希函数的作用是将输入的键转换为哈希表的一个有效索引,确保不同的键尽可能均匀地分布在哈希表中。

        举例说明,假如一个哈希表的大小为10,则计算几个不同的键值的哈希值如下:

Key: 15, Hash Value: 5
Key: -7, Hash Value: 7
Key: 3, Hash Value: 3
Key: -11, Hash Value: 1
Key: 27, Hash Value: 7

插入哈希表:

//插入哈希表
void insert(HashTable* hashTable, int key, int value)
{
	int hashIndex = hashFunction(hashTable, key);
	HashNode* newNode = (HashNode*)malloc(sizeof(HashNode));
	newNode->key = key;
	newNode->value = value;
	newNode->next = hashTable->table[hashIndex];
	hashTable->table[hashIndex] = newNode;
}

        首先给出函数定义,函数传入的参数是:指向哈希表结构体的指针,要插入的键,要插入的值。

        然后调用hashFunction计算key在哈希表中的哈希值(也就是索引位置,在哪个桶);

        创建一个新的节点,为新节点的key和value赋值。也是初始化。

        然后处理哈希冲突:将当前桶中的现有节点(可能为空)赋值给新节点的 next 字段;将新节点设置为当前桶的头节点。这一步可以总结为:更新头指针:将新节点插入到桶的头部,使其成为新的头节点。

newNode->next = hashTable->table[hashIndex];
hashTable->table[hashIndex] = newNode;

函数操作解析:

 假设有一个简单的哈希表和三个要插入的键值对:

HashTable: {size: 5, table: [NULL, NULL, NULL, NULL, NULL]}
Insert (key=3, value=10)
Insert (key=8, value=20)
Insert (key=13, value=30)

哈希函数 hashFunction(key) = key % size

  • Insert (key=3, value=10):

    • hashIndex = 3 % 5 = 3
    • 创建新节点 {key: 3, value: 10, next: NULL}
    • 哈希表更新:table[3] = newNode
  • Insert (key=8, value=20):

    • hashIndex = 8 % 5 = 3
    • 创建新节点 {key: 8, value: 20, next: table[3]}
    • 哈希表更新:table[3] = newNode
  • Insert (key=13, value=30):

    • hashIndex = 13 % 5 = 3
    • 创建新节点 {key: 13, value: 30, next: table[3]}
    • 哈希表更新:table[3] = newNode

最终哈希表结构: 

table:
[NULL, NULL, NULL, {key: 13, value: 30, next: {key: 8, value: 20, next: {key: 3, value: 10, next: NULL}}}, NULL]

查找函数:

//查找哈希表
int search(HashTable* hashTable, int key, int* value)
{
	int hashIndex = hashFunction(hashTable, key);
	HashNode* node = hashTable->table[hashIndex];
	while (node != NULL)
	{
		if (node->key == key)
		{
			*value = node->value;
			return 1;
		}
		node = node->next;
	}
	return 0;
}

        首先通过传入函数的参数key确定要找的值所在的对应的桶,然后在桶中遍历查找是否有相同的key值。遍历步骤是:

  • 遍历链表:从桶的头节点开始,遍历链表中的每个节点。
    • 检查节点键:如果当前节点的键等于查找的键 key
      • 存储值:将节点的值存储在 value 指向的位置。
      • 返回成功:返回 1 表示找到键。
    • 继续遍历:如果当前节点的键不等于查找的键,移动到下一个节点。
  • 未找到键:如果遍历完整个链表后仍未找到键,返回 0 表示未找到键。

例子:有如下一个哈希表,进行查找操作;

HashTable: {size: 5, table: [NULL, NULL, NULL, {key: 13, value: 30, next: {key: 8, value: 20, next: {key: 3, value: 10, next: NULL}}}, NULL]}
Search key=8
Search key=13
Search key=2

哈希函数 hashFunction(key) = key % size

  1. 查找 key=8:

    • hashIndex = 8 % 5 = 3
    • 遍历链表:头节点 {key: 13, value: 30, next: ...}
      • 继续遍历:第二个节点 {key: 8, value: 20, next: ...},找到匹配键。
    • 返回值:找到键,存储值 20,返回 1
  2. 查找 key=13:

    • hashIndex = 13 % 5 = 3
    • 遍历链表:头节点 {key: 13, value: 30, next: ...},找到匹配键。
    • 返回值:找到键,存储值 30,返回 1
  3. 查找 key=2:

    • hashIndex = 2 % 5 = 2
    • 2 为空,直接返回 0
    • 返回值:未找到键,返回 0

哈希表删除操作函数:

// 删除哈希表中的节点
void delete(HashTable* hashTable, int key) {
	int hashIndex = hashFunction(hashTable, key);
	HashNode* node = hashTable->table[hashIndex];
	HashNode* prev = NULL;
	while (node != NULL && node->key != key) {
		prev = node;
		node = node->next;
	}
	if (node == NULL) return;  // 未找到键
	if (prev == NULL) {
		hashTable->table[hashIndex] = node->next;
	}
	else {
		prev->next = node->next;
	}
	free(node);
}

        该函数传入的参数是一个哈希表的地址指针,一个要删除的key键。大致流程是首先调用哈希函数根据key找到对应的桶;获取桶的头指针table[hashIndex],并且用指针node指向这个桶的头指针,初始化一个前驱指针指向为NULL,前驱指针的作用是记录当前节点的前一个节点。然后用while遍历链表查找要删除的节点:

  • 遍历链表:从头节点开始,逐节点检查每个节点的键是否等于 key
    • 如果 nodeNULLnode->key == key,循环结束。
    • 否则,将 prev 设置为当前节点,将 node 设置为下一个节点。

        如果节点不存在,则直接返回。

        如果目标节点存在,则删除节点。删除节点的方法是:

  • 删除头节点:如果 prevNULL,表示要删除的节点是桶的头节点。
    • 将桶的头指针设置为 node->next
  • 删除非头节点:如果 prev 不为 NULL,表示要删除的节点不是头节点。
    • prev->next 设置为 node->next,跳过要删除的节点,相当于覆写。

        最终调用free释放目标节点的内存。

例子解析,加入现在有一个哈希表的链表如下:

HashTable: {size: 5, table: [NULL, NULL, NULL, {key: 13, value: 30, next: {key: 8, value: 20, next: {key: 3, value: 10, next: NULL}}}, NULL]}

经过删除函数之后,更新为:

HashTable: {size: 5, table: [NULL, NULL, NULL, {key: 13, value: 30, next: {key: 3, value: 10, next: NULL}}, NULL]}

释放哈希表函数:

// 释放哈希表
void freeHashTable(HashTable* hashTable) {
	for (int i = 0; i < hashTable->size; i++) {
		HashNode* node = hashTable->table[i];
		while (node != NULL) {
			HashNode* temp = node;
			node = node->next;
			free(temp);
		}
	}
	free(hashTable->table);
	free(hashTable);
}

        这个函数的参数是一个指向哈希表的指针。函数内容是:首先遍历这个哈希表的每一个桶,然后利用while释放桶中的每一个节点:

  • 遍历链表:使用 while 循环遍历当前桶中的每个节点。
    • 保存当前节点temp 保存当前节点的指针。
    • 移动到下一个节点:将 node 指针移到下一个节点。
    • 释放当前节点:使用 free(temp) 释放当前节点的内存。

        最后释放桶数组,释放哈希表结构体。


主函数

接下来是利用哈希表完成的子函数和主函数:


// 两数之和函数实现
int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
	HashTable* hashTable = createHashTable(numsSize);
	int* result = (int*)malloc(2 * sizeof(int));
	*returnSize = 0;

	for (int i = 0; i < numsSize; i++) {
		int complement = target - nums[i];
		int complementIndex;
		if (search(hashTable, complement, &complementIndex)) {
			result[0] = complementIndex;
			result[1] = i;
			*returnSize = 2;
			freeHashTable(hashTable);
			return result;
		}
		insert(hashTable, nums[i], i);
	}

	free(result);
	freeHashTable(hashTable);
	return NULL;
}

int main() {
	int nums[] = { 2, 7, 11, 15 };
	int numsSize = sizeof(nums) / sizeof(nums[0]);
	int target = 9;
	int returnSize;
	int* result = twoSum(nums, numsSize, target, &returnSize);

	if (result != NULL) {
		printf("Indices: %d, %d\n", result[0], result[1]);
		free(result);
	}
	else {
		printf("No solution found\n");
	}

	return 0;
}

        本文结合C语言代码说明一个简单的哈希表的代码实现和原理解析。

  • 28
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值