散列文件的插入、删除和查找
当你看这篇文章的时候我假定你已经熟悉文件操作、熟练掌握链表操作,并且理解哈希表原理。
- 功能要求:
(1)初始化三列文件;
(2)向散列文件中插入一个元素;
(3)从散列文件中删除一个元素;
(4)从散列文件中查找一个元素。
散列文件通常采用链接法处理冲突,并且把保存每个单链表表头指针的表头向量用一个文件单独存储起来,称此为散列表文件,把所有单链表中的结点用一个文件单独存储起来,称为散列主文件。
- 散列文件中每个节点的类型定义为:
Struct FLNode // 散列主文件中的节点类型
{
ElemType data; //值域
int next; //指向下一个节点的指针域
};
其中data域用来存储待散列的元素,next域用来存储下一个同义词元素在散列主文件中的存储位置,即所在节点的位置号。
- 题目分析
其实题目说的很明白了,我就不多罗嗦了,我觉得就是模拟数据库的索引。放一张以我的理解画的图:
- 代码分析
1. 数据结构:节点和题目一样,数据域包含关键字key和其余信息rest,指针域next是int型,就是向散列主文件中存储元素数组的下标,next == -1 表示空。
2. 在散列主文件中,以线性数组存储散列表元素。
3. 散列主文件存储元素,索引文件存储索引地址。
4. 查找、删除、增加、初始化等操作都用了两个函数。
5. 宏定义 N 为散列地址长度。
6. 向单链表插入采用头插。
7. N 号地址不存放数据,删除后的节点放入N号节点。
8. 使用二进制存取文件,使磁盘中的数据和内存中存储形式相同,方便操作。
Code,环境codeblocks17 通过
//@ChenYe 2019/2/3
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <windows.h>
// 颜色控制
#define green SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_GREEN|FOREGROUND_INTENSITY)
#define white SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_RED|FOREGROUND_BLUE|FOREGROUND_GREEN)
#define red SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_RED|FOREGROUND_INTENSITY)
#define yellow SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_INTENSITY)
using namespace std;
/*定义散列表长度,其值取决于待三列的元素数量
和选取的装填因α的大小,在此假定为13*/
#define N 13
/*定义关键字类型,这里假定为整型*/
typedef int KeyType;
struct ElemType //元素类型
{
KeyType key; //关键字域
char rest[10]; //其他域,假定用字符数组表示
};
struct FLNode //索引文件中的结点类型
{
struct ElemType data; //值域
int next; //指向下一个结点的指针域
};
const int b1 = sizeof(KeyType); //用全局常量b1保存索引表文件中的记录(元素)长度
const int b2 = sizeof(struct FLNode); //用全局常量b2保存索引主文件中的记录(结点)长度
//定义散列主文件和索引文件的名字
char filename[]="Hash"; // 散列主文件名
char Iname[]="HashIndex"; // 索引文件名
//建立并初始化散列表文件
void InitHashFile(char *fname)
{
// 确认
char ch;
printf("确实要重新初始化散列文件(y/N)?");
getchar();
scanf("%c", &ch);
if('y' == ch || 'Y' == ch)
{
printf("--- 重新初始化散列文件完毕! ---\n");
}
else
{
printf("--- 未执行重建散列文件操作! ---\n");
return;
}
/*------确认完成,开始初始化------*/
int i;
int *A;
//以读写方式新建二进制散列文件
FILE *fp, *fp2;
fp = fopen(fname, "wb+");
fp2 = fopen(Iname, "wb+");
if(NULL == fp|| NULL == fp2)
{
printf("打开文件失败!/n");
exit(1);
}
//动态分配具有N+1个整型储存空间的数组A
A = (int *)calloc(N + 1, b1);
if(NULL == A)
{
printf("内存申请失败!/n");
exit(1);
}
//给数组A中的每个元素赋初值-1,表示空指针
for(i = 0; i < N + 1; i++)
{
A[i] = -1;
}
//初始化散列文件
fwrite((char *)A, (N + 1)*b1, 1, fp);
fwrite((char *)A, (N + 1)*b1, 1, fp2);
//删除数组A
free(A);
//关闭fp对应的文件
fclose(fp);
fclose(fp2);
}
//检测是否存在散列文件
void existHash(char *fname)
{
char bcreate;
char filename[128];
FILE *fp, *fp2;
fp = fopen(fname, "rb");
fp2 = fopen(Iname, "rb");
if(NULL == fp|| NULL == fp2)
{
printf("--- 散列文件不存在, 是否新建散列文件(y:重新建立;/N:打开其他散列文件)? ---\n");
scanf("%c", &bcreate);
if('y' == bcreate || 'Y' == bcreate)
{
InitHashFile(fname);
printf("--- 新建散列文件完毕! ---\n");
}
else
{
printf("请输入散列文件路径:\n");
scanf("%s", filename);
strcpy(fname, filename);
existHash(fname);
}
}
else
{
fclose(fp);
fclose(fp2);
}
}
//把元素x插入到散列文件中
void HFInsertOne(char *fname, struct ElemType x)
{
int p;
int len; //文件尾结点位置序号
int *A;
int d;
struct FLNode temp;
struct FLNode pn;
//以读写和不新建方式打开散列文件
FILE *fp, *fp2;
fp = fopen(fname, "rb+");
fp2 = fopen(Iname, "rb+");
if(NULL == fp)
{
printf("打开文件失败!\n");
exit(1);
}
//动态分配具有N + 1个整型存储空间的数组
A = (int *)calloc(N + 1, b1);
if(!A)
{
printf("内存申请失败!\n");
exit(1);
}
//将索引文件的表头读入到数组A中
fread((char *)A, (N + 1) * b1, 1, fp2);
//以关键字x.key计算x的散列地址,采用除留余数法
d = x.key % N;
//以x和A[d]的值构成待插入散列文件的内存节点temp
temp.data = x;
temp.next = A[d];
//将temp结点的值写入到散列文件中,并链接到散列文件表头
//下表d单链表的表头
if(-1 == A[N])
{
//将文件指针移至文件尾
fseek(fp, 0L, 2);
//计算出文件尾的结点位置序号
len = (ftell(fp) - (N+1)*b1)/b2;
//将temp结点的值写入文件尾
fwrite((char *)&temp, b2, 1, fp);
//使A[d]指向新插入的结点
A[d] = len;
}
else
{
//p指向空闲单链表的表头结点
p = A[N];
//使空闲单链表的表头指针指向其下一个结点
fseek(fp, b1 * (N+1) + p*b2, 0);
fread((char *)&pn, b2, 1, fp);
A[N] = pn.next;
//使temp的值写入到p位置的结点上
fseek(fp, -b2, 1);
fwrite((char *)&temp, b2, 1, fp);
//使A[p]指向新插入的p结点
A[d] = p;
}
//将数组A中的全部内容写回到索引文件的表头中
fseek(fp,0L,0);
fseek(fp2,0L,0);
fwrite((char *)A, b1 * (N+1), 1, fp2);
//删除动态数组A和关闭散列文件
free(A);
fclose(fp);
fclose(fp2);
}
//从散列文件中删除关键字尾x.key的元素,并由x带回该
//元素,若删除成功则返回1,否则返回0
int HFDelete(char *fname, struct ElemType *x)
{
struct FLNode tp,tq;
int p, q;
int *A;
int d;
FILE *fp, *fp2;
//打开散列文件
fp = fopen(fname, "rb+");
fp2 = fopen(Iname, "rb+");
if(NULL == fp|| NULL == fp2)
{
printf("打开文件失败!\n");
exit(1);
}
//申请动态数组A
A = (int *)calloc(N+1, b1);
if(NULL == A)
{
printf("内存申请失败!\n");
exit(1);
}
//将索引文件表头读入数组A中
fread((char *)A, (N+1)*b1, 1, fp2);
//计算散列地址d
d = x->key % N;
p = A[d];
while(-1 != p)
{
fseek(fp, (N+1)*b1+p*b2, 0);
fread((char*)&tp, b2, 1, fp);
if(tp.data.key == x->key)
{
//被删除结点的元素值赋给x带回
*x = tp.data;
//从单链表中删除p结点
if(p == A[d])
{
A[d] = tp.next;
}
else
{
tq.next = tp.next;
fseek(fp, (N+1)*b1+q*b2, 0);
fwrite((char *)&tq, b2, 1, fp);
}
//将p结点连接到空闲单链表的表头
tp.next = A[N];
fseek(fp, (N+1)*b1+p*b2, 0);
fwrite((char *)&tp, b2, 1,fp);
A[N] = p;
//结束while循环
break;
}
else
{
//使p指针的值赋给q,tp结点的值赋值给tq结点
q = p;
tq = tp;
//p指向单链表中的下一个结点
p = tp.next;
}//if分支结束
}//while循环结束
//将索引文件的表头重新写入索引文件
fseek(fp, 0L, 0);
fseek(fp2, 0L, 0);
fwrite((char *)A, (N + 1) * b1, 1, fp2);
//释放A数组申请的内存空间
free(A);
//关闭散列文件
fclose(fp);
fclose(fp2);
if(-1 == p)
{
return 0; //没有找到要删除的结点
}
else
{
return 1; //成功删除要删除的结点
}//<end if>
}
//从散列文件中查找关键字为x.key的元素,并由x带回
//元素,若查找成功则返回1,否则返回0
int HFSearch(char *fname, struct ElemType *x)
{
int d;
int p;
int *A;
struct FLNode temp;
//以读写方式打开散列文件
FILE *fp, *fp2;
fp = fopen(fname, "rb+");
fp2 = fopen(Iname, "rb+");
if(NULL == fp|| NULL == fp2)
{
printf("打开文件失败!\n");
exit(1);
}
//申请动态数组A
A = (int *)calloc(N+1, b1);
if(NULL == A)
{
printf("内存申请失败!\n");
exit(1);
}
fread((char *)A, (N+1)*b1, 1, fp2);
d = x->key % N;
//取出地址为d的单链表的表头指针(指向该地址的第一个存储元素)
p = A[d];
//从d点链表中查找关键字为x.key的元素
while(p != -1)
{
fseek(fp, (N+1)*b1 + p*b2, 0);//在文件中定位
fread((char *)&temp, b2, 1, fp);
if(temp.data.key == x->key)
{
*x = temp.data; //被查找到的元素由x带回
break;
}
else
{
p = temp.next; //把结点指针移到下一个结点
}
}
//释放A数组申请的内存空间
free(A);
//关闭文件
fclose(fp);
fclose(fp2);
if(-1 == p)
{
return 0;
}
else
{
return 1;
}//if分支结构结束
}
//顺序打印出散列文件中的每个单链表中的每个结点位置序号及元素值
void HFPrint(char *fname)
{
int i;
int p;
int *A;
struct FLNode pn;
//以读写方式打开散列文件
FILE *fp, *fp2;
fp = fopen(fname, "rb+");
fp2 = fopen(Iname, "rb+");
if(NULL == fp|| NULL == fp2)
{
printf("打开文件失败!\n");
exit(1);
}
//申请动态数组A
A = (int *)calloc(N+1, b1);
if(NULL == A)
{
printf("内存申请失败!\n");
exit(1);
}
fread((char *)A, b1, N+1, fp2);
for(i = 0; i < N+1; i++)
{
printf("%d:", i);
p = A[i];
while(-1 != p)
{
fseek(fp, (N+1)*b1 + p*b2, 0); // 修改文件指针位置
fread((char *)&pn, b2, 1, fp); // 从文件中中读取节点
printf("%d->%d(%s) ", p, pn.data.key, pn.data.rest);
p = pn.next;
}
printf("\n");
}
//删除动态数组A申请的内存
free(A);
//关闭文件
fclose(fp);
fclose(fp2);
}
void Insert(char filename[])
{
struct ElemType x;
printf("输入待插入元素x的值(一个整数(关键字)和一个字符串(附加信息,长度小于10)):\n");
scanf("%d", &x.key);
scanf("%s", x.rest);
if(!HFSearch(filename,&x))
HFInsertOne(filename, x);
else
{
printf("该值在哈希表已存在");
}
}
void Delete(char filename[])
{
struct ElemType x;
//定义tag用于保存或查找函数的返回值
int tag;
printf("输入待删除元素x的关键字:");
scanf("%d", &x.key);
tag = HFDelete(filename, &x);
if(1 == tag)
{
printf("--- 删除成功! %d %s ---\n", x.key, x.rest);
}
else
{
printf("--- 删除失败 ---\n");
}//<end if>
}
void Search(char filename[])
{// 待完善,rest(其余信息)查找
struct ElemType x;
//定义tag用于保存或查找函数的返回值
int tag;
printf("输入待查找元素x的关键字:\n");
scanf("%d", &x.key);
tag = HFSearch(filename, &x);
if(1 == tag)
{
printf("--- 查找成功!%d %s ---\n", x.key, x.rest);
}
else
{
printf("查找失败!\n");
}
}
void welcome()
{// 打印欢迎界面
green;
printf("\t## ## ### ###### ## ## ######## #### ## ######## \n\t## ## ## ## ## ## ## ## ## ## ## ## \n\t## ## ## ## ## ## ## ## ## ## ## \n\t######### ## ## ###### ######### ###### ## ## ###### \n\t## ## ######### ## ## ## ## ## ## ## \n\t## ## ## ## ## ## ## ## ## ## ## ## \n\t## ## ## ## ###### ## ## ## #### ######## ######## \n");
white;
system("pause");
system("cls");
}
void start()
{// 开始
int number; //选择的功能号表
//检测散列文件是否存在
existHash(filename);
while(1)
{
// print meum
printf("\n");
yellow;
printf("\t+----------------------------------+\n");
printf("\t|----------- 散列文件 -------------|\n");
printf("\t|--1-- 初始化散列文件 -------------|\n");
printf("\t|--2-- 向散列文件中插入一个元素 ---|\n");
printf("\t|--3-- 从散列文件中删除一个元素 ---|\n");
printf("\t|--4-- 从散列文件中查找一个元素 ---|\n");
printf("\t|--5-- 打印散列文件 ---------------|\n");
printf("\t+--0-- 结束运行 -------------------|\n");
printf("\t+----------------------------------+\n");
white;
printf("*--请输入你的选择(0-5):");
scanf("%d", &number);
//fflush(stdin);// 防止错误输入
switch(number)
{
case 0:
return ;
case 1:
//初始化散列文件
InitHashFile(filename);
break;
case 2:
//向散列文件中插入一个元素
Insert(filename);
break;
case 3:
//从散列文件中删除一个元素
Delete(filename);
break;
case 4:
//从散列文件中查找一个元素
Search(filename);
break;
case 5:
//打印散列文件
HFPrint(filename);
break;
default:
printf("\n--- 输入功能号表错误 ---\n");
break;
} //switch结束
//system("pause"); 不能使用 test data 连续输入
printf("请按任意键继续. . .");
getch();
system("cls");
}
}
int main()
{
welcome();
start();
return 0;
}
/*
test data:
Insert:
2
10 a
2
11 b
2
12 c
2
13 d
2
14 e
2
15 f
2
16 g
2
17 h
2
18 i
2
19 j
2
20 k
2
21 l
2
22 m
2
23 n
2
24 o
2
25 p
*/
- 哈希表说明:
例如:0:3->13(d)
0:为地址,13%13后是0,故该节点插入位置为0到地址;
3:为插入顺序,类似数据库中的id;
13:是key关键字;
d:是剩余信息;