当前位置: 首页 > news >正文

基于哈希表的用户管理系统

三大模块:

- 哈希表模块

哈希函数
哈希表创建
哈希表销毁

- 用户管理模块

显示



- 文件模块

从文件导入用户信息
将用户信息导出至文件

1.哈希函数

//hash函数(质数除余法)
int Hash_Fun1(data_type key){int pos = key%P;return pos;}
主要思想是将待哈希的数据转化为一个整数,
然后将该整数与一个质数进行取余运算,
最终得到的余数就是该数据的哈希值。
具体实现流程如下:
  1. 定义一个质数p,通常取一个较大的质数,以尽量减少哈希冲突的概率。

  2. 对于待哈希的数据,例如一个字符串,可以将其转化为一个整数,一般使用ASCII码的值或Unicode码的值等方式将字符串转换为整数。

  3. 将该整数与质数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需要的用户名,如果找到就跳出循环,

否则两个指针依次向后移动。

跳出循环有三种情况:

  1. pDel为空,也就是没有该用户,退出程序。
  2. 哈希值位置的第一个节点就是,pPre没有移动,还是NULL,此时用头删法进行删除操作,即顺序表中元素指向找到节点的下一节点(可能为空),再free。
  3. 哈希值位置的链表有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,

并赋值给一个定义的结构体,

而后,调用增加用户函数将结构体信息添加到哈希表中,

如果插入错误就关闭文件。

相关文章:

基于哈希表的用户管理系统

三大模块&#xff1a; - 哈希表模块 哈希函数 哈希表创建 哈希表销毁 - 用户管理模块 显示 增 删 改 查 - 文件模块 从文件导入用户信息 将用户信息导出至文件 1.哈希函数 //hash函数&#xff08;质数除余法&#xff09; int Hash_Fun1(data_type key){int pos key%P;…...

GO数组切片-线性数据结构

数据结构 类型 什么是类型 &#xff1f; 内存中的二进制数据本身没有什么区别&#xff0c;就是一串0或1的组合。 内存中有一个字节内容是0x63&#xff0c;他究竟是深恶 字符串?字符&#xff1f;还是整数&#xff1f; 本来0x63表示数字 但是文字必须编码成为0和1的组合 才能记…...

C++ STL学习之【优先级队列】

✨个人主页&#xff1a; 北 海 &#x1f389;所属专栏&#xff1a; C修行之路 &#x1f383;操作环境&#xff1a; Visual Studio 2019 版本 16.11.17 文章目录 &#x1f307;前言&#x1f3d9;️正文1、优先级队列的使用1.1、基本功能1.2、优先级模式切换1.3、相关题目 2、模拟…...

keepalived脑裂现象

Keepealived最常见的问题是会出现脑裂现象&#xff1a; Master一直发送心跳消息给backup主机&#xff0c;如果中间的链路突然断掉&#xff0c;backup主机将无法收到master主机发送过来的心跳消息&#xff08;也就是vrrp报文&#xff09;&#xff0c;backup这时候会立即抢占mas…...

[stable-diffusion-art] 指北-1

https://stable-diffusion-art.com/beginners-guide/https://stable-diffusion-art.com/beginners-guide/ Stable Diffusion教程目录 - 知乎按&#xff1a; 这个外国教程站中的文章太好了&#xff0c;数量适当&#xff0c;质量很高可惜博文只能按时间浏览&#xff0c;所以整理…...

「C/C++」C/C++预处理器

博客主页&#xff1a;何曾参静谧的博客 文章专栏&#xff1a;「C/C」C/C学习 目录 一、宏替换 #define1. 定义常量2. 定义函数3. 定义代码块 二、条件编译 #if1. 使用 #ifdef、 #else 和 #endif2. 使用 #if 、#elif、#else和 #endif 编译不同版本的代码3. 使用 #ifndef 和 #def…...

java语言入门教程文章

好的&#xff0c;以下是Java语言入门教程&#xff1a; Java是一种高级编程语言&#xff0c;由Sun Microsystems于1995年推出。Java语言具有良好的可移植性和安全性&#xff0c;因此被广泛应用于Web应用程序、移动应用程序、企业应用程序等各个领域。本教程将带领初学者快速入门…...

基于灰狼算法的极限学习机(ELM)回归预测-附代码

基于灰狼算法的极限学习机(ELM)回归预测 文章目录 基于灰狼算法的极限学习机(ELM)回归预测1.极限学习机原理概述2.ELM学习算法3.回归问题数据处理4.基于灰狼算法优化的ELM5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;本文利用灰狼算法对极限学习机进行优化&#xff0c;并…...

【五一创作】ERP实施-委外业务-委外采购业务

委外业务主要有两种业务形态&#xff1a;委外采购和工序外协&#xff0c;委外采购主要是在MM模块中实现&#xff0c;工序外协主要由PP模块实现&#xff0c;工序外协中的采购订单创建和采购收货由MM模块实现。 委外采购概念 委外采购&#xff0c;有些企业也称为带料委外或者分包…...

DAY 54 数据库基础

数据库的基本概念 数据&#xff08;Data&#xff09;&#xff1a; 描述事务的符号记录包括数字、文字、图形、图像、声音、档案记录以”记录“形式按统一的格式进行存储 表&#xff1a; 将不同的记录组织在一起用来存储具体数据 数据库&#xff1a; 表的集合&#xff0c;…...

