网络编程之 哈希表原理讲解 来自老司机的源码

鉴于博主很久没由跟新过数据结构的内容了,所以博主打算给大家讲解一下哈希表的操作

下面的内容来自于一位老司机 martin的源码,博主在这里借用一下,目的是突出哈希表的原理,明天博主就周末了,也能腾出时间来给上传自己的哈希表的应用。

这个是可以插入字符串的哈希表,一般的都是对数字的操作,所以这个的逼格是很高的!!!!(难点剖析放在最后)

#pragma once 

#define DEFAULT_SIZE 16 

/*哈希表元素定义*/
typedef struct _ListNode{
	struct _ListNode *next;
	int key;
	void *data;
}ListNode;

typedef ListNode *List;
typedef ListNode *Element;

/*哈希表结构定义*/
typedef struct _HashTable{
	int TableSize;
	List *Thelists;
}HashTable;

/*哈希函数*/
int Hash(void *key, int TableSize);

/*初始化哈希表*/
HashTable *InitHash(int TableSize);

/*哈希表插入*/
void Insert(HashTable *HashTable, int key,  void *value);

/*哈希表查找*/
Element Find(HashTable *HashTable, const int key);

/*哈希表销毁*/
void Destory(HashTable *HashTable);

/*哈希表元素中提取数据*/
void *Retrieve(Element e);


hash_table.cpp


#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include"hash_table.h"

/*根据 key 计算索引,定位 Hash 桶的位置*/ 
int Hash(int key, int TableSize) { 
    return (key%TableSize); //返回索引值
    
}

/*初始化哈希表*/
HashTable *InitHash(int TableSize){ //传入 哈希桶的个数,在函数内部给哈希表分配空间,再将初始化好了的哈希表传出去。
	int i = 0;
	HashTable *hTable = NULL;
	
	if (TableSize <= 0) {  //如果用户恶意输入数字,那么我们就可以把哈希表的个数定为我们最初的 16个
		TableSize = DEFAULT_SIZE;
	}
	
	hTable = (HashTable *)malloc(sizeof(HashTable));
	if (NULL == hTable){    //如果地址分配失败
		printf("HashTable malloc error.\n");
		return NULL;
	}
	
	hTable->TableSize = TableSize;
	//为 Hash 桶分配内存空间,其为一个指针数组 
	hTable->Thelists = (List *)malloc(sizeof(List)*TableSize);  
	    //hTable->Thelists其实就是可以来指向哈希表里面的与元素了

	if (NULL == hTable->Thelists){  //分配空间失败,打印错误,然后返回
		printf("HashTable malloc error\n");
		free(hTable);
		return NULL;
	}
	
	//为 Hash 桶对应的指针数组初始化链表节点 
	for (i = 0; i < TableSize; i++){
		hTable->Thelists[i] = (ListNode *)malloc(sizeof(ListNode));//也就是给头节点分配空间地,这样后续就能完成插入了
		if (NULL == hTable->Thelists[i]){   //如果分配失败,那我们就释放之前分配了的空间地址,以免造成内存泄漏
			printf("HashTable malloc error\n");
			free(hTable->Thelists);
			free(hTable);
			return NULL;
		} else
		{
			memset(hTable->Thelists[i], 0, sizeof(ListNode));   //将好小标指向的元素全部清空。主要是防止意外情况。
		}
	}
	return hTable;
}

Element Find(HashTable *HashTable, int key)
{
	int i = 0;  //目的接受调用HashTable函数返回的索引值
	List L = NULL;  //定位到(第几个哈希桶)指针数组里面的第几个头结点。
 	Element e = NULL;  //指向定位到的(第几个哈希桶)的第一个节点(第一个节点是不包括头结点的哦)。
	i = Hash(key, HashTable->TableSize);
	L = HashTable->Thelists[i];
	e = L->next;
 	while (e != NULL && e->key != key) //目的是为了遍历对应的哈希桶是否存在相同的key值。
		e = e->next;
	return e;   //返回两种情况,找到了就返回e对应的结构体,失败了就返回NULL
}

/*哈希表插入元素,元素为键值对*/
void Insert(HashTable *HashTable, int key, void *value)
{
	Element e = NULL, tmp = NULL;   //e用来接收查找后的e,tmp是用来插入的
	List L = NULL;  //定位到第几个哈希桶,和我们的find中的L的作用是一样的。
	e = Find(HashTable, key);   //现在就找到了对应的第几个哈希桶的第一个节点了。或者是NULL
	if (NULL == e){ //如果接收到的e为空的话我们就可以进行插入元素的操作了
		tmp = (Element)malloc(sizeof(ListNode));   // 因为我们接收到的是NULL所以非配空间为了插入
		if (NULL == tmp)
		{
			printf("malloc error\n");
			return;
		}
		L = HashTable->Thelists[Hash(key, HashTable->TableSize)];   //\定位到第几个哈希桶
		//经典的头插法
		tmp->data = value;
		tmp->key = key;
		tmp->next = L->next;
		L->next = tmp;
	} else
		printf("the key already exist\n");
}

