211.哈希表实现活期储蓄账目管理系统
摘要:随着社会发展,人们的生活水平不断提高,会将一定的收入用于活期存储,因其储户开户、销户、存入、支出活动频繁的需求特点,本课程设计使用哈希表进行银行活期储蓄账目管理系统的模拟,实现基本的数据存储和处理操作。
关键词:管理系统;哈希表;数据结构
1 需求分析
1.1问题描述
活期储蓄帐目管理,活期储蓄处理中,储户开户、销户、存入、支出活动频繁,系统设计要求:1)能比较迅速地找到储户的帐户,以实现存款、取款记账;2)能比较简单,迅速地实现插入和删除,以实现开户和销户的需要。
1.2模块组成
伪UI模块:伪界面,为了使界面美观设计的伪UI
主程序模块:初始化工作及功能选择和调用
开户模块:要求能新建账户并且能够进一步对所建立的账户进行设置开户金额等操作。
销户模块:查找到目的账户,对其销户。
查询模块:能够通过账号查找,迅速的找到要查找的账户,并显示账户余额。
存取模块:在迅速地查找到目的账户后,对账户余额进行修改。
测试模块:超级管理员能够模拟输入数据,并输出全部数据
1.3 功能要求
根据1.2模块组成该系统具有以下五个部分的功能:
开户功能:能够从键盘输入用户账户新建账户,并对其进行进行设置初始开户金额。
销户功能:查找到目的账户,若余额为0则对其销户,否则提示先取钱再销户。
查询功能:能够通过账号查找,迅速的找到要查找的账户,并显示账户余额。
存取功能:在迅速地查找到目的账户后,对账户进行存钱取钱操作。
测试功能:能够通过管理员预设密码进行,模拟输入,全部输出操作,以方便测试。
2 概要设计
2.1 设计思路
每一个用户看作一个基本单位,将系统抽象只包括账号和金额,密码、日期等不做存储和处理。为了便于后期维护与找错,使结构更加明了,采用数据结构型函数与功能实现型函数分离结构,存储数据结构是哈希表。
图1 数据结构型函数和功能实现型函数
2.2 存储结构设计
数据结构存储采用的是哈希表,账号存储为字符串,常用字符串哈希函数有 BKDRHash、APHash、DJBHash、JSHash、RSHash、SDBMHash、PJWHash、ELFHash等,本程序采用 BKDRHash对字符串进行散列,得到一个整数的hash值,根据得到的hash值选择一个槽位置,解决冲突方法为链接法,如图3哈希表插入过程。其中零号元素位用于存储哈希表主槽位容积,非所有元素容积,如图2哈希表存储模型。
图2 哈希表存储模型
图3 哈希表插入过程
2.3 算法设计说明
下面将从函数调用关系和对部分算法设计说明两个方面进行
2.3.1 函数调用关系
图4 函数调用关系
2.3.2 算法设计说明
哈希表初始化算法思想是,为哈希表主槽申请空间并置为0,第0个元素的值域用于存放哈希表主槽长度。
判断哈希值所在哈希表槽位的思想是,将字符串用BKDR算法散列成一个整数哈希值,用该整数哈希值对长度取余加1一定落在哈希表主槽槽位数组内,其中加1是为了防止落在0位,因为0位用于存储长度。
哈希表插入元素的思想是,结合图3,先求插入值的主槽,主槽存在该元素则直接更新值域,如果主槽不是该元素,则冲突处理,本程序用链接法,查看主槽冲突指针域,重复上述过程,即找主槽,然后遍历主槽冲突链,存在就更新,不存在就插入。
哈希表删除元素的思想与插入元素的思想较为类似,将对应插入改成删除。
哈希表搜索的思想是,先求要搜索值的哈希值,再利用判断哈希值所在哈希表槽位的算法找到该槽位,对比该槽位元素查看是否一致,一致则查找成功,不一样则看该槽位是否有冲突元素,有的话则对比该槽位元素是否一致,没有的话则不存在该元素,查找失败。
图5 哈希表查找流程图
返回全部数据元素个数的思想为,遍历主槽,如果主槽后面有冲突链则遍历冲突链。
打印全部元素的思想与返回全部元素思想类似,先主槽,并查看是否有冲突链。
3 详细设计
3.1 结构体定义
根据2.2存储结构设计,哈希表结构体的定义为:
typedef struct _htItem{ struct _htItem *next;//冲突时下一个 char *key_string;// uint fid;//卫星值 } htItem;
3.2 功能型函数
开户、销户等功能型函数较为简单,在此不做说明,其中存取函数利用哈希表特性,不是找到该元素直接更改卫星值值域,而是取出值域中的卫星值经过处理后,重新插入该元素,代码如下:
scanf("%d",&moneyAD); htItem *tmp = htGet(spAD, ht); moneyAD = moneyAD + (tmp->fid); htSet(spAD, moneyAD, ht);
3.3 数据结构型函数
3.3.1 初始化函数
根据2.3.2哈希表初始化思想,初始化函数如下:
//初始化HashTable void htInit(htItem **ht, uint length){ int i; for (i = 0; i<length; i++){ ht[i] = (htItem*)malloc(sizeof(htItem)); memset(ht[i], 0, sizeof(htItem));//用来对一段内存空间全部设置为某个字符 } ht[0]->fid = length;//第一个存长度 }
3.3.2 判断哈希值所在哈希表槽位
根据2.3.2判断哈希值所在哈希表槽位的思想,代码如下:
// get the index of hash table 根据得到的hash值选择一个槽位置 uint htIndex(char *key, htItem **ht){ uint hashedKey = bkdrHash(key); uint length = (ht[0]->fid - 1); return (uint)hashedKey % length + 1; //char转整数哈希值再用哈希函数定位槽位 //0号位存长度,对长度取余加1,一定落在槽位内 }
其中bkdrHash(key)函数为:
uint bkdrHash(char *key){ uint seed = 131; uint hash = 0; while (*key != '\n' && *key != 0){ //通常使用时,判别条件为*key != 0即可,此处的*key != '\n'是因程序需要 hash = hash * seed + (*key++); } return (hash & 0x7FFFFFFF); }
3.3.3 哈希表插入元素与删除元素
根据2.3.2哈希表插入元素与删除元素算法思想,以插入为例代码如下:
uint htSet(char *key, uint fid, htItem **ht){ uint i = htIndex(key, ht); htItem *item = ht[i]; while (item->next){ //已经存在的话则直接更新值 if (strcmp(key, item->next->key_string) == 0){ item->next->fid = fid; return 0; }else{ item = item->next; } } //处理冲突元素 item->next = (htItem*)malloc(sizeof(htItem)); item->next->fid = fid; item->next->key_string = key; item->next->next = NULL; return 0; }
3.3.4 哈希表查找元素
根据2.3.2哈希表查找元素思想,代码如下:
//get hashTable elements 进行对应的hash值的搜索,如果找到则返回该节点 htItem* htGet(char *key, htItem **ht){ uint i = htIndex(key, ht); htItem *item = ht[i]->next; htItem *tmp = (htItem*)malloc(sizeof(htItem)); memset(tmp, 0, sizeof(htItem));// 内存空间初始化 while (item){ if (strcmp(key, item->key_string) == 0){ return item; tmp->fid = item->fid; tmp->key_string = item->key_string; return tmp;// 返回地址 } item = item->next; } return NULL; }
3.3.5 返回所有元素个数与打印全部元素
根据2.3.2哈希表计算所有元素思想和打印所有元素思想,以计算所有元素个数为例,代码如下:
// get element number in the hashtable 所有元素总和个数 //遍历所有槽位和对应槽位冲突的元素 uint htLen(htItem **ht){ uint alength = ht[0]->fid; uint i, length = 0; for (i = 1; i < alength; i++){ if (ht[i]->next) length++; } return length; }
4 调试分析
4.1 测试数据
图6 测试
测试开户20190019开户金额为666666,并查询,输出金额为666666,结果正确,存取功能,存1元,查询余额为666667,取666667,余额为0,销户后,查询用户不存在,其他异常提示等功能因篇幅所限不在此贴图。
利用测试模块,功能选择输入未在列表中展示的54321,超级管理员密码为54321,自动模拟输入120个数据,其中哈希表主槽位120,一定产生冲突,然后再功能选择输入未在列表中展示的12345,超级管理员密码为12345,展示所有元素,显示数据为120条,结果正确。
4.2 调试与分析
问题1 :最开始申请内存为sp=(char *)malloc(sizeof(char)); 虽然达到可变空间的目的,但是考虑到不安全,实际申请1个char空间, 其他空间不一定没用,和char *p;直接用赋值是一样的错误,问题1应改为sp=(char *)malloc(ACCOUNT_NUM*sizeof(char));其中ACCOUNT_NUM可用define预设。
问题2:for{ scanf(sp); htSet(sp, tempmoney, item);}未申请空间,无论输入多少都是一个记录,结合问题1,因为没用申请空间,所以所有元素都覆盖在一个未知空间,问题2应在循环中加入申请空间。
问题3:do{scanf("%d", &funChoose); switch(funChoose){};}while{}输入1个数字非法字符,显示非法输入,正确;输入1个字母,就死循环,通过询问别人,加个getchar();吸收字符,解决;发现输入1个字符串就会空出字符串长度个循环,问题3应为while(getchar() != '\n');
时间复杂度为O(1),其他调试问题因篇幅所限就不在此一一列出,多数错误产生的原因因为对C语言理解不够透彻。通过查阅书籍笔记,和讨论都有所解决。
4.3 算法改进
算法中并未加入载荷因子,下一步改进可从数据结构方面,加入载荷因子,实现自动扩容,提高效率。
程序用C语言编写,并未涉及对文件的操作,所有存储均在内存进行,仅体现结构思想,下一步从C语言方面,可添加文件映射,提高实用性。
5小结
通过本次课程设计,对哈希表及数据结构这门课有了更进一步的理解,哈希表本质是一个数组,或者说数组是特殊的哈希表,哈希表最重要的部分是如何散列,本设计是用已有技术调用BKDRHash字符串散列,并未有自己实际创新算法。最开始想用Java实现,可以直接调用已有函数,根据课程要求,提交为.c文件,应该用C语言,感觉有种重复造轮子的感觉,后来实际编写时,查询对字符串散列的函数,发现有一个题为“HashMap在Java1.7与1.8中的区别的”博客,在Java1.8中使用一个Node数组来存储数据,这个Node可能是链表结构,也可能是红黑树结构,在Java1.7中是链表结构,向HashMap中put/get 1w条hashcode相同的对象JDK1.7: put 0.26s,get 0.55s,JDK1.8(未实现Compare接口):put 0.92s,get 2.1s;100W条hashcode相同的对象JDK1.8(正确实现Compare接口,):put/get大概开销都在320ms左右,结论是避免Hash Collision DoS攻击。然后想到最初重复造轮子的想法,底层的改变带来的影响是巨大的,好在Java目前还是开源的,倘若以后闭源,或者有看似非常好的闭源语言或者框架,盲目的直接调用,拿来主义,可能会产生不可计量的后果,因此底层的技术是其他取代不了的,数据结构和算法等基础课程更像是深度课程,归根到底还是数学,因此底层基础技术的学习是必要且重要的。
参考文献:
[1]严蔚敏,李冬梅,吴伟民.数据结构(C语言版)[J].计算机教育,2012(12):62.
[2]Thomas H.Cormen,Charles E.Leiserson,Ronald L.Rivest,Clifford Stein,殷建平,徐云,王刚,刘晓光,苏明,邹恒明,王宏志.算法导论(原书第3版)[J].计算机教育,2013(10):51.
[3] wiki.《Hash table》.
https://en.wikipedia.org/wiki/Hash_table.2019年5月20访问
[4]Mr.Wang.《HashMap 在 Java1.7 与 1.8 中的区别》.
https://www.cnblogs.com/justlove/p/7624455.html.2019年5月20访问
[5]灵剑.《hash算法的数学原理是什么,如何保证尽可能少的碰撞?》.
https://www.zhihu.com/question/20507188.2019年5月20访问
1 /* 2 作者: 3 赵忠瑞 4 5 功能: 6 哈希表实现活期储蓄帐目管理 7 8 具体要求: 9 数据结构课程设计 选题19. 活期储蓄帐目管理(限1 人完成) 哈希表 10 活期储蓄处理中,储户开户、销户、存入、支出活动频繁,系统设计要求: 11 1)能比较迅速地找到储户的帐户,以实现存款、取款记账; 12 2)能比较简单,迅速地实现插入和删除,以实现开户和销户的需要。 13 14 文件脉络: 15 part1 头文件及结构体定义 16 part2 函数声明 17 数据结构型函数声明 18 功能实现型函数声明 19 part3 main函数 20 part4 功能实现型函数 21 part5 数据结构型函数 22 */ 23 24 25 //———————part1—————————————— 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <memory.h> 29 #include <string.h> 30 #define ACCOUNT_NUM 16 //账号位数 31 32 typedef unsigned int uint; 33 //哈希表元素 34 typedef struct _htItem{ 35 struct _htItem *next;//冲突时下一个 36 char *key_string;// 37 uint fid;//卫星值 38 } htItem; 39 40 41 //————————part2————————————— 42 //---------下面是数据结构型函数声明------------- 43 void htInit(htItem **ht, uint length);// 构造函数,申请哈希表的空间 44 uint htSet(char *key, uint val, htItem **ht);//向哈希表中插入一个值 45 htItem* htGet(char *key, htItem **ht);//从哈希表中获得一个对应的key 46 int htDel(char *key, htItem **ht);//从哈希表中删除一个key 47 uint bkdrHash(char *key);//对string进行散列得到一个整数值 48 uint htIndex(char *key, htItem **ht);//根据key计算一个整数值,然后获得对应的槽位索引 49 uint htLen(htItem **ht);//求哈希表元素数 50 void print_hashTable(htItem **ht);//打印哈希表,测试 51 //-------下面是功能实现型函数声明--------- 52 void funFakeUI();//伪界面,为了使界面美观设计的伪UI 53 int funOpenAccount(htItem **ht);//开户函数 54 int funDelAccount(htItem **ht);//销户函数 55 int funAddDelMoney(htItem **ht);//存款取款的流水操作 56 int funSearch(htItem **ht);//查询余额操作 57 int funTestDisplay(htItem **ht);//超级管理员测试,打印所有数据 58 int funTestInput(htItem **ht);//超级管理员测试,模拟输入数据120个数据,一定冲突 59 60 61 //———————part3—————————————— 62 int main(void){ 63 htItem *item[101]; 64 htInit(item, 101);//初始化 65 int funChoose = 0; 66 funFakeUI();//伪UI 67 do{ 68 printf("\n\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); 69 printf("\n\t请选择功能:1开户 2销户 3存取 4查询 5退出\n\t功能选择: "); 70 funChoose = 0; 71 scanf("%d", &funChoose); 72 // getchar(); //吸收单个字符 73 while(getchar() != '\n'); //吸收字符串非法字符,否则死循环 74 switch(funChoose){ 75 case 1: 76 funOpenAccount(item); 77 break; 78 case 2: 79 funDelAccount(item); 80 break; 81 case 3: 82 funAddDelMoney(item); 83 break; 84 case 4: 85 funSearch(item); 86 break; 87 case 5: 88 printf("\n\t欢迎再次使用!\n"); 89 return 0; 90 case 54321://测试项,模拟输入120个数据,一定冲突 91 funTestInput(item); 92 break; 93 case 12345://测试项,展示所有数据 94 funTestDisplay(item); 95 break; 96 default: 97 printf("\n\t非法操作!"); 98 } 99 }while(funChoose != 5); 100 } 101 102 103 //——————part4 功能实现型函数——————————— 104 /*******************************/ 105 //伪UI 106 void funFakeUI(){ 107 printf("\n\n----------------欢迎使用活期储蓄帐目管理模拟程序----------------\n");//more to develop 108 } 109 110 /********************************/ 111 //开户 112 int funOpenAccount(htItem **ht){ 113 //printf("fun 开户 begin\n"); 114 int iAccountAdd,iAccountAddSum; 115 printf("\n\n\t请输入预开户账号总数\n\t本次开户总数:"); 116 scanf("%d",&iAccountAddSum); 117 for(iAccountAdd=0; iAccountAdd<iAccountAddSum; iAccountAdd++) { 118 printf("\n\t\t请输入预开户%d的账号和开户金额:\n",iAccountAdd+1); 119 char *sp; 120 sp=(char *)malloc(ACCOUNT_NUM*sizeof(char)); 121 int tempmoney; 122 printf("\t\t预开户账号:"); 123 scanf("%s",sp); 124 if (htGet(sp, ht)){ 125 //检查账户是否存在 126 printf("\t\t该账户已存在!\n\t\t------------------------------"); 127 continue; 128 //break; 129 //return 0; 130 } 131 printf("\t\t开户金额: "); 132 scanf("%d",&tempmoney); 133 htSet(sp, tempmoney, ht); 134 printf("\t\t开户完成!\n\t\t------------------------------"); 135 } 136 // printf("\t系统所有开户信息为:\n"); 137 // print_hashTable(ht); 138 // printf("fun 开户 end\n\n\n"); 139 } 140 141 142 /*******************************/ 143 //销户 144 int funDelAccount(htItem **ht){ 145 // printf("fun 销户 begin\n"); 146 int iAccountDel,iAccountDelSum; 147 printf("\n\t请输入本次销户账号总数:\n\t销户总数:"); 148 scanf("%d",&iAccountDelSum); 149 printf("\n\t友情提示,谨慎操作!\n\n"); 150 for(iAccountDel=0; iAccountDel<iAccountDelSum; iAccountDel++) { 151 printf("\n\t\t请输入预销户%d的账号:\n",iAccountDel+1); 152 char *sp; 153 sp=(char *)malloc(ACCOUNT_NUM*sizeof(char)); 154 int tempmoney; 155 printf("\t\t预销户账号:"); 156 scanf("%s",sp); 157 htItem *tmp; 158 if ( tmp = htGet(sp, ht)){ 159 if(tmp->fid) 160 printf("\t\t用户 %s 的余额为 %d ,非空,取出余额再确认销户\n", tmp->key_string, tmp->fid); 161 else{ 162 htDel(sp, ht); 163 free(sp); 164 printf("\t\t销户成功!\n"); 165 } 166 } 167 else 168 printf("\t\t没有此用户! 请确认信息重新操作\n"); 169 printf("\t\t-----------------------------------------\n"); 170 } 171 // printf("\t系统所有开户信息为:\n"); 172 // print_hashTable(ht); 173 //printf("fun 销户 end\n\n\n"); 174 } 175 176 177 /**********************************/ 178 //存取流水 179 int funAddDelMoney(htItem **ht){ 180 //printf("fun 流水 begin\n\n\n"); 181 printf("\n\t请输入预存取的账号: "); 182 char *spAD; 183 spAD=(char *)malloc(ACCOUNT_NUM*sizeof(char)); 184 scanf("%s",spAD); 185 //htItem *tmpTestNull; 186 if (!htGet(spAD, ht)){ 187 printf("\t没有此用户! 请确认信息重新操作\n"); 188 return 0; 189 } 190 printf("\t请输入流水类型:1存款 2取款\n\t流水类型: ") ; 191 int ADkind; 192 scanf("%d", &ADkind); 193 printf("\t请输入存取的金额:"); 194 int moneyAD = 0; 195 scanf("%d",&moneyAD); 196 htItem *tmp = htGet(spAD, ht); 197 switch(ADkind){ 198 case 1: 199 moneyAD = moneyAD + (tmp->fid); 200 htSet(spAD, moneyAD, ht); 201 printf("\t存款成功\n"); 202 break; 203 case 2: 204 if((moneyAD = (tmp->fid) - moneyAD) >= 0){ 205 htSet(spAD, moneyAD, ht); 206 printf("\t取款成功\n"); 207 }else 208 printf("\t余额不足\n"); 209 break; 210 default: 211 printf("\t非法输入\n"); 212 return 0; 213 } 214 // printf("系统所有开户信息为:\n"); 215 // print_hashTable(ht); 216 } 217 218 /****************************/ 219 //查询 220 int funSearch(htItem **ht){ 221 printf("\n\t请输入要查询的账号:"); 222 char *sp; 223 sp=(char *)malloc(ACCOUNT_NUM*sizeof(char)); 224 scanf("%s",sp); 225 htItem *tmp; 226 if ( tmp = htGet(sp, ht)){ 227 if(tmp->fid) 228 printf("\t用户 %s 的余额为 %d \n", tmp->key_string, tmp->fid); 229 else{ 230 printf("\t账户有误\n"); 231 } 232 } 233 else 234 printf("\t没有此用户! 请确认信息重新操作\n"); 235 } 236 237 /****************************/ 238 //超级管理员测试,打印所有数据 239 int funTestDisplay(htItem **ht){ 240 printf("\n\t\t######################################\n\n\t\t{ 请输入超级管理员密码 }\t") ; 241 int pw; 242 scanf("%d", &pw); 243 while(getchar() != '\n'); //吸收非法字符串 244 if(pw == 12345) 245 print_hashTable(ht); 246 //return 0; 247 printf("\n\t\t######################################\n") ; 248 } 249 250 //超级管理员测试,模拟输入120个数据一定会产生冲突 251 int funTestInput(htItem **ht){ 252 printf("\n\t\t######################################\n\n\t\t{ 请输入超级管理员密码 }\t") ; 253 int pw; 254 scanf("%d", &pw); 255 while(getchar() != '\n'); //吸收非法字符串 256 if(pw == 54321){ 257 //模拟输入120个数据———————————————————————————————————————— 258 int iinput = 0; 259 for(iinput=0; iinput<120; iinput++){ 260 char *sp; 261 sp=(char *)malloc(ACCOUNT_NUM*sizeof(char)); 262 int account = 20190000 + iinput; //模拟账号 263 ltoa(account,sp,10); 264 // 将长整型值转换为字符串 265 //char *__cdecl ltoa(long _Val,char *_DstBuf,int _Radix) __MINGW_ATTRIB_DEPRECATED_MSVC2005; 266 htSet(sp, iinput, ht); 267 } 268 printf("\n\t\t模拟输入完成\n"); 269 } 270 printf("\n\t\t######################################\n") ; 271 } 272 273 274 275 //————————part5 数据结构型函数————————————————————————————————/ 276 /***************************/ 277 //初始化HashTable 278 void htInit(htItem **ht, uint length){ 279 int i; 280 for (i = 0; i<length; i++){ 281 ht[i] = (htItem*)malloc(sizeof(htItem)); 282 memset(ht[i], 0, sizeof(htItem));//用来对一段内存空间全部设置为某个字符 283 } 284 ht[0]->fid = length;//第一个存长度 285 } 286 287 /******************************/ 288 //get hashTable elements 进行对应的hash值的搜索,如果找到则返回该节点 289 htItem* htGet(char *key, htItem **ht){ 290 uint i = htIndex(key, ht); 291 htItem *item = ht[i]->next; 292 htItem *tmp = (htItem*)malloc(sizeof(htItem)); 293 memset(tmp, 0, sizeof(htItem));// 内存空间初始化 294 while (item){ 295 if (strcmp(key, item->key_string) == 0){ 296 return item; 297 tmp->fid = item->fid; 298 tmp->key_string = item->key_string; 299 return tmp;// 返回地址 300 } 301 item = item->next; 302 } 303 return NULL; 304 } 305 306 /*****************************/ 307 // set hashTable element 插入新的hash值 308 uint htSet(char *key, uint fid, htItem **ht){ 309 uint i = htIndex(key, ht); 310 htItem *item = ht[i]; 311 while (item->next){ 312 //已经存在的话则直接更新值 313 if (strcmp(key, item->next->key_string) == 0){ 314 item->next->fid = fid; 315 return 0; 316 }else{ 317 item = item->next; 318 } 319 } 320 //处理冲突元素 321 item->next = (htItem*)malloc(sizeof(htItem)); 322 item->next->fid = fid; 323 item->next->key_string = key; 324 item->next->next = NULL; 325 return 0; 326 } 327 328 /******************************/ 329 // delete one element of hashtable 删除hash值 330 int htDel(char *key, htItem **ht){ 331 uint i = htIndex(key, ht); 332 htItem *item = ht[i]; 333 while (item->next){ 334 if (strcmp(key, item->next->key_string) == 0){ 335 htItem *tmp = item->next; 336 item->next = tmp->next; 337 free(tmp); 338 return 0; 339 } 340 item = item->next; 341 } 342 return -1; 343 } 344 345 /********************************/ 346 //常用字符串哈希函数有 BKDRHash,APHash,DJBHash,JSHash,RSHash,SDBMHash,PJWHash,ELFHash等, 347 // 本程序采用 BKDR hash function 对字符串进行散列,得到一个整数的hash值 348 uint bkdrHash(char *key){ 349 uint seed = 131; 350 uint hash = 0; 351 while (*key != '\n' && *key != 0){ 352 //通常使用时,判别条件为*key != 0即可,此处的*key != '\n'是因程序需要 353 hash = hash * seed + (*key++); 354 } 355 return (hash & 0x7FFFFFFF); 356 } 357 358 /******************************/ 359 // get the index of hash table 根据得到的hash值选择一个槽位置 360 uint htIndex(char *key, htItem **ht){ 361 uint hashedKey = bkdrHash(key); 362 uint length = (ht[0]->fid - 1); 363 return (uint)hashedKey % length + 1; //char转整数哈希值再用哈希函数定位槽位 364 //0号位存长度,对长度取余加1,一定落在槽位内 365 } 366 367 /******************************/ 368 // get element number in the hashtable 所有元素总和个数 369 //遍历所有槽位和对应槽位冲突的元素 370 uint htLen(htItem **ht){ 371 uint alength = ht[0]->fid; 372 uint i, length = 0; 373 for (i = 1; i < alength; i++){ 374 if (ht[i]->next) { 375 length++; 376 } 377 } 378 return length; 379 } 380 381 /**************************/ 382 // get capacity of hashtable 哈希表主槽容积 383 uint htCapacity(htItem **ht){ 384 return ht[0]->fid; 385 } 386 387 388 /****************************/ 389 //print hashTable 打印哈希表 *用于测试 390 void print_hashTable(htItem **ht){ 391 uint length = ht[0]->fid; 392 uint i; 393 htItem *item; 394 printf("\n\t\t哈希表所有数据:\n"); 395 /* 1.遍历主槽, 396 2.主槽为null时遍历下一个主槽, 397 3.主槽不为null时,打印该元素 398 4.检查该元素是否存在冲突元素,即该元素的指针域是否为null 399 5.重复1-4 400 */ 401 for (i = 1; i < length; i++){ 402 item = ht[i]->next; 403 while (item){ 404 printf("\t\t%s --> %d\n", item->key_string, item->fid); 405 item = item->next; 406 } 407 } 408 } 409 410 411 //————————文件结束————————— 412 413 414 415 /*其他参考 416 https://en.wikipedia.org/wiki/Hash_table 417 https://github.com/utx201777/Daily 418 https://www.cnblogs.com/heyonggang/p/3419574.html 419 https://www.cnblogs.com/justlove/p/7624455.html 420 https://cloud.tencent.com/developer/article/1092226 421 https://www.cnblogs.com/liuliuliu/p/3966851.html 422 https://www.cnblogs.com/stevenczp/p/7028071.html 423 http://www.partow.net/programming/hashfunctions/#AvailableHashFunctions 424 https://blog.csdn.net/cy_tec/article/details/51202140 425 https://blog.csdn.net/fcbarcelonalove/article/details/84578418 426 https://www.zhihu.com/question/20507188 427 https://www.byvoid.com/zhs/blog/string-hash-compare 428 */