网络编程 总结二

一、TCP TCP模型 1. TCP搭建相关函数&#xff1a; 套接字Socket 1&#xff09;Socket函数&#xff1a; 2&#xff09;bind 3&#xff09;listen 4&#xff09;accept 5&#xff09;recv 注意&#xff1a; 1> TCP中的recv 可以替换成read&#xff1b; 2>TCP中的…...

消息称苹果Type-C口充电未设MFi限制,iOS17将更新Find My服务

根据国外科技媒体 iMore 报道&#xff0c;基于消息源 analyst941 透露的信息&#xff0c;苹果公司目前并未开发 MFi 限制。 根据推文信息内容&#xff0c;两款 iPhone 15 机型的最高充电功率为 20W&#xff0c;而 iPhone 15 Pro 机型的最高支持 27W 充电。 此前古尔曼表示苹…...

设计模式——工厂模式(简单工厂、工厂方法、抽象工厂)

是什么&#xff1f; 工厂模式的目的是将创建对象的具体过程隐藏起来&#xff0c;从而达到更高的灵活性 工厂模式分为&#xff1a;简单工厂模式、工厂方法模式、抽象工厂模式&#xff1b; 为什么&#xff1f; 在Java中&#xff0c;万物皆是对象&#xff0c;我们在使用的时候…...

《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.人类视觉对颜色的主观感觉包括哪三类&#xff1f; 2. 图像成像的过程包括哪三步&#xff1f; 3.图像的采样和量化分别指什么&#xff1f; 4、取k8时&#xff0c;将下图用相应矩阵表示 5、简述当限定了数字图像的数据量时采样和量化参数的选择遵循哪两条原则&#x…...

【Java校招面试】基础知识(五)——GC

目录 前言一、基础概念二、垃圾回收算法三、垃圾收集器四、引用后记 前言 本篇主要介绍Java垃圾回收机制——GC的相关内容。 “基础知识”是本专栏的第一个部分&#xff0c;本篇博文是第五篇博文&#xff0c;如有需要&#xff0c;可&#xff1a; 点击这里&#xff0c;返回本专…...

使用CMake调用Makefile 项目

目录标题 基本示例Cmake传递lib给MakefileCmake传递参数给Makefile&#xff0c;比如make cleanWindows下使用Cmake调用Makefile 基本示例 如果项目是使用传统的Makefile构建的&#xff0c;并且您希望使用CMake调用这些Makefile&#xff0c;您可以使用CMake的add_custom_target…...

快速傅里叶变换FFT学习笔记

点值表示法 我们正常表示一个多项式的方式&#xff0c;形如 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)a0​a1​xa2​x2...an​xn&#xff0c;这是正常人容易看懂的&#xff0c;但是&#xff0c;我们还有一种表示法。 我们知道&#xf…...

如何下载安装驱动

1 打开浏览器 这里以Edge浏览器举例 第一步打开桌面上的Edge浏览器 如果您的桌面上没有 那么找到搜索栏 搜索Edge 然后打开 打开之后一般是这样 然后把我发送您的地址 驱动下载地址 https://t.lenovo.com.cn/yfeyfYyD &#xff08;这个网址只是一个例子&#xff09; 删除掉前…...

鸿蒙Hi3861学习四-Huawei LiteOS介绍

一、什么是LitesOS Huawei LiteOS是华为针对物联网领域推出的轻量级物联网操作系统&#xff0c;是华为物联网战略的重要组成部分&#xff0c;具备轻量级、低功耗、互联互通、组件丰富、快速开发等关键能力。基于物联网领域业务特征打造领域性技术栈&#xff0c;为开发者提供“一…...

Vim 调用外部命令学习笔记

Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

Zustand 状态管理库:极简而强大的解决方案

Zustand 是一个轻量级、快速和可扩展的状态管理库&#xff0c;特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

从WWDC看苹果产品发展的规律

WWDC 是苹果公司一年一度面向全球开发者的盛会&#xff0c;其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具&#xff0c;对过去十年 WWDC 主题演讲内容进行了系统化分析&#xff0c;形成了这份…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

【决胜公务员考试】求职OMG——见面课测验1

2025最新版&#xff01;&#xff01;&#xff01;6.8截至答题&#xff0c;大家注意呀&#xff01; 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:&#xff08; B &#xff09; A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

【JavaSE】绘图与事件入门学习笔记

-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角&#xff0c;以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向&#xff0c;距离坐标原点x个像素;第二个是y坐标&#xff0c;表示当前位置为垂直方向&#xff0c;距离坐标原点y个像素。 坐标体系-像素 …...

ABAP设计模式之---“简单设计原则(Simple Design)”

“Simple Design”&#xff08;简单设计&#xff09;是软件开发中的一个重要理念&#xff0c;倡导以最简单的方式实现软件功能&#xff0c;以确保代码清晰易懂、易维护&#xff0c;并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计&#xff0c;遵循“让事情保…...

Vue ③-生命周期 || 脚手架

生命周期 思考&#xff1a;什么时候可以发送初始化渲染请求&#xff1f;&#xff08;越早越好&#xff09; 什么时候可以开始操作dom&#xff1f;&#xff08;至少dom得渲染出来&#xff09; Vue生命周期&#xff1a; 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...