/*基于散列文件的通信录信息管理系统的设计与实现
日志1: --6.11
* 一、初始化:
* 1.fopen(fhead, "rb");
* 先判断散列文件是否已存在
* 若不存在则fopen(fhead, "wb+");新建散列文件
* 2.head[i] = i;//初始化目录表
* bucked[i].next = -1;//初始无溢出桶
* head[N] = -1; //目录表多添加一个空间用于存储回收桶的指针
* 3.将目录表与数据桶分别存入两个文件
日志2: --6.12
*
* 更新: 结构体Contacts中增加flag标志位,
* 初始化中添加bucked[i].contacts[j].flag = 0;
* 以便于检查桶中有无空位
*
* 二、插入与打印:
* 1.插入:
* fopen(fhead, "rb+");打开文件
* checkHash检查桶中有无空位
* 1).基桶未满 -- 直接存入空位(桶未满时无需更新目录表)
* 2).基桶满,且无溢出桶 -- 优先考虑回收桶
* ①存在回收桶 -- 新数据存入回收桶,并将此回收桶改为溢出桶(头插法更新目录表)
* ②无回收桶 添加新桶(溢出桶)并将数据插入新桶的第一个空位
* 新桶存至文件末尾,并用头插法更新目录表
* 3).基桶满,且有溢出桶 -- 同1)
* 4).基桶满,且溢出桶满 -- 同2)
* 注意:使用头插法无需考虑溢出桶的位置,可以提高插入与查找效率。
日志3: --6.13
*
* 更新:插入函数中增加了检测输入数据是否已存在的判断
*
* 三、查找与删除:
* 1.查找:
* 1)查找元素的散列地址
* 2)读取文件信息,比对姓名
* 3)查找成功--打印,查找失败
* 2.删除
* 1)用户输入姓名,比对查找
* 2)
* a.在基桶中
* 1.若此基桶存在溢出桶 --> 将溢出桶中的一个数据移入此位置
* 1> 若溢出桶里的数据移出后,溢出桶为空,
* ① 若已有其它回收桶,头插法插入回收桶链表,更新目录表最后一个位置的指针
* ② 若无其它回收桶,将其指针存入目录表最后一个位置。 √
* 2> 若移出后溢出桶不为空,仅将溢出桶里的位置回收 √
* 2.若无溢出桶 --- 直接回收(flag = 0) √
* b.在溢出桶中:
* 1.删去后尚有已存在的其它数据 -- 仅回收此位置 √
* 2.删去后溢出桶为空
* 1> 若此溢出桶不是末尾的溢出桶,不将其改为回收桶,而是从末尾的溢出桶中取出一个填进来
* ①若取出后末尾的溢出桶空了,则将其改为回收桶。
* 一、已有回收桶
* 二、无
* ②直接删
* 2> 直接删
* ......
日志4 --6.14
* 更新:对插入函数进行了修改,使之桶满时优先考虑回收桶
* 完善多处对回收桶的操作,使之适应插入、删除操作中的各种情况
*
日志5 --6.15
* 应考虑的更多情况:
* 1.删除
* b.在溢出桶中:
* 1.删去后尚有已存在的其它数据 -- 仅回收此位置 √
* 2.删去后溢出桶为空
* 1> 若此溢出桶不是末尾的溢出桶,不将其改为回收桶,而是从末尾的溢出桶中取出一个填进来
* ①若取出后末尾的溢出桶空了,则将其改为回收桶。
* ......
*
日志6 --6.16 --23;16
通过对各种情况的验证不断纠正bug最终完成本次作品 -- 十分耗费时间的一个过程
*/
state.h 文件
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<conio.h>
#include <windows.h>
#define ID_SIZE 20 //学号
#define NAME_SIZE 10 //姓名长度
#define PHONE_SIZE 20 //电话号长度
#define MAIL_SIZE 30 //邮箱长度
#define N 7 //基桶的数量
#define capacity 3 //每个桶的容量
const char fhead[9] = "hashhead";//表头文件
const char fnode[9] = "hashnode";//存储信息
typedef struct Contacts {
int flag; //0未使用,1已使用
char ID[ID_SIZE];
char name[NAME_SIZE];
char phone[PHONE_SIZE];
char mail[MAIL_SIZE];
}Contacts;
typedef struct Bucked {
Contacts contacts[capacity];
int next; //下一个桶的指针
}Bucked;
void haveHash();//初始化
void hashSearch(); //查找
void hashInsert(); //插入
void hashPrt(); //打印
void deleteHashfile(); //删除
void system_menu(); //主菜单
// 散列函数:将ID的ASCLL转换为数字
unsigned int hash(char ID[]) {
unsigned int hash = 0;
for (int i = 0; i < strlen(ID); ++i) {
hash += ID[i];
}
return hash % N;
}
//找空位
int checkHash(Bucked fct) {
for (int i = 0; i < capacity; i++)
if (fct.contacts[i].flag == 0)
return i; //桶仍有空位
return -1; //桶fct已满
}
//找非空位置
int checkHash_1(Bucked fct) {
for (int i = 0; i < capacity; i++)
if (fct.contacts[i].flag == 1)
return i; //桶仍有空位
return -1; //桶fct已满
}
//记录fct桶中尚存多少个数据
int checkHash_num(Bucked fct) {
int i, j = 0;
for (i = 0; i < capacity; i++)
if (fct.contacts[i].flag == 1)
j++;
return j;
}
function.h文件
#pragma once
#include "state.h"
void haveHash() {//初始化
FILE* fp1, * fp2;
fp1 = fopen(fhead, "rb");
fp2 = fopen(fnode, "rb");
if (fp1 == NULL || fp2 == NULL) {
fp1 = fopen(fhead, "wb+");
// 读写打开建立一个二进制文件,若文件不存在则建立该文件
fp2 = fopen(fnode, "wb+");
if (fp1 == NULL || fp2 == NULL) {
printf("打开文件失败!");
exit(1); // 异常退出
}
int* head, i;
Bucked* bucked = (Bucked*)malloc(sizeof(Bucked) * N); //初始化N个基桶存入文件
head = (int*)malloc(sizeof(int) * (N + 1));//多申请一个位置用于储存回收桶的位置
if (head == NULL) {
printf("内存申请失败!");
exit(1);//异常退出
}
for (i = 0; i < N; i++)
{
head[i] = i;//初始化目录表
for (int j = 0; j < capacity; j++) {
bucked[i].contacts[j].flag = 0; //桶均为空
bucked[i].next = -1;//无溢出桶
}
}
head[i] = -1; //回收桶指针-1 --表示初始无回收桶
fwrite(head, sizeof(int), N + 1, fp1);//存入目录表表文件
fwrite(bucked, sizeof(Bucked), N, fp2);
free(head);//释放空间
fclose(fp1);
fclose(fp2);
}
else {
//printf("已存在散列函数!\n");
}
}
int searchHash(Contacts* contacts) {
FILE* fp1, * fp2; // fP1表头,fp2储存
fp1 = fopen(fhead, "rb+");
// 读写打开一个二进制文件
fp2 = fopen(fnode, "rb+");
if (fp1 == NULL || fp2 == NULL)
{
printf("打开文件失败!");
exit(1); // 异常退出
}
Bucked p; // P为工作结点
int* head, d, loc;
head = new int[N + 1];
if (head == NULL)
{
printf("内存申请失败!");
exit(1); // 异常退出
}
fread(head, sizeof(int), N + 1, fp1); // 读取表头文件
d = hash(contacts->ID); // 查找元素的散列地址
loc = head[d];
while (loc != -1)
{
fseek(fp2, loc * sizeof(Bucked), 0); // 文件指针移动到loc位置
fread(&p, sizeof(Bucked), 1, fp2); // 读取loc位置的结点信息
for (int j = 0; j < capacity; j++)
if (strcmp(p.contacts[j].ID, contacts->ID) == 0)
if (p.contacts[j].flag == 1) {
*contacts = p.contacts[j]; // 被找到结点的元素值赋给r
free(head);
fclose(fp1);
fclose(fp2);
return 1; // 结束循环
}
loc = p.next;// 工作位置后移到后继的位置
}
free(head);
fclose(fp1);
fclose(fp2);
return 0;
}
void hashSearch() {
system("cls");
Contacts contacts;
printf("请输入查找的学生ID:");
scanf("%s", contacts.ID);
int k = searchHash(&contacts);
if (k == 1)
{
printf("查找成功!\n");
printf("ID:%s;\n", contacts.ID);
printf("name:%s;\n", contacts.name);
printf("phone:%s;\n", contacts.phone);
printf("mail:%s\n\n", contacts.mail);
getchar();
}
else
{
printf("您查找的ID不存在!");
getchar();
}
printf("\n按任意键继续...\n");
getchar();
//system_menu();
}
void hashInsert() {
system("cls");
Contacts ct;
printf("请输入学生ID:");
scanf("%s", &ct.ID);
if (searchHash(&ct) == 1) {//元素在散列文件中已存在
printf("该元素在散列文件中已存在,无法插入!\n");
printf("\n按任意键继续...\n");
getchar();
getchar();
return;
}
printf("请输入姓名:");
scanf("%s", &ct.name);
printf("请输入phone:");
scanf("%s", &ct.phone);
printf("请输入mail:");
scanf("%s", &ct.mail);
FILE* fp1, * fp2;
fp1 = fopen(fhead, "rb+");
fp2 = fopen(fnode, "rb+");
if (fp1 == NULL || fp2 == NULL)
{
printf("打开文件失败!");
exit(1);//异常退出
}
Bucked prev_bucked;
int* head, hashIndex;
head = (int*)malloc(sizeof(int) * (N + 1));
if (head == NULL)
{
printf("内存申请失败!");
exit(1);//异常退出
}
fread(head, sizeof(int), N + 1, fp1);
hashIndex = hash(ct.ID); //计算哈希值
int head_node = head[hashIndex];
fseek(fp2, head_node * sizeof(Bucked), 0);
fread(&prev_bucked, sizeof(Bucked), 1, fp2);
int contacts_index = checkHash(prev_bucked); //检查桶中有无空位
if (contacts_index != -1) { //桶未满
prev_bucked.contacts[contacts_index] = ct; //数据存入桶中
prev_bucked.contacts[contacts_index].flag = 1; //标记为已使用
fseek(fp2, head_node * sizeof(Bucked), 0);
fwrite(&prev_bucked, sizeof(Bucked), 1, fp2); //将更新后的桶存入文件
free(head);
fclose(fp1);
fclose(fp2);
}
else { //桶满
if (head[N] != -1) { //优先考虑回收桶
int new_head_node = head[N];
fseek(fp2, new_head_node * sizeof(Bucked), 0);
fread(&prev_bucked, sizeof(Bucked), 1, fp2);
prev_bucked.contacts[0] = ct; //数据存入桶中
prev_bucked.contacts[0].flag = 1; //标记为已使用
head[hashIndex] = new_head_node; //改为溢出桶更新目录表
if (prev_bucked.next == -1) { //只有一个回收桶时
head[N] = -1;
prev_bucked.next = head_node;
}
else { //存在多个回收桶
head[N] = prev_bucked.next;
prev_bucked.next = head_node;
}
fseek(fp1, 0, 0);
fwrite(head, sizeof(int), N + 1, fp1); //更新目录表
fseek(fp2, new_head_node * sizeof(Bucked), 0);
fwrite(&prev_bucked, sizeof(Bucked), 1, fp2); //将更新后的桶存入文件
free(head);
fclose(fp1);
fclose(fp2);
}
else { //无回收桶 添加溢出桶
Bucked newbucked;
for (int x = 0; x < capacity; x++)
newbucked.contacts[x].flag = 0;
newbucked.contacts[0] = ct;
newbucked.contacts[0].flag = 1;
newbucked.next = head_node;
fseek(fp2, 0, 2);//文件指针移到文件尾
int loc = ftell(fp2) / sizeof(Bucked);//文件尾结点位置序号
fwrite(&newbucked, sizeof(Bucked), 1, fp2);//fr结点存入文件尾
head[hashIndex] = loc;//表头指向新插入结点
fseek(fp1, 0, 0);
fwrite(head, sizeof(int), N + 1, fp1); //更新目录表
free(head);
fclose(fp1);
fclose(fp2);
}
}
printf("添加成功!\n");
printf("\n按任意键继续...\n");
getchar();
getchar();
//system_menu();
}
void hashPrt() {
system("cls");
FILE* fp1, * fp2; // fP1表头,fp2储存
fp1 = fopen(fhead, "rb+");
// 读写打开建立一个二进制文件
fp2 = fopen(fnode, "rb+");
if (fp1 == NULL || fp2 == NULL)
{
printf("打开文件失败!");
exit(1); // 异常退出
}
Bucked p; // P为工作结点
int* head, loc, i, x = 0;
head = (int*)malloc(sizeof(int) * (N + 1));
if (head == NULL)
{
printf("内存申请失败!");
exit(1); // 异常退出
}
fread(head, sizeof(int), N + 1, fp1); // 读取表头文件
for (i = 0; i < N; i++)
{
//printf("%d:", i);
loc = head[i];
while (loc != -1)
{
printf("%d:\n", loc);
fseek(fp2, loc * sizeof(Bucked), 0); // 文件指针移动到loc位置
fread(&p, sizeof(Bucked), 1, fp2); // 读取loc位置的结点信息
for (int j = 0; j < capacity; j++)
if (p.contacts[j].flag == 1) {
//printf("%d:\n", ++x);
printf("ID:%s;\t", p.contacts[j].ID);
printf("name:%s;\t", p.contacts[j].name);
printf("phone:%s;\t", p.contacts[j].phone);
printf("mail:%s\t\n", p.contacts[j].mail);
}
loc = p.next;
//if (loc != -1)
//printf("->\n");
}
printf("\n");
}
free(head);
fclose(fp1);
fclose(fp2);
printf("\n按任意键继续...\n");
getchar();
//system_menu();
}
void deleteHashfile() {
system("cls");
Contacts contacts;
printf("请输入要删除学生的ID:");
scanf("%s", &contacts.ID);
if (searchHash(&contacts) == 0)
{
printf("该ID在散列文件中不存在,无法删除\n");
printf("\n按任意键继续...\n");
getchar();
getchar();
return;
}
FILE* fp1, * fp2; // fP1表头,fp2储存
fp1 = fopen(fhead, "rb+");
fp2 = fopen(fnode, "rb+");
if (fp1 == NULL || fp2 == NULL)
{
printf("打开文件失败!");
exit(1);//异常退出
}
Bucked p, q;
int* head, d, loc;
head = (int*)malloc(sizeof(int) * (N + 1));
if (head == NULL)
{
printf("内存申请失败!");
exit(1);//异常退出
}
fread(head, sizeof(int), N + 1, fp1);//读取表头文件
d = hash(contacts.ID);//计算插入元素的散列地址
loc = head[d];
int flag = 0; // 0 --的数据在基桶中;1 -- 在溢出桶
int excess, locx = loc;
fseek(fp2, loc * sizeof(Bucked), 0);//文件指针移动到loc位置
fread(&q, sizeof(Bucked), 1, fp2);//读取loc位置的结点信息
if (loc >= N) {
flag = 1; // 先访问的溢出桶
excess = checkHash_num(q); //纪录溢出桶中数据位置,便于回收溢出桶中的数据(移出基桶数据时)
locx = loc; //记录溢出桶指针,以便回收溢出桶
}
while (loc != -1){
fseek(fp2, loc * sizeof(Bucked), 0);//文件指针移动到loc位置
fread(&p, sizeof(Bucked), 1, fp2);//读取loc位置的结点信息
for (int j = 0; j < capacity; j++)
if (strcmp(p.contacts[j].name, contacts.name) == 0 && p.contacts[j].flag == 1) {
if (loc < N) { //数据在基桶中
p.contacts[j].flag = 0;
if (flag == 1 && excess == 1) { //基桶存在溢出桶且溢出桶只有一个数据 -- 此时将溢出桶中的数据移入基桶,并将此空的溢出桶改为回收桶
excess = checkHash_1(q);
p.contacts[j] = q.contacts[excess];
q.contacts[excess].flag = 0;
if (head[N] == -1) {
head[loc] = q.next; //更新目录表
q.next = -1;
head[N] = locx; //记录回收桶的指针
}
else {
head[loc] = q.next; //更新目录表
q.next = head[N];
head[N] = locx;
}
}
else if (flag == 1) { //基桶存在溢出桶且溢出桶存在不止一个数据
excess = checkHash_1(p);
p.contacts[j] = q.contacts[excess];
q.contacts[excess].flag = 0;
}
fseek(fp2, locx * sizeof(Bucked), 0);//文件指针移动到locx位置
fwrite(&q, sizeof(Bucked), 1, fp2);//读取loc位置的结点信息
fseek(fp2, loc * sizeof(Bucked), 0);
fwrite(&p, sizeof(Bucked), 1, fp2); //将更新后的桶存入文件
flag = 2;
break;
} //if数据在基桶
else { //待删除的数据在溢出桶中
if (checkHash_num(p) > 1) {
p.contacts[j].flag = 0; //溢出桶中存在不止一个数据 -- 则直接回收此位置
flag = 2; //跳出while循环
fseek(fp2, loc * sizeof(Bucked), 0);
fwrite(&p, sizeof(Bucked), 1, fp2); //将更新后的桶存入文件
break;
}
else { //溢出桶只有一个数据
if (head[d] == loc) { //溢出桶在末尾 -- 删去数据后将此溢出桶改为回收桶
p.contacts[j].flag = 0;
head[d] = p.next; //更新目录表
if (head[N] == -1) { //无回收桶
head[N] = loc; //记录回收桶指针
p.next = -1;
}
else { //已有其他回收桶
p.next = head[N];
head[N] = loc;
}
}
else { //溢出桶不在末尾 --将此溢出桶视作基桶,从末尾溢出桶中取出一个数据补进来。
if (flag == 1 && excess == 1) { //末尾的溢出桶只有一个数据 -- 此时将末尾溢出桶中的数据移入基桶,并将此空的溢出桶改为回收桶
excess = checkHash_1(q);
p.contacts[j] = q.contacts[excess];
q.contacts[excess].flag = 0;
if (head[N] == -1) {
head[d] = q.next; //更新目录表
q.next = -1;
head[N] = locx; //记录回收桶的指针
}
else {
head[d] = q.next; //更新目录表
q.next = head[N];
head[N] = locx;
}
}
else if (flag == 1) { //基桶存在溢出桶且溢出桶存在不止一个数据
excess = checkHash_1(p);
p.contacts[j] = q.contacts[excess];
q.contacts[excess].flag = 0;
}
}
fseek(fp2, locx * sizeof(Bucked), 0);
fwrite(&q, sizeof(Bucked), 1, fp2); //将更新后的溢出桶存入文件
fseek(fp2, loc * sizeof(Bucked), 0);
fwrite(&p, sizeof(Bucked), 1, fp2); //将更新后的桶存入文件
flag = 2;
break;
}
}
//fseek(fp2, loc * sizeof(Bucked), 0);
//fwrite(&prev_bucked, sizeof(Bucked), 1, fp2); //将更新后的桶存入文件
}
if (flag == 2)
break;
loc = p.next;
}
fseek(fp1, 0, 0);//head表头重新存入散列表头文件
fwrite(head, sizeof(int), N + 1, fp1);
free(head);
fclose(fp1);
fclose(fp2);
//printf("删除成功!");
printf("已删除!");
printf("\n按任意键继续...\n");
getchar();
getchar();
//system_menu();
}
void huishoutong() {
system("cls");
FILE* fp1, * fp2; // fP1表头,fp2储存
fp1 = fopen(fhead, "rb+");
// 读写打开建立一个二进制文件
fp2 = fopen(fnode, "rb+");
if (fp1 == NULL || fp2 == NULL)
{
printf("打开文件失败!");
exit(1); // 异常退出
}
Bucked p; // P为工作结点
int* head, loc;
head = new int[N + 1];
if (head == NULL)
{
printf("内存申请失败!");
exit(1); // 异常退出
}
fread(head, sizeof(int), N + 1, fp1); // 读取表头文件
loc = head[N];
while (loc != -1)
{
printf("N = %d,回收桶编号:%d\n", N, loc);
fseek(fp2, loc * sizeof(Bucked), 0); // 文件指针移动到loc位置
fread(&p, sizeof(Bucked), 1, fp2); // 读取loc位置的结点信息
for (int j = 0; j < capacity; j++) {
printf("flag:%d;\t", p.contacts[j].flag);
//printf("name:%s;\t", p.contacts[j].name);
//printf("phone:%s;\t", p.contacts[j].phone);
//printf("mail:%s\t\n", p.contacts[j].mail);
}
printf("\n");
loc = p.next;
//if (loc != -1)
//printf("->\n");
}
free(head);
fclose(fp1);
fclose(fp2);
printf("\n按任意键继续...\n");
getchar();
}
void system_menu() {
while (1) {
system("cls");
printf("\t\t\t欢迎使用yt通信录信息管理系统!\t\n\n");
printf("\t++========================主菜单==========================++\t\n");
printf("\t||请输入选项所对应的序号: ||\t\n");
printf("\t|| === 1.新建联系人 === ||\t\n");
printf("\t|| === 2.查找联系人 === ||\t\n");
printf("\t|| === 3.删除联系人 === ||\t\n");
printf("\t|| === 4.查看通讯录 === ||\t\n");
printf("\t|| === 0.退出系统 === ||\t\n");
printf("\t++========================================================++\t\n");
int a;
scanf("%d", &a);
getchar();
switch (a) {
case 1:hashInsert(); break;
case 2:hashSearch(); break;
case 3:deleteHashfile(); break;
case 4:hashPrt(); break;
case 10:huishoutong(); break;
case 0:printf("感谢您的使用~"); Sleep(1000); exit(0);
default:printf("不要调皮哦,请重新输入~"); getchar(); system_menu(); break;
}
}
}
最后是main.c
#include "function.h"
int main() {
haveHash();
system_menu();
system("pause");
return 0;
}