/*哈希表删除元素,元素为键值对*/
void Delete(HashTable *HashTable, int key){ //key是你要删除的第几个位置的键值对
	Element e = NULL, last = NULL;  
	List L = NULL;
	
	int i = Hash(key, HashTable->TableSize);    //找到对应的i是第几个
	L = HashTable->Thelists[i]; //找到对应的第几个哈希桶
	last = L;   //让last 等于 对应的第几个哈希桶的头结点
	e = L->next;    //e指向对应的第几个哈希桶的第一个节点。
	
	while (e != NULL && e->key != key) {    //遍历对应的哈希桶的全部链表,结束条件是 找到了要删除的key或者是遍历完成
		last = e;   //last为e也就是以后要删除的结点的上一个结点
		e = e->next;    //指向后面的结点
	}
	if (e) {//如果键值对存在,那么就可以删除了。
		last->next = e->next;   
		delete(e);
	}
}

/*哈希表元素中提取数据*/
void *Retrieve(Element e)   //在元素e存在的情况下降找到的 e->data 返回出去
{
	return e ? e->data : NULL;  //三目运算符
}



/*销毁哈希表*/
void Destory(HashTable *HashTable)
{
	int i = 0;
	List L = NULL;
	Element cur = NULL, next = NULL;
	for (i = 0; i < HashTable->TableSize; i++)  //将所有的哈希桶都遍历完成
	{
		L = HashTable->Thelists[i];
		cur = L->next;
		while (cur != NULL) //将对应的链表全部销毁,跳出之后只有头结点没有销毁了
		{
			next = cur->next;
			free(cur);
			cur = next;
		}
		free(L);    //释放那一个头结点
	}   //完成之后所有的哈希桶全部销毁
	free(HashTable->Thelists);  //释放那个二级指针
	free(HashTable);    //释放哈希表。
}

int main(void)
{
	char  *elems[] = { "翠花","小芳","苍老师" };
	int i = 0;
	HashTable *HashTable;
	HashTable = InitHash(31);

	Insert(HashTable, 1, elems[0]);
	Insert(HashTable, 2, elems[1]);
	Insert(HashTable, 3, elems[2]);

	Delete(HashTable, 1);

	for (i = 0; i < 4; i++) {
		Element e = Find(HashTable, i);
		if (e) {
			printf("%s\n", (const char *)Retrieve(e));
		} else {
			printf("Not found [key:%d]\n", i);
		}
	}
	system("pause");
	return 0;
}

希望大家能好好的观看我的注释,相信一定能给你收获的。

如果觉得代码太长的,博主在这里给大家将模块分解了。大家也可以观看分解之后的代码,这样
压力会小一点。 
头文件就不用说了相信大家都能看明白,就只是声明和定义结构体的类型而已。

哈希函数

/*根据 key 计算索引,定位 Hash 桶的位置*/ 
int Hash(int key, int TableSize) { 
    return (key%TableSize); //返回索引值
    
}

哈希表的初始化

/*初始化哈希表*/
HashTable *InitHash(int TableSize){ //传入 哈希桶的个数,在函数内部给哈希表分配空间,再将初始化好了的哈希表传出去。
	int i = 0;
	HashTable *hTable = NULL;
	
	if (TableSize <= 0) {  //如果用户恶意输入数字,那么我们就可以把哈希表的个数定为我们最初的 16个
		TableSize = DEFAULT_SIZE;
	}
	
	hTable = (HashTable *)malloc(sizeof(HashTable));
	if (NULL == hTable){    //如果地址分配失败
		printf("HashTable malloc error.\n");
		return NULL;
	}
	
	hTable->TableSize = TableSize;
	//为 Hash 桶分配内存空间,其为一个指针数组 
	hTable->Thelists = (List *)malloc(sizeof(List)*TableSize);  
	    //hTable->Thelists其实就是可以来指向哈希表里面的与元素了

	if (NULL == hTable->Thelists){  //分配空间失败,打印错误,然后返回
		printf("HashTable malloc error\n");
		free(hTable);
		return NULL;
	}
	
	//为 Hash 桶对应的指针数组初始化链表节点 
	for (i = 0; i < TableSize; i++){
		hTable->Thelists[i] = (ListNode *)malloc(sizeof(ListNode));//也就是给头节点分配空间地,这样后续就能完成插入了
		if (NULL == hTable->Thelists[i]){   //如果分配失败,那我们就释放之前分配了的空间地址,以免造成内存泄漏
			printf("HashTable malloc error\n");
			free(hTable->Thelists);
			free(hTable);
			return NULL;
		} else
		{
			memset(hTable->Thelists[i], 0, sizeof(ListNode));   //将好小标指向的元素全部清空。主要是防止意外情况。
		}
	}
	return hTable;
}

