【白话杂谈 No.3】简单哈希表的实现

前言

好久不见老朋友,初次见面新朋友,失踪人口回来啦。最近忙于学业没有更新动静so sorry,不过也正是因为被各种任务压着才让我想起,“为什么不去找找有没有人为我们分享这些内容呢?”,于是又转念想到,好像我曾经开了个系列坑是用来分享干货的来着?

这不又到期末了嘛,想必不少同学还在为数据结构这块内容痛苦着,咱也就来为各位的及格线奋斗史尽一点绵薄之力,若有帮助还请三连支持哦~,你们的反馈将成为我创作的巨大动力~

代码

废话不多说,直接上代码。

list.h
#ifndef _LIST_H_
#define _LIST_H_

//链式实现
struct list_node;

struct list;

//初始化线性表
struct list* list_create();

//销毁回收线性表
void list_destroy(struct list** list);

//查询节点个数
int list_count(const struct list*);

//查空
int list_isempty(const struct list*);

//清空
void list_clear(struct list*);

//添加
void list_set(struct list*, int key, int data, bool NX);

//删除
void list_remove(struct list* list, int key);

//获取
int list_get(const struct list* list, int key);

#endif
hashtable.h
#ifndef _HASHTABLE_H_
#define _HASHTABLE_H_

#include "list.h";

struct hashtable;

//初始化
struct hashtable* hs_init();

//回收
void hs_free(struct hashtable** h);

//哈希算法
int hash(int key);

//设置
void hs_insert(struct hashtable* h, int key, int value, bool NX);

//删除
void hs_remove(struct hashtable* h, int key);

//清空
void hs_clear(struct hashtable* h);

//访问
int hs_get(const struct hashtable* h, int key);

//查询元素个数
int hs_count(const struct hashtable* h);

#endif
list.cpp
#include "list.h"
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

//链式实现
struct list_node {
	int key;
	int value;
	struct list_node* next;
};

struct list {
	struct list_node* head;
	int count;
};

list* list_create()
{
	list* new_list = (list*)malloc(sizeof(list));
	assert(new_list != nullptr);
	new_list->count = 0;
	new_list->head = nullptr;
	return new_list;
}

void list_destroy(struct list** list)
{
	assert(*list != nullptr);

	//先遍历释放链表中的元素
	while ((*list)->head != nullptr) {
		list_node* new_head = (*list)->head->next;
		free((*list)->head);
		(*list)->head = new_head;
	}

	free(*list);
	*list = nullptr;

}

int list_count(const list* list)
{
	assert(list != nullptr);
	return list->count;
}

bool list_isempty(const list* list)
{
	assert(list != nullptr);
	if (list->head == nullptr) return true;
	return false;
}

void list_clear(list* list)
{
	assert(list != nullptr);

	//先顺序释放链表中的元素
	while (list->head != nullptr) {
		list_node* tmp = list->head;
		list->head = list->head->next;
		free(tmp);
	}
	list->count = 0;
	return;

	//和destroy唯一的区别无非就是不用释放表
}

//对用作拉链法的list进行改造,永远进行头插法,且可以覆盖原有的同key数据
void list_set(list* list, int key, int data, bool NX)
{
	assert(list != nullptr);

	//先进行遍历查找是否已有key,有就得根据NX决定是否覆盖
	list_node* tmp = list->head;
	while (tmp) {
		if (tmp->key == key) {
			if (NX) {
				return;
			}
			else {
				tmp->value = data;
				return;
			}
		}
		tmp = tmp->next;
	}

	list_node* new_head = (list_node*)malloc(sizeof(list_node));
	new_head->key = key;
	new_head->value = data;

	//如果初始表为空
	if (list->count == 0) {
		list->head = new_head;
		++list->count;
		new_head->next = nullptr;
		return;
	}

	new_head->next = list->head;
	list->head = new_head;
	++list->count;
	return;

}

void list_remove(list* list, int key)
{
	assert(list != nullptr);

	list_node* prev = nullptr;
	list_node* now = list->head;
	list_node* last = now->next;

	while (now) {

		//若为删除头节点,需要特殊处理
		if (now->key == key && now == list->head) {

			//减少节点个数
			--list->count;
			
			//更新头节点
			list->head = list->head->next;

			//回收操作
			free(now);
			now = nullptr;
			return;
		}

		//若为删除中间或尾部节点,则需要将前驱与后继进行连接处理
		if (now->key == key) {
			list_node* tmp = now;

			//重新连接
			prev->next = last;

			//删除节点
			free(tmp);
			tmp = nullptr;

			//减少个数
			--list->count;

			return;
		}

	}

	return;
}

int list_get(const list* list, int key)
{
	//遍历查找
	list_node* tmp = list->head;

	while (tmp) {
		if (tmp->key == key) {
			return tmp->value;
		}
		tmp = tmp->next;
	}

	printf("error: the data is invalid\n");
	return 0;
}
hashtable.cpp
#include "hashtable.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

const int HASH_NUM = 17;

struct hashtable {
	struct list* table[HASH_NUM];
};

hashtable* hs_init() {
	hashtable* new_hashtable = (hashtable*)malloc(sizeof(hashtable));
	assert(new_hashtable != nullptr);

	for (int i = 0; i < HASH_NUM; ++i) {
		new_hashtable->table[i] = list_create();
		assert(new_hashtable->table[i] != nullptr);
	}

	printf("operate init success\n");
	return new_hashtable;
}

