鉴于博主很久没由跟新过数据结构的内容了,所以博主打算给大家讲解一下哈希表的操作
下面的内容来自于一位老司机 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); //释放哈希表。 }
博主认为比较难的就是:
指针数组里面存放的是每个哈希桶的头结点,通过求余来锁定要查找或删除的值在哪一个哈希桶里(认为就是这个最难理解,理解了之后其实哈希就不难了)。其他的就是链表的操作了。
明天上传自己敲得代码