基于哈希表的用户管理系统
三大模块:
- 哈希表模块
哈希函数
哈希表创建
哈希表销毁
- 用户管理模块
显示
增
删
改
查
- 文件模块
从文件导入用户信息
将用户信息导出至文件
1.哈希函数
//hash函数(质数除余法)
int Hash_Fun1(data_type key){int pos = key%P;return pos;}
主要思想是将待哈希的数据转化为一个整数,
然后将该整数与一个质数进行取余运算,
最终得到的余数就是该数据的哈希值。
具体实现流程如下:
-
定义一个质数p,通常取一个较大的质数,以尽量减少哈希冲突的概率。
-
对于待哈希的数据,例如一个字符串,可以将其转化为一个整数,一般使用ASCII码的值或Unicode码的值等方式将字符串转换为整数。
-
将该整数与质数p进行取余运算,得到的余数即为该数据的哈希值。
优点:简单、计算速度快,适用于键值分布比较均匀的场景,
但如果哈希表中数据分布不均匀,容易导致哈希冲突,影响哈希表的性能。
//改进质数取余法(字符串转为哈希值)
int Hash_Fun(char* str){int hash = 0;while(*str){hash = hash * 31 + (*str++);}return hash%HASH_SIZE;
}
遍历字符,将其ASCII码值乘以质数31,
再加上前面所有字符的哈希值,
最后再对哈希值进行取余运算,得到最终的哈希值。
使用质数31作为哈希函数的乘数
可以使得哈希函数更加均匀地分布哈希值,避免冲突的概率更小。
31是一个质数,它的二进制表示为0001 1111。
乘以31相当于将数值左移5位再减去原值,即x * 31 = (x << 5) - x。
因此,使用31作为乘数可以有效地减少乘法操作的计算量,同时还能够使得哈希值更加分散。
2.创建哈希表
//创建哈希表
Hash* Creat_Hash(void){//1.定义Hash表的头,存储哈希表首地址和尺寸Hash* pHash = (Hash*)malloc(sizeof(Hash));if(NULL == pHash){perror("malloc error");return NULL;}memset(pHash, 0, sizeof(Hash));//2.pHash的两个成员需分别赋值//arr是一个指向数组的指针,数组名也是数组首地址,所以是**//本身的数据类型是LinkNode**,指向LinkNode*pHash->arr = (LinkNode**)malloc(sizeof(LinkNode*)*HASH_SIZE);//此处要对空间初始化,否则显示时会段错误,因为只要非NULL,//就会访问结构体信息,而此时结构体是NULLmemset(pHash->arr, 0, sizeof(LinkNode*)*HASH_SIZE);if(NULL == pHash->arr){perror("malloc error");return NULL;}pHash->count = HASH_SIZE;return pHash;
}
首先,申请一片空间(Hash* pHash)用于存储哈希表所在位置的首地址,
以及定义变量(int count)存储哈希表中链表的个数。
而后,再申请一片连续的空间存储链表首结点的地址,
连续空间大小为HASH_SIZEsizeof( LinkNode ),
HASH_SIZE是存储链表的数量。
最后,给结构体中的count赋值为HASH_SIZE。
3.销毁哈希表
//销毁所有用户信息,传入哈希表首地址的地址
int Destroy_Hash(Hash** pHash){//1.判断入参空否if(NULL == *pHash){return NULL_ERROR;}//2.遍历哈希表int i = 0;for( i=0; i<HASH_SIZE; i++ ){LinkNode* pTemp = (*pHash)->arr[i]; //定义临时指针来遍历哈希表while(pTemp != NULL){LinkNode* pDel = pTemp; //删除指针来删除pTemp = pTemp->pNext; //头删法,先连free(pDel); //后释放}}free((*pHash)->arr); //释放存储顺表地址的空间(*pHash)->arr = NULL; free(*pHash); //释放哈希表*pHash = NULL;return OK;
}
哈希表申请了三次空间,
第一次是哈希表本身,
第二次是顺表的空间,
第三次是添加新用户时,申请新结点。
释放空间时,要确保申请每个空间都被释放了。
因此,对顺表遍历的同时,也要对后面的链表遍历。
由于顺表的大小是确定的,用for循环遍历,
而链表则用while,定义一个临时指针来遍历、释放。
执行完嵌套的循环遍历后,
最后释放pHash->arr和pHash。
4.显示用户信息
//显示哈希表
int Display_Hash (Hash* pHash){//1.入参是否存在if(NULL == pHash){return NULL_ERROR;}//2.遍历显示printf("**************用户信息****************\n");int i = 0, j=1;for( i=0; i<HASH_SIZE; i++ ){LinkNode* pNode = pHash->arr[i];while(pNode != NULL){if( pNode->data.name!=NULL && pNode->data.password && pNode->data.mail){printf("用户%d:%s\t 密码:%s\t邮箱:%s\n", j++, \pNode->data.name, pNode->data.password, \pNode->data.mail);pNode = pNode->pNext;}}}printf("显示完毕!\n");return OK;
}
5.增加用户信息
//插入哈希表
int Insert_Hash(Hash* pHash, User_Inf item){//1.判断Hash表是否存在if(NULL == pHash){return NULL_ERROR;}//2.查找hash,若存在,返回无需插入if(Search_Hash(pHash, item.name) != NULL){printf("已存在,无需插入!\n");return NULL_ERROR;}//3.创建新节点pNew,并存入数据LinkNode* pNew = (LinkNode*)malloc(sizeof(LinkNode));if(NULL == pNew){perror("malloc error");return MALLOC_ERROR;}memset(pNew, 0, sizeof(LinkNode));pNew->data = item;//结构体可以直接赋值?//4.通过hash函数获得要存入的位置int pos = -1;pos = Hash_Fun(item.name);//5.将新节点,以头插方式插入hash表pNew->pNext = pHash->arr[pos];pHash->arr[pos] = pNew;return OK;
}
首先,调用查找函数,如果找到了,说明链表中有该用户,无需添加;
如果没有,则创建新结点,将用户信息存入后,
调用哈希函数获取存入的位置,采用头插法添加用户信息。
6.删除用户信息
//删除哈希表
int Del_Hash(Hash* pHash, char* user_name){//1.判断入参空否if(NULL == pHash){return NULL_ERROR;}//2.获得哈希值int pos = Hash_Fun(user_name);//3.遍历该位置上的链表LinkNode* pDel = pHash->arr[pos];//删除指针指向第一个结点LinkNode* pPre = NULL;//前继指针指向第二while(pDel != NULL ){if( 0 == strcmp(pDel->data.name, user_name) ){break;}pPre = pDel;pDel = pDel->pNext;}//4.若pDel为空,则要删除的不存在if(NULL == pDel){return NULL_ERROR;}//5.若pPre为空,说明找到的是第一个节点//否则,改变前一个节点的pNextif(NULL == pPre){pHash->arr[pos] = pDel->pNext;}else{pPre->pNext = pDel->pNext;}//打印删除结点信息printf("删除的是:\n");printf("用户名:%s\t密码:%s\t邮箱:%s\n", \pDel->data.name, pDel->data.password, pDel->data.mail);//6.释放结点free(pDel);pDel = NULL;return OK;
}
首先,定义两个指针,
LinkNode* pDel指向链表首节点,也就是pHash->arr[pos]中的地址(可能为NULL),
LinkNode* pPre是前驱结点,初始化为NULL。
而后,利用这两个指针对链表进行遍历:
从首结点开始strcmp需要的用户名,如果找到就跳出循环,
否则两个指针依次向后移动。
跳出循环有三种情况:
- pDel为空,也就是没有该用户,退出程序。
- 哈希值位置的第一个节点就是,pPre没有移动,还是NULL,此时用头删法进行删除操作,即顺序表中元素指向找到节点的下一节点(可能为空),再free。
- 哈希值位置的链表有N个节点,在链表中间,或最后找到了,此时pDel指向的是需要删除的用户。先将pPre指向pDel后的结点,再free(pDel),是最后一个结点也不影响
7.修改用户信息
//修改用户信息
int Modify_Hash(Hash* pHash, char* Name){//1.判断入参空否if(NULL == pHash || NULL == Name){return NULL_ERROR;}//2.获取哈希值int pos = Hash_Fun(Name);//3.通过顺序表找到用户LinkNode* pTemp = pHash->arr[pos];while(pTemp != NULL){if( 0 == strcmp(pTemp->data.name, Name) ){printf("该用户信息如下:\n");printf("%s ", pTemp->data.name);printf("%s ", pTemp->data.password);printf("%s ", pTemp->data.mail);// Del_Hash(pHash, pTemp->data.name);printf("\n输入新的用户信息\n");scanf("%s", pTemp->data.name);scanf("%s", pTemp->data.password);scanf("%s", pTemp->data.mail);//更改信息后,要将新的用户信息放到对应位置,并删除就结点,否则显示还会显示// Insert_Hash(pHash, pTemp->data);return OK;}pTemp = pTemp->pNext;}//4.若pTemp为空,则未找到该用户if(NULL == pTemp){return EMPTY;}return -1;
}
首先,调用哈希函数获取存储该用户信息的位置,
而后定义一个临时指针用于遍历链表,
如果找到,提示输入新信息;
如果没找到,指针向后移动;
直到指向NULL,说明没有该用户。
8.查找某用户
//查找哈希表
LinkNode* Search_Hash(Hash* pHash, char* user_name){//1.判断hash表是否存在if(NULL == pHash){return NULL;}//2.通过hash函数获得user_name所在位置posint pos = Hash_Fun(user_name);//3.通过pos获得链表中的首地址LinkNode* pTemp = pHash->arr[pos];//4.遍历链表至找到该值while(pTemp != NULL){if( 0 == strcmp(user_name, pTemp->data.name) {return pTemp;}pTemp = pTemp->pNext;}return NULL;
}
首先,调用哈希函数获取存储该用户信息的位置,
而后定义一个临时指针用于遍历链表,
如果找到,返回该指针;
如果没找到,指针向后移动;
直到指向NULL,说明没有该用户。
9.导出用户信息
//导出信息
int Output_Data(Hash* pHash, char* filename){//1.判断入参空否if(NULL == pHash || NULL == filename){return NULL_ERROR;}//2.打开文件FILE* fp = fopen(filename, "w");if(NULL == fp){perror("open error");return NULL_ERROR;}//3.遍历哈希表,存入信息int i = 0;for( i=0; i<HASH_SIZE; i++){LinkNode* pTemp = pHash->arr[i];while(pTemp != NULL){User_Inf user = pTemp->data;fprintf(fp, "用户名:%s 密码:%s 邮箱:%s\n", \user.name, user.password, user.mail);pTemp = pTemp->pNext;}}fclose(fp);return OK;
}
遍历整个哈希表,将其中的信息输出至指定文件中,
首先以只写方式打开指定文件,
如果不存在就创建一个,
每次写入信息时,清空上次的内容。
遍历每个结点时,调用fprintf函数将该名用户的信息输出至文件中,
末尾加上”\n”,最后关闭文件。
10.导入用户信息
//导入信息
int Input_Data(Hash* pHash, char* filename){//1.判断入参空否if(NULL == pHash || NULL == filename){return NULL_ERROR;}//2.打开文件FILE* fp = fopen(filename, "r");if(NULL == fp){perror("open error");return OPEN_ERROR;}//3.读取文件信息,文件中每行一个用户,信息间用空格隔开int MAX = 70;char Line[MAX];//缓冲区:存储按行读的信息while( fgets(Line, MAX, fp)){//从文件读取不为空,就一直取char Name[N];char Password[N];char Mail[N];//如果解析不出3个字符串,结束本次循环,继续下一次if( sscanf(Line, "%s %s %s",Name, Password, Mail) != 3){continue;}User_Inf user;strcpy(user.name, Name);strcpy(user.password, Password);strcpy(user.mail, Mail);//4.插入信息int ret = Insert_Hash(pHash, user);if(OK != ret){fclose(fp);return ret;}}fclose(fp);return OK;
}
首先,打开一个文件,调用fgets函数按行读取文件内容,
再调用sscanf将读取到的每行内容解析为三个字符串,
分别为name, password, mail,
并赋值给一个定义的结构体,
而后,调用增加用户函数将结构体信息添加到哈希表中,
如果插入错误就关闭文件。
相关文章:
基于哈希表的用户管理系统
三大模块: - 哈希表模块 哈希函数 哈希表创建 哈希表销毁 - 用户管理模块 显示 增 删 改 查 - 文件模块 从文件导入用户信息 将用户信息导出至文件 1.哈希函数 //hash函数(质数除余法) int Hash_Fun1(data_type key){int pos key%P;…...
GO数组切片-线性数据结构
数据结构 类型 什么是类型 ? 内存中的二进制数据本身没有什么区别,就是一串0或1的组合。 内存中有一个字节内容是0x63,他究竟是深恶 字符串?字符?还是整数? 本来0x63表示数字 但是文字必须编码成为0和1的组合 才能记…...
C++ STL学习之【优先级队列】
✨个人主页: 北 海 🎉所属专栏: C修行之路 🎃操作环境: Visual Studio 2019 版本 16.11.17 文章目录 🌇前言🏙️正文1、优先级队列的使用1.1、基本功能1.2、优先级模式切换1.3、相关题目 2、模拟…...
keepalived脑裂现象
Keepealived最常见的问题是会出现脑裂现象: Master一直发送心跳消息给backup主机,如果中间的链路突然断掉,backup主机将无法收到master主机发送过来的心跳消息(也就是vrrp报文),backup这时候会立即抢占mas…...
[stable-diffusion-art] 指北-1
https://stable-diffusion-art.com/beginners-guide/https://stable-diffusion-art.com/beginners-guide/ Stable Diffusion教程目录 - 知乎按: 这个外国教程站中的文章太好了,数量适当,质量很高可惜博文只能按时间浏览,所以整理…...
「C/C++」C/C++预处理器
博客主页:何曾参静谧的博客 文章专栏:「C/C」C/C学习 目录 一、宏替换 #define1. 定义常量2. 定义函数3. 定义代码块 二、条件编译 #if1. 使用 #ifdef、 #else 和 #endif2. 使用 #if 、#elif、#else和 #endif 编译不同版本的代码3. 使用 #ifndef 和 #def…...
java语言入门教程文章
好的,以下是Java语言入门教程: Java是一种高级编程语言,由Sun Microsystems于1995年推出。Java语言具有良好的可移植性和安全性,因此被广泛应用于Web应用程序、移动应用程序、企业应用程序等各个领域。本教程将带领初学者快速入门…...
基于灰狼算法的极限学习机(ELM)回归预测-附代码
基于灰狼算法的极限学习机(ELM)回归预测 文章目录 基于灰狼算法的极限学习机(ELM)回归预测1.极限学习机原理概述2.ELM学习算法3.回归问题数据处理4.基于灰狼算法优化的ELM5.测试结果6.参考文献7.Matlab代码 摘要:本文利用灰狼算法对极限学习机进行优化,并…...
【五一创作】ERP实施-委外业务-委外采购业务
委外业务主要有两种业务形态:委外采购和工序外协,委外采购主要是在MM模块中实现,工序外协主要由PP模块实现,工序外协中的采购订单创建和采购收货由MM模块实现。 委外采购概念 委外采购,有些企业也称为带料委外或者分包…...
DAY 54 数据库基础
数据库的基本概念 数据(Data): 描述事务的符号记录包括数字、文字、图形、图像、声音、档案记录以”记录“形式按统一的格式进行存储 表: 将不同的记录组织在一起用来存储具体数据 数据库: 表的集合,…...
网络编程 总结二
一、TCP TCP模型 1. TCP搭建相关函数: 套接字Socket 1)Socket函数: 2)bind 3)listen 4)accept 5)recv 注意: 1> TCP中的recv 可以替换成read; 2>TCP中的…...
消息称苹果Type-C口充电未设MFi限制,iOS17将更新Find My服务
根据国外科技媒体 iMore 报道,基于消息源 analyst941 透露的信息,苹果公司目前并未开发 MFi 限制。 根据推文信息内容,两款 iPhone 15 机型的最高充电功率为 20W,而 iPhone 15 Pro 机型的最高支持 27W 充电。 此前古尔曼表示苹…...
设计模式——工厂模式(简单工厂、工厂方法、抽象工厂)
是什么? 工厂模式的目的是将创建对象的具体过程隐藏起来,从而达到更高的灵活性 工厂模式分为:简单工厂模式、工厂方法模式、抽象工厂模式; 为什么? 在Java中,万物皆是对象,我们在使用的时候…...
《C语言技术体系》 学习路线总目录 + 思维导图
目录 前言 正文 思维导图 第1章 流程结构 1.1 初识C语言 1.2 流程结构 1.3 数据类型 1.4 运算符表达式 第2章 指针与数组 2.1 指针基本概念 2.2 一维数组 2.3 二维及多维数组 2.4 指针与数组 第3章 模块化重构 3.1 函数 3.2 typedef类型定义 3.3 enum枚举 3.…...
数字图像处理简答题
目录 1.人类视觉对颜色的主观感觉包括哪三类? 2. 图像成像的过程包括哪三步? 3.图像的采样和量化分别指什么? 4、取k8时,将下图用相应矩阵表示 5、简述当限定了数字图像的数据量时采样和量化参数的选择遵循哪两条原则&#x…...
【Java校招面试】基础知识(五)——GC
目录 前言一、基础概念二、垃圾回收算法三、垃圾收集器四、引用后记 前言 本篇主要介绍Java垃圾回收机制——GC的相关内容。 “基础知识”是本专栏的第一个部分,本篇博文是第五篇博文,如有需要,可: 点击这里,返回本专…...
使用CMake调用Makefile 项目
目录标题 基本示例Cmake传递lib给MakefileCmake传递参数给Makefile,比如make cleanWindows下使用Cmake调用Makefile 基本示例 如果项目是使用传统的Makefile构建的,并且您希望使用CMake调用这些Makefile,您可以使用CMake的add_custom_target…...
快速傅里叶变换FFT学习笔记
点值表示法 我们正常表示一个多项式的方式,形如 A ( x ) a 0 a 1 x a 2 x 2 . . . a n x n A(x)a_0a_1xa_2x^2...a_nx^n A(x)a0a1xa2x2...anxn,这是正常人容易看懂的,但是,我们还有一种表示法。 我们知道…...
如何下载安装驱动
1 打开浏览器 这里以Edge浏览器举例 第一步打开桌面上的Edge浏览器 如果您的桌面上没有 那么找到搜索栏 搜索Edge 然后打开 打开之后一般是这样 然后把我发送您的地址 驱动下载地址 https://t.lenovo.com.cn/yfeyfYyD (这个网址只是一个例子) 删除掉前…...
鸿蒙Hi3861学习四-Huawei LiteOS介绍
一、什么是LitesOS Huawei LiteOS是华为针对物联网领域推出的轻量级物联网操作系统,是华为物联网战略的重要组成部分,具备轻量级、低功耗、互联互通、组件丰富、快速开发等关键能力。基于物联网领域业务特征打造领域性技术栈,为开发者提供“一…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...
html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
代码规范和架构【立芯理论一】(2025.06.08)
1、代码规范的目标 代码简洁精炼、美观,可持续性好高效率高复用,可移植性好高内聚,低耦合没有冗余规范性,代码有规可循,可以看出自己当时的思考过程特殊排版,特殊语法,特殊指令,必须…...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...