void hs_free(hashtable** h)
{
	assert(h != nullptr);
	for (int i = 0; i < HASH_NUM; ++i) {
		if ((*h)->table[i] != nullptr) {
			list_destroy(&((*h)->table[i]));
		}
	}
	free(*h);
	*h = nullptr;
	printf("operate free success\n");
	return;
}

int hash(int key)
{
	return key % HASH_NUM;
}

void hs_insert(struct hashtable* h, int key, int value, bool NX)
{
	int index = hash(key);

	list_set(h->table[index], key, value, NX);
	printf("operate insert success\n");
	return;
}

void hs_remove(hashtable* h, int key)
{
	int index = hash(key);
	list_remove(h->table[index], key);
	printf("operate remove success\n");
}

void hs_clear(hashtable* h)
{
	for (int i = 0; i < HASH_NUM; ++i) {
		list_clear(h->table[i]);
	}
	printf("operate clear success\n");
	return;
}

int hs_get(const hashtable* h, int key)
{
	int index = hash(key);
	return list_get(h->table[index], key);
}

int hs_count(const hashtable* h)
{
	assert(h != nullptr);
	int count = 0;

	for (int i = 0; i < HASH_NUM; ++i) {
		count = count + list_count(h->table[i]);
	}
	return count;
}
main.cpp
#include <stdio.h>
#include "hashtable.h"

int main() {

	hashtable* test_h = hs_init();

	//元素个数获取测试
	printf("The count of the hashtable is %d\n", hs_count(test_h));

	//分割线
	printf("----------------------------------\n");

	//设置测试,不可覆盖
	hs_insert(test_h, 49, 10, true);
	hs_insert(test_h, 64, 20, true);
	hs_insert(test_h, 1, 30, true);
	hs_insert(test_h, 13, 40, true);
	hs_insert(test_h, 666, 50, true);
	hs_insert(test_h, 87, 60, true);

	//分割线
	printf("----------------------------------\n");

	//元素个数获取测试
	printf("The count of the hashtable is %d\n", hs_count(test_h));

	//分割线
	printf("----------------------------------\n");

	int key;

	//元素获取测试
	printf("Input key number to get: ");
	scanf_s("%d",&key);
	printf("The data of %d(key) is %d\n", key, hs_get(test_h, key));

	//分割线
	printf("----------------------------------\n");

	//设置测试,可覆盖
	hs_insert(test_h, 49, 70, false);
	hs_insert(test_h, 64, 80, false);
	hs_insert(test_h, 1, 90, false);
	hs_insert(test_h, 13, 100, false);
	hs_insert(test_h, 666, 110, false);
	hs_insert(test_h, 87, 120, false);

	//分割线
	printf("----------------------------------\n");

	//元素个数获取测试
	printf("The count of the hashtable is %d\n", hs_count(test_h));

	//分割线
	printf("----------------------------------\n");

	//元素获取测试
	printf("Input key number to get: ");
	scanf_s("%d", &key);
	printf("The data of %d(key) is %d\n", key, hs_get(test_h, key));

	//分割线
	printf("----------------------------------\n");

	//元素删除测试
	printf("Input key number to remove: ");
	scanf_s("%d", &key);
	hs_remove(test_h, key);

	//分割线
	printf("----------------------------------\n");

	//元素获取测试
	printf("Input key number to get: ");
	scanf_s("%d", &key);
	printf("The data of %d(key) is %d\n", key, hs_get(test_h, key));

	//分割线
	printf("----------------------------------\n");

	//元素个数获取测试
	printf("The count of the hashtable is %d\n", hs_count(test_h));

	//分割线
	printf("----------------------------------\n");

	//清空测试
	hs_clear(test_h);

	//分割线
	printf("----------------------------------\n");

	//元素个数获取测试
	printf("The count of the hashtable is %d\n", hs_count(test_h));

	//分割线
	printf("----------------------------------\n");

	hs_free(&test_h);

	return 0;
}
运行结果

讲解

别看内容很多,实际上逻辑很简单,无非还是那一套crud而已,有STL容器或Redis使用经验的同学或许已经对这些操作十分熟悉了,在此仅对几个特殊的点进行讲解:

①本哈希表采用的是拉链法来解决哈希冲突的;

②容量与哈希算法用定位数为HASH_NUM,可根据需要进行修改,最好为质数,可以最大程度上地防止过多元素产生哈希冲突;

③hs_insert为设置功能,可以根据NX参数来决定是否对已有元素进行覆盖;

④hs_get默认返回值为0,若访问的为空键值对,则会进行报错,但并不终止程序;

⑤main.cpp为简单的功能测试用程序,不放心的可以自行进行设计测试,需要注意的是,本程序是在Visual Studio 2022环境下编写的,scanf需要使用其安全模式scanf_s,否则需要自行进行项目预处理参数的修改或使用其他编译器;

⑥为了让更多同学能够无障碍读码,本哈希表采用C语言面向过程编程实现,如需要更改为其他面向对象编程的语言如C++、Java请自行参考。

惯例啰嗦

数据结构与算法是编程底力的重中之重,也是难啃的硬骨头,如果还有想了解的内容可以在评论区留言,或许下一期会给予参考哦~。前提是我还能想起账号密码的话

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值