【实验目的】
为某个单位建立一个员工通讯录管理系统,可以方便查询每一个员工的电话与地址。设计哈希表存储,设计并实现通讯录查找系统。
【实验要求】
(1)每个员工记录有下列数据项:电话号码、用户名、地址,可以适当再增加数据项;员工数据保存在employee.txt文件里。
(2)从employee.txt文件读取数据,分别以用户名、电话号码为关键字建立哈希表;
(3)哈希函数采用除留余数法,解决冲突采用二次探测再散列法;
(4)对通讯录进行添加、删除、修改、浏览、查找等操作,每进行一项操作后将内存中的数据写入到文件中。
(5)记录操作者的操作内容和当地时间并记录到当前文件夹下的Log.dat文件中(为方便查看,博主用的Log.txt文件)。
【主要功能】
(1)从文件employee.txt输入数据,初始化
(2)添加一个员工数据
(3)查找员工
(4)按姓名或电话号码定位员工,返回数据的地址
(5)删除一个员工
(6)保存内存中的数据到文件中
(7)按姓名查找员工
(8)按电话号码查找员工
(9)显示所有员工的数据
(10)显示指定员工数据
(11)更改员工数据
(12)更改员工的指定数据
(13)显示原员工信息和新员工信息,判断是否修改
(14)菜单显示
(15)获得系统时间,记录日志时使用
(16)将操作日志保存到文件Log.dat中
【参考程序】
header.h
/*****************************************************************
* 版权所有(C)2022 浅笑醉红楼
*
* 文件名称:header.h
* 内容摘要:哈希表的基本操作
* 其他说明:无
* 当前版本:1.0
* 作者:浅笑醉红楼
****************************************************************/
/*------------------头文件引用------------------*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/*------------------头文件引用------------------*/
/**************************相关宏定义**************************/
#define Hashsize 50/*哈希表长*/
#define DELEFLAG -1/*删除标志*/
/**************************相关宏定义**************************/
/************************相关结构体定义************************/
typedef struct{
char add[50];/*员工地址*/
long int num;/*员工电话号码*/
char name[10];/*员工姓名*/
int key;/*关键字*/
} Datatype;
typedef struct{
Datatype data;/*数据域*/
int times;/*比较次数*/
}HashTable[Hashsize];
/************************相关结构体定义************************/
/*----------------------------------------函数的声明----------------------------------------*/
void CreatHash(HashTable ht, Datatype items[], int n); /*创建Hash表*/
int HashInsert(HashTable ht, Datatype x); /*哈希表插入*/
int HashDelete(HashTable ht, Datatype x); /*哈希表删除*/
int HashSearch(HashTable ht, Datatype x); /*哈希表查找*/
int Collision(int d); /*线性探测处理冲突*/
int Collision2(int d, int i); /*平方探测处理冲突*/
int HashFunc(int num); /*除留余数法构造哈希函数*/
int Getkey(Datatype x); /*直接定址法获取名字的哈希地址*/
int Getkeyn(Datatype x); /*直接定址法获取电话号码的哈希地址*/
/*----------------------------------------函数的声明----------------------------------------*/
/**
* @brief 创建哈希表
* @param ht:哈希表 items:关键字序列 n:关键字个数
* @retval 无
*/
void CreatHash(HashTable ht, Datatype items[], int n) /*创建哈希表*/
{
int i;
for(i = 0; i < Hashsize; i++)/*初始化哈希表*/
{
ht[i].data.key = 0;
ht[i].times = 0;
}
for(i = 0; i < n; i++)
{
HashInsert(ht, items[i]);/*依次向哈希表中插入元素*/
}
}
/**
* @brief 哈希表插入
* @param ht:哈希表 x:插入数据元素
* @retval 插入成功,返回1
*/
int HashInsert(HashTable ht, Datatype x) /*哈希表插入*/
{
int addr;
addr = HashSearch(ht, x);/*在哈希表中查找*/
if(addr > 0)/*找到,不必插入*/
{
return 0;
}
ht[-addr].data = x;/*没有找到,则插入*//*-addr是因为上面返回的是-addr,故这儿是-addr(哈希地址不能为负)*/
ht[-addr].times = 1;
ht[-addr].data.key = 1;
return 1;
}
/**
* @brief 哈希表的删除
* @param ht:哈希表 x:删除数据元素
* @retval 成功返回1,失败返回0
*/
int HashDelete(HashTable ht, Datatype x)/*哈希表的删除*/
{
int addr;
addr = HashSearch(ht, x);/*查找数据元素*/
if(addr >= 0)/*找到,打上删除标记*/
{
ht[addr].data.key = -1;/*删除*/
return 1;/*删除成功返回1*/
}
return 0;/*删除失败返回0*/
}
/**
* @brief 哈希表的查找
* @param ht:哈希表 x:查找元素
* @retval 查找成功返回addr,失败返回-addr
*/
int HashSearch(HashTable ht, Datatype x)/*哈希表的查找*/
{
int addr;
x.key = Getkey(x);/*获取键值*/
addr = HashFunc(x.key);/*获得哈希地址*/
x.key = 1;
while(ht[addr].data.key != 0 && ht[addr].data.key != x.key)/*地址不为空或循环回到原点未找到查找元素,则冲突*/
{
addr = Collision(addr);/*没找到,处理冲突*/
}
if(ht[addr].data.key == x.key)
{
return addr;/*查找成功*/
}
else
{
return -addr;/*查找失败*/
}
}
/**
* @brief 线性探测处理冲突
* @param d:探查位置
* @retval 返回空闲的位置
*/
int Collision(int d)/*线性探测处理冲突*/
{
return(d + 1) % Hashsize;/*返回哈希地址*/
}
/**
* @brief 平方探测处理冲突
* @param d:探查位置 i:探测次数
* @retval 返回空闲的位置
*/
int Collision2(int d, int i)/*平方探测处理冲突*/
{
return (int)(d + pow(-1, i - 1) * i * i) % Hashsize;/*返回哈希地址*/
}
/**
* @brief 除留余数法构造哈希函数
* @param key:关键字
* @retval 返回余数为散列地址
*/
int HashFunc(int key)/*除留余数法构造哈希函数*/
{
return key % Hashsize;/*模为50取余*/
}
/**
* @brief 直接定址法获取名字的哈希地址
* @param x:哈希地址
* @retval 返回名字哈希地址
*/
int Getkey(Datatype x)/*直接定址法获取名字的哈希地址*/
{
int i, key = 0;
for (i = 0; i< 10; i++)
{
key = key + x.name[i];/*以数据元素关键字k本身或它的线性函数作为它的哈希地址*/
}
key = -key;
return key;
}
/**
* @brief 直接定址法获取名字的哈希地址
* @param x:哈希地址
* @retval 返回电话号码哈希地址
*/
int Getkeyn(Datatype x)/*直接定址法获取电话号码的哈希地址*/
{
int key = 0;
long int z = x.num;
while (z != 0)
{
key = key + z % 10;/*以数据元素关键字k本身或它的线性函数作为它的哈希地址*/
z = z / 10;
}
return key;
}
hash.c
/************************************************
* 版权所有(C) 2022 浅笑醉红楼
*
* 文件名称:hash.cpp
* 内容摘要:实现通讯录的显示、增加、查找、删除、修改等功能
* 其他说明:无
* 当前版本:1.0
* 作者:浅笑醉红楼
*************************************************/
/*------------------头文件引用------------------*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "header.h"
#include <conio.h>
/*------------------头文件引用------------------*/
/*----------------------------------------函数的声明----------------------------------------*/
void Displaymanu(HashTable ht, int n); /*主菜单*/
void SJXR(HashTable ht); /*数据写入*/
int SJDQ(HashTable ht); /*数据读取*/
void add(HashTable ht, int n); /*数据增加*/
void Delet(HashTable ht, int n); /*数据删减*/
void change(HashTable ht, int n); /*数据修改*/
int Search(HashTable ht, int n); /*数据查找*/
void display(HashTable ht, int n); /*显示所有员工数据*/
void fanhui(HashTable ht, int n); /*退出函数*/
/*----------------------------------------函数的声明----------------------------------------*/
void main() /*主函数*/
{
HashTable ht;
int n;
n = SJDQ(ht);
Displaymanu(ht, n);
}
void Displaymanu(HashTable ht, int n)/*主菜单*/
{
int choose;
FILE* fp1;/*定义操作日志记录文件*/
fp1 = fopen("Log.txt", "a");/*打开日志记录文本文件(“a”打开或新建一个文本文件,只允许在文件末尾追写,即只能增加不能删除)*/
if (fp1 == NULL)/*打开文件失败*/
{
printf("打开文件出错!");
}
system("cls");/*清屏操作*/
SJDQ(ht);/*员工数据读取*/
/*-----------------------------人机交互-----------------------------*/
printf("********************************************************\n");
printf("* 菜单 *\n");
printf("*1、增加员工信息 2、删除员工信息 *\n");
printf("*3、修改员工信息 4、查找员工信息 *\n");
printf("*5、打印所有员工信息 6、退出程序 *\n");
printf("********************************************************");
/*-----------------------------人机交互-----------------------------*/
printf("\n请输入序号来进行下一步操作:");
scanf_s("%d", &choose);/*键盘输入功能选择*/
switch(choose)/*功能选择*/
{
case 1: /*增加*/
fprintf(fp1, "【%s】 %s %s\t\n", __DATE__, __TIME__, "增加员工信息");/*日志文件输出*/
printf("【%s】 %s %s\t\n", __DATE__, __TIME__, "增加员工信息");
add(ht, n);
break;
case 2: /*删除*/
fprintf(fp1, "【%s】 %s %s\t\n", __DATE__, __TIME__, "员工删除");/*日志文件输出*/
printf("【%s】 %s %s\t\n", __DATE__, __TIME__, "员工删除");
Delet(ht, n);
break;
case 3: /*修改*/
fprintf(fp1, "【%s】 %s %s\t\n", __DATE__, __TIME__, "修改员工信息");/*日志文件输出*/
printf("【%s】 %s %s\t\n", __DATE__, __TIME__, "修改员工信息");
change(ht, n);
break;
case 4: /*查找*/
fprintf(fp1, "【%s】 %s %s\t\n", __DATE__, __TIME__, "查找员工信息");/*日志文件输出*/
printf("【%s】 %s %s\t\n", __DATE__, __TIME__, "查找员工信息");
Search(ht, n);
break;
case 5: /*显示*/
fprintf(fp1, "【%s】 %s %s\t\n", __DATE__, __TIME__, "打印所有员工信息");/*日志文件输出*/
printf("【%s】 %s %s\t\n", __DATE__, __TIME__, "打印所有员工信息");
fclose(fp1);
display(ht, n); /*哈希显示函数*/
break;
case 6: /*退出*/
exit (0);
}
Displaymanu(ht, n); /*主菜单*/
}
/**
* @brief 数据读取
* @param ht:哈希表
* @retval 返回文件读取个数
*/
int SJDQ(HashTable ht)/*数据读取*/
{
Datatype Hl[Hashsize];/*定义哈希表长度*/
int n = 0;
FILE* fp; /*定义员工信息文本文件*/
fp = fopen("employee.txt", "r");/*打开文本文件*/
if(fp == NULL)/*打开文件失败*/
{
printf("文件打开失败");
return -1;
}
while(fscanf(fp, "%s %d %s", &Hl[n].name, &Hl[n].num, &Hl[n].add) == 3)/*读取文本数据*/
{
n++;
}
fclose(fp);/*关闭文件*/
CreatHash(ht, Hl, n);/*根据关键字个数n创建哈希表*/
return n;
}
/**
* @brief 员工数据增加
* @param ht:哈希表 n:增加员工个数
* @retval 无
*/
void add(HashTable ht, int n)/*数据增加*/
{
Datatype x;
int addr, i;
printf("请输入增加员工信息:\n");
printf("请依次输入姓名,电话,地址:\n");
scanf_s("%s %d %s", &x.name,10, &x.num, &x.add,50);
addr = HashSearch(ht, x);/*在哈希表中查找*/
if(addr < 0)/*没找到,则插入*/
{
i = HashInsert(ht, x);/*哈希插入*/
printf("*****员工信息增加成功!*****\n");
}
else
{
printf("已有数据,请重新输入\n");/*找到,不必插入*/
add(ht, n);/*再次调用数据增加函数*/
}
SJXR(ht);/*数据写入*/
fanhui(ht, n);/*退出此段程序*/
}
/**
* @brief 员工信息的删除
* @param ht:哈希表 n:删除位置
* @retval 无
*/
void Delet(HashTable ht, int n)/*数据删除*/
{
Datatype x;
int i;
printf("请输入删除员工姓名:\n");
scanf_s("%s", &x.name,10);/*键盘输入删除员工姓名*/
i = HashDelete(ht, x);/*查找删除元素,并把删除结果赋值于i*/
if(i == 1)/*哈希删除函数删除元素成功返回1*/
{
printf("*****员工信息删除成功!*****\n");
}
else
{
printf("没有找到员工数据,请重新输入\n");
Delet(ht, n);/*再次调用删除函数*/
}
SJXR(ht);/*数据写入*/
fanhui(ht, n);/*退出此段程序*/
}
/**
* @brief 员工信息的修改
* @param ht:哈希表 n:修改位置
* @retval 无
*/
void change(HashTable ht, int n)/*数据修改*/
{
int addr = -1;
int i, m;
Datatype x;
printf("请输入查找联系人的方式:\n1、姓名\n2、电话号码\n输入除1、2外任意正数返回主菜单\n");
scanf_s("%d", &m);
if(m == 1)
{
printf("请输入查找姓名:\n");
scanf_s("%s", &x.name,10);
x.key = Getkey(x);/*获取哈希表键为Key的键值*/
addr = HashFunc(x.key);/*获得哈希地址*/
x.key = 1;
while(ht[addr].data.key != 0 && ht[addr].data.key != x.key)
{
addr = Collision2(addr,i);/*没找到,处理冲突*/
}
if(strcmp(ht[addr].data.name, x.name) == 0)/*两个字符串相同,返回0*/
{
printf("找到员工信息:%s %d %s\n", ht[addr].data.name, ht[addr].data.num, ht[addr].data.add);
printf("请输入修改后员工信息:\n");
scanf_s("%s %d %s", &ht[addr].data.name,10, &ht[addr].data.num, &ht[addr].data.add,50);/*键盘输入员工修改信息*/
printf("修改后员工信息为:%s %d %s\n", ht[addr].data.name, ht[addr].data.num, ht[addr].data.add);
SJXR(ht);/*数据写入*/
}
else
{
printf("未找到该员工,请重新输入。\n");
change(ht, n);/*调用修改函数*/
}
}
if(m == 2)
{
printf("请输入查找电话号码:\n");
scanf_s("%d", &x.num);/*键盘输入查找的电话号码*/
for(i = 0; i < Hashsize; i++)
{
if(ht[i].data.num == x.num)
{
addr = i;
}
}
if(addr == -1)
{
printf("未找到该号码,请重新输入\n");
change(ht,n);
}
if(ht[addr].data.num == x.num)
{
printf("找到信息:%s %d %s\n", ht[addr].data.name, ht[addr].data.num, ht[addr].data.add);
printf("请输入修改后信息:\n");
scanf_s("%s %d %s", &ht[addr].data.name,10, &ht[addr].data.num, &ht[addr].data.add,50);
printf("修改后信息为:%s %d %s\n", ht[addr].data.name, ht[addr].data.num, ht[addr].data.add);
SJXR(ht);/*数据写入*/
}
}
fanhui(ht, n);
}
/**
* @brief 员工信息的查找
* @param ht:哈希表 n:查找位置
* @retval 无
*/
int Search(HashTable ht, int n)/*数据查找*/
{
int i, z, addr = -1;
Datatype x;
printf("请输入查找方式:\n1、姓名\n2、电话号码\n输入除1、2外任意正数返回主菜单\n");
scanf_s("%d", &z);
switch (z)
{
case 1:
printf("请输入查找员工姓名:");
scanf_s("%s", &x.name,10);
x.key = Getkey(x);/*获取哈希表键为Key的键值*/
addr = HashFunc(x.key);/*获得哈希地址*/
x.key = 1;
while(ht[addr].data.key != 0 && ht[addr].data.key != x.key)
{
addr = Collision2(addr,i);/*没找到,处理冲突*/
}
if(strcmp(ht[addr].data.name, x.name) == 0)/*对比输入员工信息和文本信息,一致返回0*/
{
printf("找到信息:%s %d %s\n", ht[addr].data.name, ht[addr].data.num, ht[addr].data.add);
}
else
{
printf("未找到该员工,请重新输入。");
Search(ht, n);
}
break;
case 2:
printf("请输入查找电话号码:");
scanf_s("%d", &x.num);
for (i = 0; i < Hashsize; i++)
{
if (ht[i].data.num == x.num)
{
addr = i;
}
}
if (addr == -1)
{
printf("未找到该号码,请重新输入。");
Search(ht, n);
}
if (ht[addr].data.num == x.num)
{
printf("找到信息:%s %d %s\n", ht[addr].data.name, ht[addr].data.num, ht[addr].data.add);
}
break;
}
fanhui(ht, n);
}
/**
* @brief 员工信息数据写入
* @param ht:哈希表
* @retval 无
*/
void SJXR(HashTable ht)/*数据写入*/
{
FILE *fp;/*定义员工信息储存文件*/
int i;
fp = fopen("employee.txt", "w");/*打开文本文件*/
if (fp == NULL)
{
printf("打开文件出错!");
}
for (i = 0; i < Hashsize; i++)
{
if (ht[i].data.key >0)
{
fprintf(fp, "%s %d %s\n",ht[i].data.name ,ht[i].data.num, ht[i].data.add);
}
}
printf("*****操作成功*****\n");
fclose(fp);/*关闭文件*/
}
/**
* @brief 打印所有员工信息
* @param ht:哈希表 n:员工个数
* @retval 无
*/
void display(HashTable ht, int n)/*显示所有员工数据*/
{
int i;
printf("所有数据为:\n");
for(i = 0; i < Hashsize; i++)/*依次访问哈希表中员工数据,并显示*/
{
if(ht[i].data.key == 1)/*从哈希表数据域的第一个开始*/
{
printf("%s %d %s\n", ht[i].data.name, ht[i].data.num, ht[i].data.add);
}
}
fanhui(ht, n);
}
/**
* @brief 子程序退出函数
* @param ht:哈希表 n:键盘读入正数
* @retval 无
*/
void fanhui(HashTable ht, int n)/*退出函数*/
{
int A;
while(1)
{
printf("**请输入任意正数返回目录**\n");
scanf_s("%d", &A);
if(A>0)
{
Displaymanu(ht, n);/*返回主菜单*/
break;
}
else
{
printf("不是正数,请重新输入正数:");
scanf_s("%d",&A);
if (A > 0)
{
Displaymanu(ht, n);/*返回主菜单*/
break;
}
}
}
}