哈希表中插入元素:

/*哈希表插入元素,元素为键值对*/
void Insert(HashTable *HashTable, int key, void *value)
{
	Element e = NULL, tmp = NULL;   //e用来接收查找后的e,tmp是用来插入的
	List L = NULL;  //定位到第几个哈希桶,和我们的find中的L的作用是一样的。
	e = Find(HashTable, key);   //现在就找到了对应的第几个哈希桶的第一个节点了。或者是NULL
	if (NULL == e){ //如果接收到的e为空的话我们就可以进行插入元素的操作了
		tmp = (Element)malloc(sizeof(ListNode));   // 因为我们接收到的是NULL所以非配空间为了插入
		if (NULL == tmp)
		{
			printf("malloc error\n");
			return;
		}
		L = HashTable->Thelists[Hash(key, HashTable->TableSize)];   //\定位到第几个哈希桶
		//经典的头插法
		tmp->data = value;
		tmp->key = key;
		tmp->next = L->next;
		L->next = tmp;
	} else
		printf("the key already exist\n");
}

哈希表中查找元素:

Element Find(HashTable *HashTable, int key)
{
	int i = 0;  //目的接受调用HashTable函数返回的索引值
	List L = NULL;  //定位到(第几个哈希桶)指针数组里面的第几个头结点。
 	Element e = NULL;  //指向定位到的(第几个哈希桶)的第一个节点(第一个节点是不包括头结点的哦)。
	i = Hash(key, HashTable->TableSize);
	L = HashTable->Thelists[i];
	e = L->next;
 	while (e != NULL && e->key != key) //目的是为了遍历对应的哈希桶是否存在相同的key值。
		e = e->next;
	return e;   //返回两种情况,找到了就返回e对应的结构体,失败了就返回NULL
}

哈希表中删除元素:

/*哈希表删除元素,元素为键值对*/
void Delete(HashTable *HashTable, int key){ //key是你要删除的第几个位置的键值对
	Element e = NULL, last = NULL;  
	List L = NULL;
	
	int i = Hash(key, HashTable->TableSize);    //找到对应的i是第几个
	L = HashTable->Thelists[i]; //找到对应的第几个哈希桶
	last = L;   //让last 等于 对应的第几个哈希桶的头结点
	e = L->next;    //e指向对应的第几个哈希桶的第一个节点。
	
	while (e != NULL && e->key != key) {    //遍历对应的哈希桶的全部链表,结束条件是 找到了要删除的key或者是遍历完成
		last = e;   //last为e也就是以后要删除的结点的上一个结点
		e = e->next;    //指向后面的结点
	}
	if (e) {//如果键值对存在,那么就可以删除了。
		last->next = e->next;   
		delete(e);
	}
}

哈希表中提取元素以及销毁哈希表:

/*哈希表元素中提取数据*/
void *Retrieve(Element e)   //在元素e存在的情况下降找到的 e->data 返回出去
{
	return e ? e->data : NULL;  //三目运算符
}



/*销毁哈希表*/
void Destory(HashTable *HashTable)
{
	int i = 0;
	List L = NULL;
	Element cur = NULL, next = NULL;
	for (i = 0; i < HashTable->TableSize; i++)  //将所有的哈希桶都遍历完成
	{
		L = HashTable->Thelists[i];
		cur = L->next;
		while (cur != NULL) //将对应的链表全部销毁,跳出之后只有头结点没有销毁了
		{
			next = cur->next;
			free(cur);
			cur = next;
		}
		free(L);    //释放那一个头结点
	}   //完成之后所有的哈希桶全部销毁
	free(HashTable->Thelists);  //释放那个二级指针
	free(HashTable);    //释放哈希表。
}

博主认为比较难的就是:
指针数组里面存放的是每个哈希桶的头结点,通过求余来锁定要查找或删除的值在哪一个哈希桶里(认为就是这个最难理解,理解了之后其实哈希就不难了)。其他的就是链表的操作了。

明天上传自己敲得代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值