哈希表设计与实现数据结构课程设计.docx
《哈希表设计与实现数据结构课程设计.docx》由会员分享,可在线阅读,更多相关《哈希表设计与实现数据结构课程设计.docx(22页珍藏版)》请在冰豆网上搜索。
![哈希表设计与实现数据结构课程设计.docx](https://file1.bdocx.com/fileroot1/2023-5/17/64aecd35-5328-4907-b901-b987d188bf00/64aecd35-5328-4907-b901-b987d188bf001.gif)
哈希表设计与实现数据结构课程设计
课程名称:
数据结构
XXXXXXXX
本科学生课程设计(论文)
题目哈希表的设计与实现
姓名XXX
学号XXXXXXXXXXXX
学部计算机科学与技术
专业、年级计算机科学与技术大二
指导教师XX
2010年11月28日
摘要
随着信息技术的发展,关于各种程序中的数据结构也是层出不穷,对于项目某一方面的计算或者是某一方面的研究,出现了专门的数据结构,哈希表就是其中之一,哈希表作为另类的一种数据结构,其作用也是区别于其它同类的数据结构的,它是由两部分组成的:
键(key)和值,通过键可以迅速的查找到你需要的值。
常见的构造哈希函数的方法有直接定址法除留余数法平方取中法数字分析法等。
一般创建哈希表时可能会出现很多的冲突,常用的处理冲突的方法为开放定址法再哈希法链地址法建立一个公共溢出区。
关键词:
数据结构;哈希表;键(key);
第1章前言与系统实现
1.1前言
在信息化时代的今天,计算机技术已经是发展到一个很可观的地步了,特别是面向窗口的操作系统的出现,使得程序设计更加的容易了。
在过去计算机内存容量小,CPU计算速度慢,关于程序设计中的数据结构也因此提出来很多的关于解决这方面的问题。
哈希表就是其中之一,哈希表是一个由关键字与值组成的特殊的一种数据结构。
它的出现主要是为了解决在结构中查找记录时需要进行一系列和关键字的比较,这一类查找方法是建立在“比较”的基础上的,在顺序等的查找中,查找的效率是依赖于查找过程中所比较的次数。
理想的情况是希望不经过任何的比较一次存取便能得到所查记录,那就必须在记录的存储位置和它的关键字之间建立一个确定的对应关系,使得每个关键字和结构中一个唯一的存储位置相对应。
因而在查找时只要根据这个对应关系找到给定的值的像。
若结构中存在关键字和该值相等的记录,则所要查找的数就必定就是这个所查找到的记录。
哈希函数是建立哈希表的一个重要的成员,它的构造方法分为以下几种:
直接定址法、数字分析法、平方取中法、折叠法、除留余数法、随机数法。
本程序中主要用的是除余取留法,除留取余法主要是取关键字被某个不大于哈希表表长m的数p出后所得余数为哈希地址即:
H(key)=keyMODp,p<=m,这是一种最简单,也是一种最常用的构造函数的方法,它不仅可以对关键字直接取模,也可在折叠、平方中等运算之后取模。
在哈希表的建立中,很容易出现同义词,这些同义词的出现也导致了建立哈希表时冲突的出现,如果不解决这些冲突那么建立好的哈希表与预料的哈希表不同。
关于处理冲突的方法主要有:
开放定址法、再哈希法、链地址法。
本程序中主要用的就是链地址法莱解决冲突的。
1.2系统实现
本程序是在Vc++6.0环境下编写测试运行的。
1.2.1开发环境
表1-1列出了系统硬件配置,表6-2列出了系统软件配置。
设备名称
配置
CPU
E12002.6GHz
内存
128MB
硬盘
40GB
表1.1组装台式机配置
设备名称
版本
操作系统
WindowsXPsp3
开发环境
VisualStudioC++6.0
设计工具
VC++
表1.2软件环境
1.2.2VisualC++环境的安装
在计算机中安装VisualC++安装程序,VisualC++应用程序的开发主要有两种模式,一种是WINAPI方式,另一种则是MFC方式,传统的WINAPI开发方式比较繁琐,而MFC则是对WINAPI再次封装,所以MFC相对于WINAPI开发更具备效率优势。
本软件中因为程序主要是为了实现某个算法所以这里没有用到MFC。
第2章系统功能分析
2.1系统功能需求分析
实现本程序需要解决以下几个问题:
1.设计一个结点使该结点包括电话号码、用户名、QQ等结点信息。
2.利用用户名为关键字建立哈希表,哈希函数用除留余数法构照。
3.利用链表法处理冲突问题。
4.实现用哈希法查找并显示给定姓名的记录。
5.显示哈希表中的全部记录。
2.2任务定义
由功能需求分析知,本设计主要要求以用户名为关键字建立哈希表,并实现查找功能。
所以本设计的核心问题是如何解决散列的问题,亦即设计一个良好的哈希表。
根据题目的要求采用链地址法散列算法。
当出现同义词冲突时,使用链表结构把同义词链接在一起,即同义词的存储地址不是散列表中其他的空地址。
首先,解决的是定义链表结点,在链地址法中,每个结点对应一个链表结点,它由六个域组成,而由于该程序需要用用户名为关键字建立哈希表,所以该链表结点它是charstrName[20];charstrClass[20];charstrPhone[11];charstrqq[10];intnum;charstrAddress六个数据域和structName*next一个地址域组成。
构造哈希表的函数主要是用除留取余法来构造哈希函数的。
冲突的解决采用链地址法,具体的实现思想是,所有同义词构成一个单链表,再由一个表头结点指向这个单链表的第一个结点。
这些表头结点组成一个一维数组,即哈希表。
数组元素的下标对应由散列函数求出的散列地址。
第3章总体设计
3.1系统数据结构
本设计涉及到的数据结构为:
哈希表。
程序中建立了两个结构体,要求输入电话号码、用户名、QQ、地址、四个信息,给structName结构体变量,在创建哈希表时哈希函数用除留余数法构照,并把structName结构体中的数据赋值给哈希表结构体。
在链地址法中,每个结点对应一个链表结点,它由六个域组成,链地址法结点结构如表:
strName[20]
num
strAddress[30]
strPhone[11]
strClass[20]
Nqq
next
表3.1
其中哈希表是以用户名为关键字next指针是用来指向下一个结点的地址。
具体的存储结构如下图所示:
图3.1数据结构存储图
3.2主要算法流程图
3.2.1以姓名为关键字的CreateHashList()函数流程图
图3.2
3.2.2哈希表查找算法流程图
图3.3
3.2.3主程序流程图
图3.4
第4章详细设计和编码
4.1节点的建立
定义结构体如下所示:
typedefstructName
{
charstrName[20];//姓名
charstrClass[20];//班级
charstrPhone[11];//手机号码
charstrAddress[30];//地址
charNqq[10];//QQ
intnum;//关键字
structName*next;
}pName;
pNameNameList[HASH_LEN];
pNamek;
structHlist
{
pName*next;
}HashList[HASH_LEN];
4.2对哈希函数的定义
本程序设计一个hash()函数,本设计中按照题意要求知对散列函数选择的是除留余数法,即对关键字进行模运算,将计算结果所得的余数作为关键字(或结点)的存储地址,即H(key)=keymodp,在程序中p取的值为范围内的最大的素数,以用户名为关键字建立哈希函数CreateHash(),利用强制类型转换将用户名的每一个字母的ASCLL码值相加并且除以范围内最大的素数,将计算出来的数作为该结点的地址赋给adr。
然后通过以下几种方式就可以完成哈希表程序的设计了。
4.3创建哈希表算法、代码如下所示:
4.3.1算法
建立结点,并添加结点,利用链地址法解决冲突。
建立结点应包括动态申请内存空间。
向结点中输入信息。
同时将结点中的next指针等于null。
添加结点,首先需要利用哈希函数计算出地址即关键字,其次将该结点插入以关键字为地址的链表后。
4.3.2代码
voidCreateHashList()
{
inti;
for(i=0;i{
HashList[i].next=NULL;
}
for(i=0;i<2;i++)
{
structHlist*q;
pName*p,*m;
intadr=(NameList[i].num)%M;//哈希函数
if(HashList[adr].next==NULL)//表明不冲突
{
HashList[adr].next=&NameList[i];
}
else//表明冲突
{
q=&HashList[adr];
m=q->next;
while(m->next!
=NULL)
{
m=m->next;
}
p=(pName*)malloc(sizeof(pName));
strcpy(p->strName,NameList[i].strName);
strcpy(p->strPhone,NameList[i].strPhone);
strcpy(p->strAddress,NameList[i].strAddress);
strcpy(p->Nqq,NameList[i].Nqq);
p->num=NameList[i].num;
m->next=p;//单链表向后指
}
}
}
4.4哈希查找
想要实现查找功能,需要一个查找函数,以用户名为关键字来实现查找,首先,需要利用hash函数来计算出地址。
再通过比对,如果该地址中的用户名拼音字符相加的num与查找的相同则输出该结点的所有信息,否则输出“无此记录”。
具体实现代码如下所示:
voidSearchList()
{
system("cls");
char*f;
printf("\n\n请输入你要查找姓名(拼音)");//输入姓名
charname[20];
scanf("%s",name);
f=name;
ints=0,r;
for(r=0;*(f+r)!
='\0';r++)//求出姓名的拼音所对应的整数也就是关键字
{
s+=(int)*(f+r);//利用字符与整数的自动转换相加字符的ASCII码
}
intadr=s%M;//使用哈希函数
if(HashList[adr].next==NULL)//通过指针的指向判断一个单链表中是否存在要查找的数
{
printf("无该记录");
}
else
{
if((HashList[adr].next)->next==NULL)
{
if((HashList[adr].next)->num==s)
{
inti=1;
printf(“\n姓名:
%s”,(HashList[adr].next)->strName);
printf(“班级:
%s”,(HashList[adr].next)->strClass);
printf(“电话:
%s”,(HashList[adr].next)->strPhone);
printf(“QQ:
%s”,HashList[adr].next)->Nqq);
printf(“地址:
%s\n”,(HashList[adr].next)->strAddress);
printf(“关键字:
%d”,(HashList[adr].next)->num);
printf(“查找长度:
%d”,i);
}
}
else
{
pName*m;
inti=1;
m=HashList[adr].next;
while(m->next!
=NULL)//循环该单链表查找出你所要查找的数据并把他们输出
{
if(m->num==s)
{
Printf(“\n姓名:
%s”,m->strName);
Printf(“班级:
%s”,m->strClass);
Printf(“电话:
%s”,m->strPhone);
Printf(“QQ:
%s”,m->Nqq);
Printf(“地址:
%s”,m->strAddress);
Printf(“关键字:
%d”,m->num);
printf("查找长度:
%d",i);
break;
}
m=m->next;
i++;
}
}
}
}
4.5显示哈希表
通过姓名关键字,循环查找有值的下标并输出来,具体设计代码如下:
voidDisplay()
{
system("cls");
inti,s,j,adr,n=0;
char*f;
structHlist*m;
structName*k;
charname[20];
printf("\n\n下标地址\t关键字\tH(key)\t拼音班级QQ地址电话\n");//显示的格式
for(i=0;i<2;i++)
{
s=0;
strcpy(name,NameList[i].strName);
f=name;
for(j=0;*(f+j)!
='\0';j++)
{
s+=(int)*(f+j);
}
adr=s%M;
m=&HashList[adr];
if(m->next!
=NULL)
{
k=m->next;
n+=1;
while(k->next!
=NULL)
{
printf("下标地址:
%d",i);
printf("\t%d",k->num);
printf("\t%d",(k->num)%M);
printf("\t%s",k->strName);
printf("%s",k->strClass);
printf("%s",k->Nqq);
printf("%s",k->strAddress);
printf("%s",k->strPhone);
printf("\n");
k=k->next;
}
printf("下标地址:
%d",i);
printf("\t%d",k->num);
printf("\t%d",(k->num)%M);
printf("\t%s",k->strName);
printf("%s",k->strClass);
printf("%s",k->Nqq);
printf("%s",k->strAddress);
printf("%s",k->strPhone);
printf("\n");
}
}
}
4.6主菜单设计
根据题目中的要求我们只要写出哈希表的初始化查找显示三个功能,代码如下所示:
voidMenu()
{
printf("\n------------------------哈希表的建立和查找操作----------------------");
printf("\n\n");
printf("1.哈希表初始化\n");
printf("2.显示哈希表\n");
printf("3.查找\n");
printf("4.退出\n");
}
4.7主函数设计
主函数是一个软件运行的入口,其通过调用自定义函数来完成相应的功能,其实现代码如下所示:
intmain(intargc,char*argv[])
{
inti,f=0;
while
(1)
{
Menu();
scanf("%d",&i);
switch(i)
{
case1:
InitNameList();
CreateHashList();
f=1;
break;
case2:
if(f==1)
{
Display();
break;
}
else
{
printf("哈希表未初始化请先初始化再操作");
}
break;
case3:
if(f==1)
{
SearchList();
}
else
{
printf("哈希表未初始化请先初始化再操作");
}
break;
case4:
return0;
default:
printf("请输入正确的选项");
break;
}
}
}
第5章程序运行测试
5.1程序主界面
图5.1程序主界面
5.2哈希表初始化
测试数据(正确):
姓名:
kaluo班级:
305电话:
123456789地址:
长沙涉外QQ:
282265478
姓名:
tianxia 班级:
306电话:
987654321地址:
长沙理工QQ:
974562228
姓名:
xiantu 班级:
3076电话:
987654322地址:
长沙学院QQ:
974562222
测试数据(错误):
姓名:
张三(否拼音) 班级:
3077电话:
987654322地址:
长沙学院QQ:
974562222
姓名:
李四(否拼音) 班级:
3078电话:
987654322地址:
长沙学院QQ:
974562222
图5.2哈希表正确初始化
图5.3哈希表的错误初始化
图5.4哈希表初始化
5.3按姓名查找记录
图5.5输入查找条件
图5.6查找结果
5.4显示哈希表全部记录
图5.7显示表中全部记录
总结
1、语法错误及修改:
程序是分块写的,调试时可以使用分步调试的方式进行,以便能查找看程序是在哪出错了。
程序中使用了链表结构和链地址法解决冲突的问题,以姓名为关键字的哈希表中要注意涉及ASCLL码的类型转换。
2、逻辑问题修改和调整:
链表结构方法虽然方便了运行,但是增加了对算法过程的认识难度。
在本程序中每一个函数中都需要涉及到指针的操作。
所以需要仔细分析函数中的指针指向。
3、时间,空间性能分析:
散列法本质上是一种通过关键字直接计算存储地址的方法。
在理想情况下,散列函数可以把结点均匀地分布到散列表中,不发生冲突,则查找过程无需比较,其时间复杂度O(n)=1。
但在实际使用过程中,为了将范围广泛的关键字映射到一组连续的存储空间,往往会发生同义词冲突,这时在查找过程中就需要进行关键字比较。
因此散列法的查找性能取决于3个因素:
散列函数、冲突处理方法和填充因子。
采用链地址法,可以从根本上杜绝“二次聚集”的发生,从而提高散列表的均匀度,提高查找性能,不过也会“浪费”一部分散列表的空间。
当散列函数和冲突处理办法固定时,散列法的查找性能就取决于散列表的填充因子。
填充因子a=表中已有的结点数/表的长度。
填充因子a标志表的添满程度。
很显然,a越小则发生冲突的机会就越小;反之,a越大冲突的机会就越大,查找的性能也就越低。
哈希表链地址法查找成功的平均查找长度SNc=1+a/2。
链地址法查找不成功的平均查找长度Un满足:
Unc=a+e-a.由以上可以看出,散列表的平均查找长度是填充因子的函数,和散列表的长度没有关系,因此在实际应用中,我们应该选择一个适当的填充因子,以便把平均查找长度控制在一个尽量小的范围内。
参考文献
[1](美)佛罗赞(Forouzan).计算机科学导论[M].北京:
机械工业出版社,2004.217~219
[2]严蔚敏吴伟民.数据结构[M].北京:
清华大学出版社,1997.251~259
[3]王昆仑李红.数据结构与算法[M].北京:
中国铁道出版社,2007.6
[4]李春葆.数据结构题集[M].北京:
清华大学出版社,1992.2
[5]武马群缪春池吕峻闽.C语言程序设计.北京:
北京工业大学出版社,2006.5