算法与数据结构查找.docx
《算法与数据结构查找.docx》由会员分享,可在线阅读,更多相关《算法与数据结构查找.docx(65页珍藏版)》请在冰豆网上搜索。
算法与数据结构查找
北京邮电大学软件学院
2019-2020学年第1学期实验报告
课程名称:
算法与数据结构课程设计
实验名称:
查找
实验完成人:
日期:
2019年12月12日
一、实验目的
本次实验旨在集中对几个专门的问题作较为深入的探讨和理解,不强调对某些特定的编程技术的训练。
。
。
二、实验内容
必做内容
【必做内容】
二叉排序树
[问题描述]
从键盘读入一组数据,建立二叉排序树并对其进行查找、遍历、格式化打印等有关操作。
[基本要求]
建立二叉排序树并对其进行查找,包括成功和不成功两种情况,并给出查找长度。
[测试数据]
由学生依据软件工程的测试技术自己确定。
注意测试边界数据。
哈希表设计
[问题描述]
针对某个集体中人名设计一个哈希表,使得平均查找长度不超过R,并完成相应的建表和查表程序。
[基本要求]
假设人名为中国人姓名的汉语拼音形式。
待填入哈希表的人名共有30个,取平均查找长度的上限为2。
哈希函数用除留余数法构造,用线性探测再散列法或链地址法处理冲突。
[测试数据]
取你周围较熟悉的30个人名,以你自己的姓名全拼作为第一条数据。
【选做内容】
实现二叉排序树的插入、删除操作。
(已完成)
从教科书上介绍的集中哈希函数构造方法中选出适用者并设计几个不同的哈希函数,比较他们的地址冲突率(可以用更大的名字集合作实验)。
(已完成)
研究必做实验2)的30个人名的特点,努力找一个哈希函数,使得对于不同的拼音名一定不发生地址冲突。
在哈希函数确定的前提下尝试各种不同处理冲突的方法,考察平均查找长度的变化和造好的哈希表中关键字的聚集性。
(已完成)
。
三、实验环境
Windows10
vs2019
四、实验过程描述
1.二叉搜索树:
先设计二叉搜索树类。
该类继承自树类,可以用树的遍历等功能。
classSortTree:
publicTree
{
public:
Kindkind=sort;
SortTree():
Tree(){}//创建的空二叉树用来继承
SortTree(Kind);//创建搜索二叉树
~SortTree(){
Release(root);//删除二叉树(继承自树的功能)
}
voidSearch(ElemType);//查找目的元素,用户使用的查找
boolInsert(ElemType);//插入目的元素
voidDelete(ElemType);//删除目的元素
private:
voidDelete(BiTree&);
boolSearch(ElemType,BiTree,BiTree,BiTree&);//内部使用的查找
voidCreateSortTree();//创建二叉树
boolDelete(BiTree&,ElemType);
};
实现相应功能:
二叉搜索树的创建:
先让用户输入结点数,然后提示用户输入结点的值,再插入该结点。
voidSortTree:
:
CreateSortTree(){
cout<<"请输入结点数:
"<intn,i=1;
cin>>n;
while(i<=n){
cout<<"请输入第"<
";
ElemTypeval;
cin>>val;
if(!
Insert(val))//如果已存在该结点
{
cout<<"该结点已存在"<}
else
i++;
}
}
查找:
设计了两个查找,第一个查找是递归的,用来内部使用给插入做铺垫,可以得到要插入的结点插入的位置。
第二个查找是非递归的,用来给用户使用。
内部使用的查找:
传入待查找元素key,搜索二叉树T,T的父节点f(初始时为null),以及查找路径上最后一个结点p(初始时为Null)。
函数递归的查找key,如果key小于结点的值就递归的使用此函数,并传入T的左孩子以及其父节点T,如果大于则同理。
最终如果查找到key则将p指向该结点,如果查找不到则将p指向f(T的父节点,由于此时T为NULL,所以f为查找路径上的最后一个结点)。
boolSortTree:
:
Search(ElemTypekey,BiTreeT,BiTreef,BiTree&p){
//key为关键字,T为二叉搜索数,f为T的父结点,初始时为null,查找成功时,p指向该结点,不成功时,指向要插入的位置
//该函数查找key是否存在,并修改p的值
//因为要传入p并修改p的值,所以用引用
if(!
T)//查找不成功
{
p=f;//p指向查找路径上访问的最后一个结点
returnfalse;
}
elseif(key==T->data)//查找成功
{
p=T;//p指向该节点
returntrue;
}
elseif(keydata)
returnSearch(key,T->lchild,T,p);//在左子树中继续查找
else
returnSearch(key,T->rchild,T,p);//在右子树中继续查找
}
给用户使用的查找:
非递归查找,如果key小于该结点的值则向左子树查找,反之则向右子树查找
voidSortTree:
:
Search(ElemTypekey){
BiTreep=this->root;
while(p){
if(p->data==key){
cout<<"该元素存在且查找长度为"<level<break;
}
elseif(p->data>key)
{
p=p->lchild;
}
else
p=p->rchild;
}
if(!
p)//不存在时
cout<<"不存在该元素"<}
插入:
传入数据e,然后比较大小。
主要依靠内部的查找函数,得到查找路径上最后一个结点,方便后续的插入
boolSortTree:
:
Insert(ElemTypee){//插入失败返回false,成功返回true
BiTreep=NULL,s=NULL;//p为插入结点的父结点
if(!
Search(e,root,NULL,p))//查找不成功
{
s=newBiTNode;
s->data=e;
s->lchild=s->rchild=NULL;
if(!
p)//树为空时创建新结点
this->root=s;//被插结点s为新的根结点
elseif(edata)
p->lchild=s;//被插结点s为左孩子
else
p->rchild=s;//被插结点s为右孩子
returntrue;
}
else
returnfalse;//树中已有关键字相同的结点,不再插入
}
删除:
若待删结点左子树或右子树为空则直接删除并重接其子树即可。
如果左右子树都不为空则需要找到该结点的“前驱“,并将前驱结点的值代替该结点的值。
最后还需重连前驱结点的子树。
若待删结点的左子树有右孩子,则需要重连前驱的前驱的右孩子,如果待删结点的左左子树没有右孩子,则只需将前驱结点的左子树接到被删除结点的左子树上。
voidSortTree:
:
Delete(BiTree&p)//从二叉排序树中删除结点p,并重接它的左或右子树
{
BiTreeq,s;
if(!
p->rchild)//p的右子树空则只需重接它的左子树(待删结点是叶子也走此分支)
{
q=p;
p=p->lchild;
deleteq;
}
elseif(!
p->lchild)//p的左子树空,只需重接它的右子树
{
q=p;
p=p->rchild;
deleteq;
}
else//p的左右子树均不空
{
q=p;
s=p->lchild;//转左,
while(s->rchild)//然后向右到尽头(找待删结点的前驱)
{
q=s;//此时q是s的上一个节点,即被删除结点的前驱的前驱
s=s->rchild;
}
p->data=s->data;//s指向被删结点的"前驱"(将被删结点前驱的值取代被删结点的值)
if(q!
=p)//情况1p的左子树有右孩子
q->rchild=s->lchild;//重接q的右子树
else//情况2p的左子树没有右孩子,此时s就是p的左孩子
q->lchild=s->lchild;//重接q的左子树
deletes;
}
}
为了给外部使用时,只需输入一个元素的值就能删除该元素而不需输入节点的值,所以设置了这两个函数,但本质上还是调用上面那个删除函数。
//外部使用
voidSortTree:
:
Delete(ElemTypee){
this->Delete(this->root,e);
}
//内部使用,查找加删除
boolSortTree:
:
Delete(BiTree&T,ElemTypekey){
if(!
T)
returnfalse;
else{
if(key==T->data)
{
Delete(T);
returntrue;
}
elseif(keydata)
returnDelete(T->lchild,key);
elseif(key>T->data)
returnDelete(T->rchild,key);
}
}
遍历、打印同树的操作一样:
voidprintT(BiTreeT){//打印二叉树图形
for(inti=0;ilevel;i++){
cout<<"";
}
cout<data<}
voidPrint(){
getLevel(root,1);//得到层数
InOrderTraverseRDL(&Tree:
:
printT);
}
//中序遍历RDL
voidTree:
:
InOrderTraverseRDL(BiTreeT,void(Tree:
:
*Visit)(BiTree))
{
if(T)
{
InOrderTraverseRDL(T->rchild,Visit);/*先中序遍历左子树*/
(this->*Visit)(T);/*再访问根结点*/
InOrderTraverseRDL(T->lchild,Visit);/*最后中序遍历右子树*/
}
}
2.哈希表:
先设计哈希表类:
typedefstruct{
stringname;//名字
intkey;//关键字
intsearch_length;//查找长度
}Hash;
classHashTable
{
public:
HashTable();//初始化哈希表的大小
~HashTable(){}
voidadd(string,ProbeKind,HashKind);//将元素添加到哈希表
voidSearch_Print(string,ProbeKind,HashKind);//查找哈希表中的元素并打印
voidAVL();//打印平均查找长度
voidcollision_R();//打印地址冲突率
voidPrint();//打印哈希表
private:
inttable_length;//表长
HashTable[TABLE_LENGTH];
//搜索
boolSearch(string,ProbeKind,HashKind);//内部使用,添加新元素前先查找是否存在
//哈希函数
inthash1(string);
inthash2(string);
inthash3(string);
//冲突处理
voidLinearProbe(string,int);//线性探测
voidSquaredProbe(string,int);//二次探测
voidRandomProbe(string,int);//伪随机探测
};
实现相应功能:
哈希表的创建:
初始化哈希表:
HashTable:
:
HashTable(){
table_length=TABLE_LENGTH;
for(inti=0;i//初始化哈希表
Table[i].name="";
Table[i].key=NULL_KEY;
Table[i].search_length=0;
}
}
插入元素:
选择一种哈希函数,再选择一种处理冲突的方法
voidHashTable:
:
add(strings,ProbeKindprobe,HashKindhash){
if(Search(s,probe,hash)){
std:
:
cout<<"该元素已经存在"<:
endl;
}
intkey=0;
switch(hash)//选择一种哈希函数
{
caseHashKind:
:
hash1:
key=hash1(s);
break;
caseHashKind:
:
hash2:
key=hash2(s);
break;
caseHashKind:
:
hash3:
key=hash3(s);
break;
default:
break;
}
switch(probe)//选择一种处理冲突方法
{
caselinear:
LinearProbe(s,key);
break;
casesquare:
SquaredProbe(s,key);
break;
caserandom:
RandomProbe(s,key);
break;
default:
break;
}
}
查找:
用何种哈希函数及处理冲突方法就需要用相应的方法去查找
//根据哈希函数与处理方法来查找
voidHashTable:
:
Search_Print(strings,ProbeKindprobe,HashKindhash){
intkey;
//确定所用的哈希函数
if(hash==HashKind:
:
hash1){
key=hash1(s);
}
elseif(hash==HashKind:
:
hash2){
key=hash2(s);
}
else
key=hash3(s);
intlength=0;
//确定所用的处理冲突方法
if(probe==linear){
while(Table[key].name!
=s){
if(Table[key].name=="")
{
cout<<"元素不存在"<break;
}
length++;
key=(key+1)%table_length;
}
}
elseif(probe==square){
intkey1=key,n=1;
while(Table[key].name!
=s){
if(Table[key].name=="")
{
cout<<"元素不存在"<break;
}
if(n%2){
key=(key1+(int)pow((n+1)/2,2))%table_length;
}
else{
key=(key1-(int)pow((n+1)/2,2))%table_length;
}
length++;
n++;
}
}
else
{
srand(SEED);
intkey1=key;
while(Table[key].name!
=s){
if(Table[key].name=="")
{
cout<<"元素不存在"<break;
}
key=(key1+rand())%table_length;
length++;
}
}
if(Table[key].name==s&&Table[key].search_length==length)
cout<<"名字"<
}
内部使用的查找:
用于插入前先看是否存在该元素,结构同上
boolHashTable:
:
Search(strings,ProbeKindprobe,HashKindhash){
intkey;
//确定所用的哈希函数
if(hash==HashKind:
:
hash1){
key=hash1(s);
}
elseif(hash==HashKind:
:
hash2){
key=hash2(s);
}
else
key=hash3(s);
intlength=0;
//确定所用的处理冲突方法
if(probe==linear){
while(Table[key].name!
=s){
if(Table[key].name=="")
returnfalse;
length++;
key=(key+1)%table_length;
}
}
elseif(probe==square){
intkey1=key,n=1;
while(Table[key].name!
=s){
if(Table[key].name=="")
returnfalse;
if(length%2){
key=(key1+(int)pow((n+1)/2,2))%table_length;
}
else{
key=(key1-(int)pow((n+1)/2,2))%table_length;
}
length++;
n++;
}
}
else
{
srand(SEED);
intkey1=key;
while(Table[key].name!
=s){
if(Table[key].name=="")
returnfalse;
key=(key1+rand())%table_length;
length++;
}
}
if(Table[key].name==s&&Table[key].search_length==length)
returntrue;
}
设计的三种处理冲突的方法:
1.线性探测,冲突的话向后加一位
voidHashTable:
:
LinearProbe(strings,intkey){
intk=0;//查找长度
while(Table[key].name!
=""){
key=(key+1)%table_length;
k++;
}
Table[key].name=s;
Table[key].key=key;
Table[key].search_length=k;
}
2.二次探测,冲突的话左移或右移一个平方数的距离
voidHashTable:
:
SquaredProbe(strings,intkey){
intn=1,k=0,key1=key;
while(Table[key].name!
=""&&n<=table_length/2){
if(n%2){
key=(key1+(int)pow((n+1)/2,2))%table_length;
}
else
key=(key1-(int)pow((n+1)/2,2))%table_length;
k++;
n++;
}
Table[key].name=s;
Table[key].key=key;
Table[key].search_length=k;
}
3.伪随机探测,冲突时后移一个随机距离
voidHashTable:
:
RandomProbe(strings,intkey){
srand(SEED);
intk=0,key1=key;//查找长度
while(Table[key].name!
=""){
key=(key1+rand())%table_length;
k++;
}
Table[key].name=s;
Table[key].key=key;
Table[key].search_length=k;
}
三个哈希函数:
1.将人名的所有字母的ascii码相加并除留余数
intHashTable
展开阅读全文
相关搜索