引言

近来无聊,决定动手写点程序练练手,所以从最基础的哈希表数据结构开始,全程参考的此处的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);elseprintf(s);delete(ht,"shuoshuo");char* c = search(ht,"shuoshuo");if(c==NULL)printf("查找失败  %d\n",c);elseprintf(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);elseprintf(s);delete(ht,"shuoshuo");char* c = search(ht,"shuoshuo");if(c==NULL)printf("查找失败  %d\n",c);elseprintf(c);ht_del_hash_table(ht);return 0;
}

【C语言】动手写一个哈希表相关推荐

  1. C++从0到1手写一个哈希表

    C++从0到1手写一个哈希表 简易版哈希表 优化哈希表 目的:手写实现一个哈希表,采用拉链法构建,每个hash(key)对应的是一个红黑树. 看起来很简单,但可以学到很多东西.实现语言:C++. 简易 ...

  2. 自己动手写一个分库分表中间件(三)数据源路由实现

    相关文章: 自己动手写一个分库分表中间件(一)思考 自己动手写一个分库分表中间件(二)数据源定义和分片代理层设计 排查项目中读写分离失效原因 小议 Java 内省机制 注:本文内容暂不涉及事务相关的问 ...

  3. 学习较底层编程:动手写一个C语言编译器

    动手编写一个编译器,学习一下较为底层的编程方式,是一种学习计算机到底是如何工作的非常有效方法. 编译器通常被看作是十分复杂的工程.事实上,编写一个产品级的编译器也确实是一个庞大的任务.但是写一个小巧可 ...

  4. 自己动手写一个 strace

    这次主要分享一下一个动手的东西,就是自己动手写一个 strace 工具. 用过 strace 的同学都知道,strace 是用来跟踪进程调用的 系统调用,还可以统计进程对 系统调用 的统计等.stra ...

  5. 自己动手写一个推荐系统,推荐系统小结,推荐系统:总体介绍、推荐算法、性能比较, 漫谈“推荐系统”, 浅谈矩阵分解在推荐系统中的应用...

    自己动手写一个推荐系统 废话: 最近朋友在学习推荐系统相关,说是实现完整的推荐系统,于是我们三不之一会有一些讨论和推导,想想索性整理出来. 在文中主要以工程中做推荐系统的流程着手,穿插一些经验之谈,并 ...

  6. 自己动手写一个简单的bootloader

    自己动手写一个简单的bootloader 15年10月31日19:44:27 (一) start.S 写这一段代码前,先要清楚bootloader开始的时候都做什么了.无非就是硬件的初始化,我们想要写 ...

  7. 哈希(散列)(三):C语言实现 动态态哈希表

    哈希(散列)的概念: https://blog.csdn.net/mowen_mowen/article/details/82943192 C语言实现:静态哈希表: https://blog.csdn ...

  8. C语言实现简单的哈希表

    CRC是通信领域中用于校验数据传输正确性的最常用机制,也是Hash算法的一个典型应用,Hash一般翻译为"散列",也可直接音译为"哈希",就是把任意长度的输入( ...

  9. 自己动手写一个仿Docker虚拟容器

    自己动手写一个仿Docker虚拟容器 本项目参照书籍<自己动手写Docker> 作者:陈显鹭(花名:遥鹭)-阿里云高级研发工程师等 项目地址:https://gitee.com/Sheng ...

最新文章

  1. SAP SD基础知识之交货中的控制元素
  2. zabbix-3.0.4添加对windows 2008r2的监控
  3. 【转】Android菜单详解——理解android中的Menu--不错
  4. 【五】每个球队胜率统计
  5. windows下dos窗口实现持续ping显示时间保存至日志
  6. vlan划分不能上网_VLAN工作原理
  7. JAVA受检异常和非受检异常举例
  8. 【java】蔡勒公式计算星期(switch语句方法和数组方法)
  9. centos journalctl日志查看
  10. sso单点登录系统(精华篇)
  11. 关于onselect与onchange的介绍
  12. [Erlang 0125] Know a little Erlang opcode
  13. android 如何进入安全模式,手机怎么进入安全模式
  14. FORECAST函数预算产品的使用寿命测试值
  15. Ubuntu20.04 Server+Xubuntu-desktop英文环境下安装百度五笔
  16. java中指数形式的格式_java – 复数的指数形式
  17. layui 表格加载动画_巴州动画
  18. EXCEL数据分析——分列
  19. 视频播放器(AVPlayer)
  20. Python练习题:根据一段单词,找出其中的最长单词

热门文章

  1. K8S集群Pod资源自动扩缩容方案
  2. 进程理论以及开启子进程的两种方式
  3. TSC打印机,利用javascript实现连续扫码自动打印,配置说明
  4. 用友显示用友通服务器,用友T3用友通无法连接服务器--用友T3用友通无法连接服务器...
  5. 【海外APP】Twitch 全球首屈一指的游戏直播平台
  6. BatchNorm的通俗解释
  7. 纵横20年,我所经历的数据开放演化史 by 傅一平
  8. 裸金属服务器是什么?有什么特点?
  9. Python项目设计计划——树莓派自动浇花系统
  10. Py之pycocotools:pycocotools库的简介、安装、使用方法之详细攻略续篇