背景
静态查找表(Static Search Table):只作查找操作的查找表。
动态查找表(Dynamic Search Table):在查找过程同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素。
实验目的
基于课程“BST”,实现二叉查找树(BST),实现一个静态查找表。
基本要求
1.使用二叉查找树(BST)来实现。
2. 二叉树使用链式结构(二叉链表)实现。
3. 基于BST,实现静态查找表。
4. 在查找表中查找时,要输出关键字比较的次数。
需求分析
0问题描述
给定一个含有n个整数的集合,有一查找系统:用户可随意给出一个整数m,便可 通过该系统知道m是否在这个集合中,且可以知晓查找该整数m需要多少步。请你实 现该功能。
1.问题分析
实现的功能:
1.通过键盘输入整数的个数n以及n个整数还有查找的元素值m;
2.查找m是否在这n个整数中;
3.求出查找该整数m需要的步数;
4.通过屏幕显示是否查找成功以及比较的次数。
2.输入数据
第一行输入整数个数n,且0<n<=100;
第二行包含n个整数a1, a2, …, an,用空格分隔,a_1≠a_2≠⋯≠a_n,且-1000=<a1, a2, …, an<=1000;接下来的每一行都有一个整数m,作为待查元素值;最终以ctrl+Z结束输入。
3.输出数据
每当输入一个待查元素,就输出查找成功或不成功,并且后面有一个数字表示查找的次数,例如:
输入:21 //查找21,
输出:查找成功 1//表示查找时比较的次数。
4.测试样例设计
样例 1: 整数以升序排列(构建的树为斜树)
输入: 请输入数据个数: 4
请输入数据: 2 3 7 8
请输入欲查找的数:3
请输入欲查找的数:100
输出: 查找成功 2
查找失败 4
样例 2:集合中比第一个数小的数的个数大于比第一个数大的数的个数
输入: 请输入数据个数:5
请输入数据:8 4 10 6 3
请输入欲查找的数:4
请输入欲查找的数:10
请输入欲查找的数:2
输出 查找成功 2
查找成功 2
查找失败 3
样例 3:按照BST特性可构建出完全二叉树
输入: 请输入数据个数:5
请输入数据:20 9 30 7 25
请输入欲查找的数:25
输出: 查找成功 3
样例 4: 只有一个整数
输入: 请输入数据个数:1
请输入数据:2
请输入欲查找的数:0
请输入欲查找的数:2
输出: 查找成功 1
查找失败 1
样例 5:第一个数为这组数中最小的数
输入: 请输入数据个数:7
请输入数据:1 6 7 78 41 54 4
请输入欲查找的数:41
请输入欲查找的数:85
输出: 查找成功 5
查找失败 4
二、概要设计
1.抽象数据类型
数据对象:一组互不相等且可比较大小的整数
数据关系:空树或者有BST特性的二叉树
基本操作:
1.准备能储存这组数据的空间
2.插入元素
3.访问左子树
4.访问右子树
5.查找元素
BST的ADT的设计:
ADT BSTtree{
数据对象:D={pi|pi∈整数,i=1,2,... ,n,n∈整数}
数据关系:R
若D = Φ,则R=Φ;
若D≠Φ,则R={H},H是如下二元关系:
在D中存在唯一的称为根的数据元素root,它在关系H下无前驱;
若D-{root}≠Φ,则存在D-{root}={Dl,Dr},且Dl∩Dr=Φ,xl<root<xr;
若Dl≠Φ,则Dl中存在惟一的元素x1,<root,x1>∈H,且存在Dl上的关系Hl∈H; 若Dr≠Φ,则Dr中存在惟一的元素xr,<root,xr>∈H,且存在Dr上的关系Hr∈H;H={<root,x1>,<root,xr>,H1,Hr};
(D1,{H1})是一棵符合本定义的二叉树,称为根的左子树; (Dr,{Hr})是一棵符合本定义的二叉树,称为根的右子树。
基本操作:
BSTNode* getroot();//操作功能:获得根节点,结果返回根节点
void inserthelp(BSTNode*, int &e );
//操作功能:插入值为e的结点
void insert(E& e);//调用inserthelp函数,实现数据封装
bool findhelp(BSTNode* ,int e,int &count=0);
//操作功能:查找值为e的结点
//若e存在,则返回查找成功,否则返回查找失败,count为比较的次数。
bool find( int e);
//调用findhelp函数,实现数据封装
class BSTNode{
private:
int data; //二叉树节点存储的值
BSTNode*lc;
BSTNode*rc; //左右孩子指针
public:
BSTNode() ; //初始化节点
BSTNode(int e,BSTNode* l=NULL,BSTNode* r=NULL) ;
E getData();
BSTNode* left();
BSTNode* right();
void setLeft(BSTNode* b);
void setRight(BSTNode* b);
};
2. BST Tree ADT
int count=0;//记录关键字的比较次数
class BST:public BSTNode{
private:
BSTNode*root; // 树的根节点
int nodeCount; //结点个数
public:
BST() ; //初始化一棵空树
BSTNode* getRoot() ;//获得根结点
BSTNode* inserthelp(BSTNode*, const E&);
void insert(E& e);//插入
bool findhelp(BSTNode* ,int&, int & );
bool find( int& e);//查找
BST的基本操作-find
问题描述:
对于给定的一棵BST和待查元素e,count作为计数器记录查找过程中比较的次数,若BST中存在某个结点的元素值等于e,结果返回计数器的值。否则则查找失败,结果返回计数器的值。
算法思想:
1.定义一个指向BST树节点的临时指针q,并将count重设为0;
2.从BST根节点开始,将其地址赋给q;
3.若q不等于空指针,取q指针所指向的值与待查找的值e进行比较,count+1;
4.若q指针所指向的值等于e,则查找成功,结束查找;
5.若q指针所指向的值小于e,则将q结点左孩子的指针赋给q;
6.若q指针所指向的值大于e,则将q结点右孩子的指针赋给q;
7.重复第三步,直至q等于空结点,则查找不成功,结束查找。
算法描述:
算法分析:该查找算法取决于结点被找到的深度,最佳情况是该结点在根节点被找到,时间复杂度是O(1);最差情况等于树的深度。对于有n个结点的二叉树,如果二叉树是平衡的,则最差时间复杂度是O(logn);如果该树是斜树,则最差时间复杂度是O(n)。
2.算法的基本思想
(1)先用二叉链表实现二叉查找树,将一组数据的值储存到结点中;
(2)通过自定义的查找的基本操作对元素进行查找,若值未找到,则比较的次数为树的深度。
3.程序的流程
建树模块:输入n个整数,并根据BST特性建一棵BST树;
输出模块:调用BST树的查找函数,判断是否能找到以及用计数器进行记录比较次数; 以find函数的结果作为输出的内容。
三、详细设计
1.物理数据类型
物理数据类型:每个结点都有一个value,为整形int;还有两个指针。
物理数据结构:为了方便BST树进行插入和查找,所以选用二叉链表。
BSTNode* getroot();//操作功能:获得根节点,结果返回根节点
{return root;}
BSTNode* inserthelp(BSTNode*root, int &val );
{//操作功能:插入值为val的结点
{
if(root==NULL)
return new BSTNode(val,NULL,NULL);
if(val<root->getdata())
root->setLeft(inserthelp(root->left(),val));
else
root->setRight(inserthelp(root->right(),val));
return root;
}
void insert(int e){ root=inserthelp(root, e );}//插入值为e的元素
bool find(int e)
{
return findhelp(root,e);
}
2.输入和输出的格式
输入格式:第一行输入整数个数n;
第二行包含n个整数a1, a2, …, an,用空格分隔;
接下来的每一行都有一个整数b;
输出格式:若该元素在树中,则输出查找成功,并输出比较次数;否则输出查找不成功与 比较次数。
3.算法的具体步骤
建树模块:
void creat(BSTtree& tree,int n){
//n为输入的整数个数 int i, n,val; cin>>n;
for(i=0;i<n;i++){
//输入这组整数 cin>>val;
}
for(i=0;i<n;i++){
//将整数依次插入BST中 tree.insert(A[i]);
}
}
输出模块:
void print(BSTtree T,int e){
//调用基本函数find if(T.find(e))
//如果函数返回值不为0 cout<<"查找成功 "<<” ”<< count<<endl;
则可找到e,返回比较的次数 else
//否则返回0 cout<<"查找不成功 "
比较次数为该BST树的深度 <<” ”<<count<<endl;
}
4.算法的时空分析
建树模块:利用BST的基本操作中的插入函数,最差情况下其时间按复杂度是O(logn);
输出模块:利用BST的基本操作中的查找函数,最差情况下其时间按复杂度是O(logn);
四、调试分析
1.调试方案设计
调试目的:查看BST树基本操作insert函数与find函数内部语句执行顺序
样例:n=4,这组数为1,2,3,4,依次查找1,2,3,4,5
方案:在函数里面设置一个断点,添加查看,然后开始编译调试,按照提示一步一步操作,在断点处,点击下一步下一步,并在函数里面,添加查看指针所位置的值的变化。
调试步骤:
1.添加端点
2.添加查看
3.编译调试
调试过程和结果,及分析
调试过程:
1)先输入整数个数与该组整数
2)语句进入插入函数,建立新结点,此时BSTnode *&t并没有报错
3)开始插入下一个整数,发现根节点依然为空,然后发现不管插入哪个数据,都是执 行的if(t==NULL)里面的语句。
4)接着开始进行查找,输入要查找的值。
5)发现每次都是直接返回false,也就是根节点一直为空。
实验结果:无论查找什么数据都是查找失败,比较0次,知道是insert函数出了问题。
分析:根据以上调试,可得知是insert函数出了问题,root作为实参,函数执行完树并 没有发生改变。
解决方案:让insert函数每次都返回参数BSTnode *t,然后令root=insert(t,e)。
五、测试结果
样例一:
理由:该组数据对象构成一棵斜树,3是第二个元素,比较2次;100不能被找到,会 遍历该整棵树,比较4次。
样例二:
理由:4是根节点左子树结点,因此要比较2次;10是根节点右子树结点,因此比较2 次;2不能被找到,2小于8,所以依次与8,4,3比较,共比较3次。
样例三:
理由:该树为完全二叉树,25在尾部,共比较3次。
样例四:
理由:该树仅有根节点,所以都只会比较一次。
样例五:
理由:查找41需要依次与1,6,7,78,41比较,共比较5次;查找85需要依次查找 1,6,7,78共4次。
六、实验日志
2018-11-10:观看有关BST的视频,复习其特性与ADT的构建。
2018-11-11:撰写实验四预习报告。
2018-11-12:检查并修改实验报告,修改问题描述,将其改的更实用化。
在演算本上举例的时候,我发现查找的基本操作实现有误,原来的思路是:find函数最终返回一个整数,可找到的时候就返回比较的次数,找不到的时候就返回0。在主函数中,如果find返回0,深度就是其比较次数,但是最后我发现对于一些例子这是错误的。然后我将find函数改成了bool函数,里面加了一个参数count,并引用作为计数器。
2018-11-13:代码实现BST树与结点的ADT,对个别基本操作进行数据封装。
2018-11-14:代码实现主函数并运行。
第一次运行结果无论如何都是查找失败,比较次数是0,检查代码的时候发现insert函数写的不对,t->setleft(insert(t->left(),e))是正确的语句,我写成了insert(t->left(),e),反思了下,是自己对函数调用的理解不深。
而且在这个函数里我的参数写成了*&root,原意是在函数中就更改树的结构,所以我在公有成员中的函数insert里没有令root=insert(root,e),调试中发现root一直为空。 比较气人的是,dev里我对.h文件进行更改然后运行时,其实运行的一直都是相当于没更改前的代码,除非我在主函数中出现了一个大错误,然后我重新更改运行才是对的,这就导致我写成*&编译器也没有报错,不管我怎么修改代码结果也一直是错的,所以当天什么作业都没有写成。然后还想起上次写链表的时候也是结果不对,然后百度、一点点检查代码搞了白天一天加一晚上也不行,没想到三天过后我重新打开运行竟然就对了。
2018-11-15:整理完善最终实验报告。
附代码:
BSTnode.h
**BSTnode.h**
#ifndef _BSTNODE_H
#define _BSTNODE_H
#include <iostream>
class BSTnode
{
private:
int date;
BSTnode *lc;
BSTnode *rc;
public:
BSTnode()//构造函数
{
lc=rc=NULL;
}
BSTnode(int e,BSTnode *l=NULL, BSTnode *r=NULL)//含参构造函数
{
date = e;
lc = l;
rc = l;
}
int getdate()
{
return date;
}
BSTnode* left()
{
return lc;
}
BSTnode* right()
{
return rc;
}
void setleft(BSTnode *l)
{
lc = l;
}
void setright(BSTnode *r)
{
rc = r;
}
};
#endif
BSTtree.h
#include "BSTnode.h"
#ifndef _BSTTREE_H
#define _BSTTREE_H
#include <iostream>
class BSTtree
{
private:
BSTnode *root; //根节点
BSTnode* insert(BSTnode *t, int e)//插入值为e的结点
{
if (t == NULL)
{
t = new BSTnode(e, NULL, NULL);
return t;
}
if (e < t->getdate())
{
t->setleft(insert(t->left(),e));
}
else if (e > t->getdate())
{
t->setright(insert(t->right(),e));
}
return t;
}
bool find(BSTnode *root, int e, int &count)
{
count=0;
BSTnode *q = root;
while (q)
{
count++;
if (e == q->getdate()) return true;
else if (e < q->getdate())
q = q -> left ();
else if (e > q->getdate())
q = q -> right ();
}
return false;
}
void clear(BSTnode* t)
{
if (t == NULL) return;
clear(t->left());
clear(t->right());
delete t;
}
public:
BSTtree()
{
root = NULL;
}
BSTnode* getroot()
{
return root;
}
void clear()
{
clear(root);
}
void insert(int e)
{
root = insert(root,e);
}
bool find(int e,int &count)
{
return find(root,e,count);
}
};
#endif
main.cpp
#include "BSTnode.h"
#include "BSTtree.h"
#include <iostream>
using namespace std;
int main()
{
BSTtree T;
int n, a, count=0;
cout << "请输入该组整数的个数:";
cin >> n;
cout << "请输入该组整数:";
for (int i=0; i<n; i++)
{
cin >> a;
T.insert(a);
}
int f;
cout << "请输入要查找的值:" ;
while (cin >> f)
{
if (T.find(f,count))
{
cout << "查找成功,共比较:" << count << "次" << endl;
}
else
{
cout << "查找失败,共比较:" << count << "次" << endl;
}
cout << "请输入要查找的值:" ;
}
T.clear();
return 0;
}