GDUT数据结构课设——构建开放定址哈希表

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测试结果

  1. 用户交互界面
  2. A选项把输入的值构造哈希表
  3. B选项插入一个关键字(成功时)
  4. B选项插入一个关键字(已存在时)
  5. C选项查找一个关键字(找到时)
  6. C选项查找一个关键字(不存在时)
  7. D选项根据关键字删除时(删除成功)
  8. D选项根据关键字删除时(删除失败)
  9. E选项退出程序
  • 5总结与体会

在进行实验研究开放定址法时,我对这种常用的哈希冲突解决方法有了更深入的理解,以下是我对实验的总结与体会。

开放定址法是一种解决散列冲突的方法,它通过在散列表中探测下一个可用的空槽位,将冲突的元素放置在其他位置。实验过程中,我使用了线性探测的开放定址法,其基本原理是当发生冲突时,顺序地检查下一个槽位,直到找到一个空槽位为止。

在实验中,我首先创建了一个空的哈希表,并使用哈希函数将关键字映射到散列表中的槽位。然后,我生成了一系列具有相同哈希值的关键字,模拟了散列冲突的情况。当发生冲突时,使用线性探测的方法,顺序地检查下一个槽位,直到找到一个空槽位来存储冲突的元素。我还实现了散列表的插入、查找和删除操作,并测试了这些操作在开放定址法下的性能。

结合课本并经过自己的理解消化吸收,合理的运用了各个端口函数,实现了哈希表的构建、增加、查找、删除功能,学习的过程十分的让人沉浸其中。

总的来说,通过这次实验,我对开放定址法有了更深入的理解。我认识到它是一种简单高效的散列冲突解决方法,但在实际应用中需要考虑负载因子等因素。进一步的研究和实践可以帮助我们更好地理解和应用开放定址法,以满足不同场景下的需求。

  • 6思考题

优缺点分析

方法

链地址法

开放定址法

优点

无堆聚现象,ALS短

算法实现相对简单

删除易实现

特定情况下更优

缺点

时空复杂度较高

删除困难

处理冲突性能受装填因子影响不大

装填因子大于0.6后处理冲突性能急剧下降

该文章已在bilibili发表专栏:数据结构课设——构造开放定址哈希表 - 哔哩哔哩 (bilibili.com)

录制讲解视频后会放到评论区。

  • 22
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YZYwpt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值