GDUT2024春数据结构课设,没想到自己根本不用做。。。如果有同期课的同学没做课设,刚好可以拿我的这个交了哈哈哈,记得在评论区“宣示一下主权”(20xx春/秋 Sam 已采用),免得多人直接复制被老师发现就麻烦啦!(不过感觉过一个学期的话老师应该不会发现。。。)
其中,课本是有部分代码的;接口也是参考课本的。
咱们就不设VIP要求了,主打一个同学互助!
交互界面
-
1实验题目
开放定址哈希表的实现
-
2实验目的
对某组具体的抽象数据类型,运用课程所学的知识和方法,设计合理的数据结构,并在此基础上实现该抽象数据类型的全部基本操作。通过本设计性实验,检验所学知识和能力,发现学习中存在的问题。 进而达到熟练地运用本课程中的基础知识及技术的目的。
-
3运行环境
硬件:华硕幻15
软件:Microsoft Visual Studio 2019
-
4详细设计
4.1ADT HashTable
采用整型为元素类型和直接寻址表为存储结构,通过开放定址法进行冲突处理,实现抽象数据类型哈希表HashTable。
ADT HashTable{
数据对象:D={}
数据关系:R1={}
基本操作:
InitHash(&H,size,hash), collision)
初始条件:hash函数,collision函数已存在,哈希表size已定义。
操作结果:初始化一个哈希表,分配存储空间,tag全置零。
DestroyHash(&H)
初始条件:哈希表已存在。
操作结果:释放哈希表所占所有空间。
CreateHash(&H)
初始条件:哈希表已初始化。
操作结果:给rcd和tag置零。
SearchHash(H, key, &p, &c)
初始条件:哈希表已存在。
操作结果:全表检索关键字key,找到则返回1,没找到则返回0。
InsertHash(&H, e)
初始条件:哈希表已存在,提供待插入的存储结构体指针。
操作结果:若存储结构体e的key已存在于表中,则返回c=0;若成功插入key, 则返回经过计算后的c值。
DeleteHash(&H, key, &e)
初始条件:哈希表已存在,提供待删除的关键字key。
操作结果:若关键字存在于表中,则可以删除,返回1;若关键字不在表中, 则返回0。
}ADT HashTable
4.2存储结构定义
公用头文件yzyhash.h:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define OVERFLOW -2
#define SUCCESS 1
#define UNSUCCESS 0
#define DUPLICATE -1
#define NUM 10 //预设置关键字可放入长度
#define NULLKEY -1
typedef char ElemType;
typedef int KeyType;
typedef int Status;
typedef struct{//构造结构体
KeyType key;
}RecordType, RcdType;
//开放定址哈希表
typedef struct{
RcdType *rcd; //关键字地址
int size;//哈希表容量
int count;//登记表中个数
int * tag; //标记 :0空位,1占用,-1已删除
int * Col; //标记冲突次数
int (*hash)(KeyType key, int hashSize);//哈希函数
void(*collision)(int &hashValue, int hashSize);//冲突处理函数
}HashTable;
4.3算法设计
4.3.1基本操作实现
int size = 20; //初始化表长
Status InitHash(HashTable &H, int size, int(*hash)(KeyType, int), void (*collision)(int &, int)){
//初始化操作
int i;
H.rcd = (RcdType *)malloc(size * sizeof(RcdType)); //分配size存储空间
H.tag = (int *)malloc(size * sizeof(int)); //分配tag空间
H.Col = (int *)malloc(size * sizeof(int)); //分配col空间(冲突次数登记)
if (NULL == H.rcd || NULL == H.tag)return OVERFLOW;
for(i=0; i<size; i++) H.tag[i] = 0; //先把各个标记tag置空
H.size = size;//录入对应的哈希表大小
H.hash = hash;//录入哈希函数
H.collision = collision;//录入冲突处理函数
H.count = 0;//目前关键字总计个数为0
return OK;
}
Status DestroyHash(HashTable &H){ //销毁哈希表
free(H.rcd);//释放结构体指针
free(H.tag);//释放整型指针
H.rcd = NULL;
H.tag = NULL;
H.size = 0;
H.count = 0;
return OK;
}
Status CreateHash(HashTable &H){//构造哈希表
int i;
for(i = 0; i<H.size; i++)
{
H.tag[i] = 0;
H.rcd[i].key = 0;//在初始化的基础上,标记每个关键字为0
H.Col[i] = 0;//初始化基础实,标记每个冲突为0
}
H.count = 0;
return OK;
}
Status SearchHash(HashTable H, KeyType key, int &p, int &c){//查找key记录
p = H.hash(key, H.size);//取得地址
while ((1 == H.tag[p] && H.rcd[p].key != key) || -1 == H.tag[p]){
H.collision(p, H.size);//若该位置已被其他关键字占用或已被删除,则发生冲突,用线性探测法进行冲突处理
c++;//记录冲突次数
}
if (H.rcd[p].key ==key && 1 == H.tag[p]) return SUCCESS;//找到了
else return UNSUCCESS;//没找到
}
void collision(int &hashValue, int hashSize){//线性探测法冲突处理
hashValue = (hashValue+1)%hashSize;//其实就是+1再取模
}
int InsertHash(HashTable &H, RcdType e){//插入e操作
int c = 0, j;
if (SUCCESS == SearchHash(H, e.key, j, c)) {//如果找到了,就不用插入了
H.Col[j] = c;
return c;
}
else{//没找到的时候可以插入
H.rcd[j] = e;
H.tag[j] = 1;
++H.count;
//printf("conter with %d\t", c);
H.Col[j] = c;
return c; //返回冲突次数
}
}
Status DeleteHash(HashTable &H, KeyType key, RcdType &e){//删除key记录
int j, c;
if (UNSUCCESS == SearchHash(H, key, j, c))
return UNSUCCESS;//不存在关键字记录,删除失败
else {
e = H.rcd[j];//被删除的key保存到e
H.tag[j] = -1;
H.count--;
return SUCCESS;
}
}
4.3.2自定义算法实现
int hash(KeyType key, int hashsize){//自定义的哈希函数
return (3*key)%hashsize;
}
void menu() {//用户交互界面
printf("**************************************\n");
printf("** HashTable **\n");
printf("** 姓名:ZZZ 学号:3xxxxxxxxxx **\n");
printf("** A:输入值以生成哈希表 **\n");
printf("** B:插入 **\n");
printf("** C:查找 **\n");
printf("** D:删除 **\n");
printf("** E:退出程序 **\n");
printf("**************************************\n");
}
//键入获取字符
char inpChar() {
char c = getchar();
while (getchar() != '\n');
{
return c;
}
}
HashTable H;
KeyType keys[NUM];
void Output() {//输出整个哈希表
printf("\tHash Table:\n");
printf("Index\tKey\ttag\tCollisions\n");//内容包括:序号、关键字值、标记、冲突次数
for (int j = 0; j < H.size; j++) {
int p, c;
SearchHash(H, H.rcd[j].key, p, c);
printf("%d\t%d\t%d\t%d\n", j, H.rcd[j].key, H.tag[j], H.Col[j]);
}
}
Status MakeHash() {
//获取关键字数组
printf("Enter the keys:(At most %d number and typein -1 to terminal)\n", NUM);
int* dynamicA = NULL;//创建一个动态空数组
int big = 0;//其实是计数器
int element;
while (true)
{
scanf_s("%d", &element);
if (element == -1)//识别键入内容,直到“-1”就停止键入
{
break;
}
big++;//每次键入都进行计数
int* newArray = (int*)realloc(dynamicA, big * sizeof(int));//动态分配空间
if (newArray == NULL) {
printf("Memory reallocation failed.\n");
return TRUE; // 返回非零值表示错误
}
dynamicA = newArray;//新旧指针对应关系
dynamicA[big - 1] = element;//把数据给到对应位置
}
/*for (int i = 0; i < big; i++) {//测试代码,当时为了检验是否成功键入
printf(" we have got %d\n ", dynamicA[i]);
}*/
printf("we have totally %d numbers\n", big);//输出最终的键入个数
//插入关键字
int keylen = big;//这时候动态数组长度就是关键字个数
printf("the keys length is %d\n", keylen);
for (int i = 0; i < keylen; i++)
{
RcdType record;//构造一个结构体指针
record.key = dynamicA[i];//把关键字进行一一对应
int collisionCount = InsertHash(H, record);//每次对应都是一次插入操作,登记冲突次数
printf("key %d inserted with %d collisions.\n", dynamicA[i], collisionCount);
}
Output();//输出整个哈希表
}
Status ChaRu() {//实现插入操作
RcdType e;
printf("Please tell me what number do you want to put in:\n");
scanf_s("%d", &e.key);
int c = 0, j;
if (SUCCESS == SearchHash(H, e.key, j,c))
{//当已经成功检索到,就无需插入,插入失败
printf("The number %d already exist!\n", e.key);
}
else
{//否则,进行常规插入操作
InsertHash(H, e);
printf("Insert '%d' Successfully!\n", e.key);
}
Output();//显示插入后的整个表
return SUCCESS;
}
Status ChaZhao() {//自定义查找操作
KeyType key;
int c=0;
int p;
printf("Please tell me which number you want to find out:\n");
scanf_s("%d", &key);
if (SearchHash(H, key, p, c) != 0) {//检索关键字key,若检索函数返回非0,即成功找到,返回关键字信息及冲突次数
printf("Yes we have %d.Its collisions happened %d times.\n", key, c);
}
else
{//若检索函数返回为0即检索失败,不存在该关键字
printf("Sorry, we don't have this number. Maybe you can insert it.\n");
}
Output();
return SUCCESS;
}
Status ShanChu() {//自定义删除操作
RcdType e;
KeyType key;
Output();//先把整个表放出来,方便选择要删除什么
printf("Please tell me which number you want to delete:\n");
scanf_s("%d", &key);
if (DeleteHash(H, key, e) == 1) {
printf("Delete '%d' Successfully!\n", e.key);
}//进行删除操作
else
{
printf("Sorry, the number you enter doesn't exist.\n");
}
Output();//再次把整个表放出来,展示删除后的结果
return SUCCESS;
}
4.4测试
4.4.1测试代码
int main() {
char inp;
//初始化
if (InitHash(H, size, hash, collision) != OK) {
printf("Failed to initialize hash table.\n");
return FALSE;
}
CreateHash(H);//创建哈希表
menu();//展示交互选项ABCDE
while (true)
{
printf("\n请输入操作选项(或回车以查看选项)");
inp = inpChar();//获取键入选项
menu();//展示交互选项ABCDE
switch (inp)
{
case'A':
case'a':
MakeHash();//A选项实现把键入的多个内容生成哈希表
break;
case'B':
case'b':
ChaRu();//B选项实现单个插入一个元素
break;
case'C':
case'c':
ChaZhao();//C选项实现查找哈希表中的关键字key,返回冲突次数
break;
case'D':
case'd':
ShanChu();//D选项实现删除某个位置
break;
case'E':
case'e':
printf("已退出程序!");//E选项即退出程序
DestroyHash(H);
return 0;
default:
printf("请重新输入或退出程序");
}
}
}
4.4.2测试结果
- 用户交互界面
- A选项把输入的值构造哈希表
- B选项插入一个关键字(成功时)
- B选项插入一个关键字(已存在时)
- C选项查找一个关键字(找到时)
- C选项查找一个关键字(不存在时)
- D选项根据关键字删除时(删除成功)
- D选项根据关键字删除时(删除失败)
- E选项退出程序
-
5总结与体会
在进行实验研究开放定址法时,我对这种常用的哈希冲突解决方法有了更深入的理解,以下是我对实验的总结与体会。
开放定址法是一种解决散列冲突的方法,它通过在散列表中探测下一个可用的空槽位,将冲突的元素放置在其他位置。实验过程中,我使用了线性探测的开放定址法,其基本原理是当发生冲突时,顺序地检查下一个槽位,直到找到一个空槽位为止。
在实验中,我首先创建了一个空的哈希表,并使用哈希函数将关键字映射到散列表中的槽位。然后,我生成了一系列具有相同哈希值的关键字,模拟了散列冲突的情况。当发生冲突时,使用线性探测的方法,顺序地检查下一个槽位,直到找到一个空槽位来存储冲突的元素。我还实现了散列表的插入、查找和删除操作,并测试了这些操作在开放定址法下的性能。
结合课本并经过自己的理解消化吸收,合理的运用了各个端口函数,实现了哈希表的构建、增加、查找、删除功能,学习的过程十分的让人沉浸其中。
总的来说,通过这次实验,我对开放定址法有了更深入的理解。我认识到它是一种简单高效的散列冲突解决方法,但在实际应用中需要考虑负载因子等因素。进一步的研究和实践可以帮助我们更好地理解和应用开放定址法,以满足不同场景下的需求。
-
6思考题
优缺点分析 | 方法 | 链地址法 | 开放定址法 |
优点 | 无堆聚现象,ALS短 | 算法实现相对简单 | |
删除易实现 | 特定情况下更优 | ||
缺点 | 时空复杂度较高 | 删除困难 | |
处理冲突性能受装填因子影响不大 | 装填因子大于0.6后处理冲突性能急剧下降 |
该文章已在bilibili发表专栏:数据结构课设——构造开放定址哈希表 - 哔哩哔哩 (bilibili.com)
录制讲解视频后会放到评论区。