DS博客作业05--查找
0.PTA得分截图
1.本周学习总结(0-4分)
1.1 总结查找内容
一、静态查找
1.线性表的查找方法:顺序查找、二分查找、分块查找
2.查找算法的评价指标
关键字的平均比较次数,也称平均搜索长度ASL(Average Search Length)
3.算法简介
①顺序查找
思路:
(1)从表的一端开始,顺序扫描线性表,依次将扫描到的关键字和给定值k相比较
(2)若当前扫描到的关键字与k相等,则查找成功;
(3)若扫描结束后,仍未找到关键字等于k的记录,则查找失败。
代码展示:
int SeqSearch(SeqList R,int n,KeyType k)
{ int i=0;
while (i<n && R[i].key!=k) //从表头往后找
i++;
if (i>=n) //未找到返回0
return 0;
else
return i+1;//找到返回逻辑序号i+1
}
ASL(succ)=(n+1)/2
ASL(unsucc)=n
②二分查找
思路:
二分查找也称为折半查找,要求线性表中的节点必须己按关键字值的递增或递减顺序排列。
代码展示:
int BinSearch(SeqList R,int n,KeyType k)
{ int low=0,high=n-1,mid;
while (low<=high) //当前区间存在元素时循环
{ mid=(low+high)/2;
if (R[mid].key==k)//查找成功
return mid+1;
if (k<R[mid].key)
high=mid-1;
else
low=mid+1;
}
return 0;
}
ASL(bn)=log2(n+1)-1
ASL(sq)=(n+1)/2
③分块查找
思路:
(1)将数据表R[0..n-1]均分为b块;
(2)表是“分块有序”;
(3)抽取各块中的最大关键字及其起始位置构成一个索引表IDX[0..b-1],即IDX[i](0≤i≤b-1)中存放着第i块的最大关键字及该块在表R中的起始位置。
图像展示:
二、二叉搜索树
1.定义
二叉排序(查找)树:或者是一棵空树;或者是具有如下特性的二叉树。
-
若它的左子树不空,则左子树上所有结点的值均小于根结点的值;
-
若它的右子树不空,则右子树上所有结点的值均大于根结点的值;
-
它的左、右子树也都分别是二叉排序树。
2.基本运算
结点结构体定义
typedef struct node
{ KeyType key; //关键字项
InfoType data; //其他数据域
struct node *lchild,*rchild; //左右孩子指针
} BSTNode,*BSTree;
查找
①递归查找
BSTNode *SearchBST(BSTNode *bt,KeyType k)
{ if (bt==NULL || bt->key==k)
return bt;
if (k<bt->key) return SearchBST(bt->lchild,k);
else return SearchBST(bt->rchild,k);
}
②非递归查找
BSTNode *SearchBST1(BSTNode *bt,KeyType k)
{ while (bt!=NULL)
{
if (k==bt->key)
return bt;
else if (k<bt->key)
bt=bt->lchild; //在左子树中迭代查找
else
bt=bt->rchild; //在左子树中迭代查找
}
return NULL; //没有找到返回NULL
}
生成
BSTNode *CreatBST(KeyType A[],int n) //返回树根指针
{ BSTNode *bt=NULL; //初始时bt为空树
int i=0;
while (i<n)
{
InsertBST(bt,A[i]); //将A[i]插入二叉排序树T中
i++;
}
return bt; //返回建立的二叉排序树的根指针
}
插入
int InsertBST(BSTree &p,KeyType k)
{ if (p==NULL) //原树为空 {
p=new BSTNode;
p->key=k;p->lchild=p->rchild=NULL;
return 1; }
else if (k==p->key) //相同关键字的节点0
return 0;
else if (k<p->key)
return InsertBST(p->lchild,k); //插入到左子树
else
return InsertBST(p->rchild,k); //插入到右子树
}
删除
int DeleteBST(BSTree &bt,KeyType k)
{ if (bt==NULL) return 0; //空树删除失败
else
{ if (k<bt->key) return DeleteBST(bt->lchild,k);
//递归在左子树中删除为k的节点
else if (k>bt->key) return DeleteBST(bt->rchild,k);
//递归在右子树中删除为k的节点
else
{ Delete(bt); //删除*bt节点
return 1;
}
}
}
void Delete(BSTreee &p) //从二叉排序树中删除*p节点
{ BSTNode *q;
if (p->rchild==NULL) //*p节点没有右子树的情况
{
q=p; p=p->lchild;delete q;
}
else if (p->lchild==NULL) //*p节点没有左子树
{
q=p; p=p->rchild;delete q;
}
else Delete1(p,p->lchild);
//*p节点既有左子树又有右子树的情况
}
//即有左子树又有右子树的删除算法
void Delete1(BSTNode *p,BSTNode *&r)
//被删节点:p,p的左子树节点:r
{ BSTNode *q;
if (r->rchild!=NULL)
Delete1(p,r->rchild); //递归找最右下节点
else //找到了最右下节点*r
{ //将*r的关键字值赋给*p
p->key=r->key;
q=r; r=r->lchild;delete q;
}
}
三、平衡二叉树
1.定义
-
左、右子树是平衡二叉树;
-
所有结点的左、右子树深度之差(平衡因子)的绝对值≤ 1
-
最坏情况下的时间也均为O(log2n).
2.结构体定义
typedef struct node //记录类型
{ KeyType key; //关键字项
int bf; //增加的平衡因子
InfoType data; //其他数据域
struct node *lchild,*rchild;//左右孩子指针
} BSTNode;
3.平衡调整
如果在一棵AVL树中插入一个新结点,就有可能造成失衡,此时必须重新调整树的结构,使之恢复平衡。我们称调整平衡过程为平衡旋转。
①LL平衡旋转
思路:
若在A的左子树的左子树上插入结点,使A失衡,平衡因子2,需要进行一次顺时针旋转。
1)A的左孩子B右上旋转作为A的根节点
2)A节点右下旋转称为B的右孩子
3)B原右子树称为A左子树
图形理解:
②RR平衡旋转
思路:
若在A的右子树的右子树上插入结点,使A的平衡因子从-1增加至-2,需要进行一次逆时针旋转
1)A的右孩子B左上旋转作为A的根节点
2)A节点左下旋转称为B的左孩子
3)B原左子树称为A右子树
图形理解:
③LR平衡旋转
思路:
若在A的左子树的右子树上插入结点,使A的平衡因子从1增加至2,(以插入的结点C为旋转轴),先C进行逆时针旋转,A再顺时针旋转。
1)C向上旋转到A的位置,A作为C右孩子
2)C原左孩子作为B的右孩子
3)C原右孩子作为A的左孩子
保持二叉排序树有序
图像理解:
④RL平衡旋转
思路:
若在A的右子树的左子树上插入结点,使A的平衡因子从-1增加至-2,(以插入的结点C为旋转轴),先进行顺时针旋转,再逆时针旋转。
1)C向上旋转到A的位置,A作为C左孩子
2)C原左孩子作为A的右孩子
3)C原右孩子作为B的左孩子
保持二叉排序树有序
图像理解:
总结:
多个失衡点,从最下面失衡点开始调整
了解是哪种失衡,谁是失衡点
LL和RR是选择失衡点的L或R孩子旋转。LR和RL选择失衡点的相应的LR或RL孙子旋转。
每次调整后,检查是否二叉排序树。还有失衡点否。
4.平衡二叉树的查找
构造一系列的平衡二叉树T1,T2,T3,…,其中,Th表示高度为h且结点数尽可能少的平衡二叉树,下图所示的T1,T2,T3、T4和Th。
设N(h)为Th的结点数,从图中可以看出有下列关系成立:
N(1)=1,N(2)=2,N(h)=N(h-1)+N(h-2)+1
含有n个结点的平衡二叉树的平均查找长度为O(log2n)。
n个节点AVL树的查找性能分析
-
平衡二叉树高度h和n关系:h≈log2(N(h)+1)
-
平衡二叉树上最好最坏进行查找关键字的比较次数
- 不会超过平衡二叉树的深度O(log2n)
-
最坏的情况下,普通二叉排序树查找长度为O(n)
5.平衡二叉树删除节点过程
思路:
在平衡二叉树上删除节点x的过程如下:
1)采用二叉排序树的删除方法找到节点x并删除。
2)删除节点后,会使得x祖先节点失衡,沿根节点到被删除节点的路线之逆逐层向上查找,进行相应的调整。
3)如果调整之后对应的子树高度降低,这个过程还应继续进行,直到根节点为止。
四、B树
1.简介
B-树和B+树:一个节点可放多个关键字,降低树的高度。可放外存,适合大数据量查找。如数据库中数据。
2.B-树
定义:
一棵m阶B-树或者是一棵空树,或者是满足下列要求的m叉树:
每个节点至多m个孩子节点(至多有m-1个关键字)
除根节点外,其他节点至少有[m/2]个孩子节点(即至少有[m/2]-1个关键字)
若根节点不是叶子节点,根节点至少两个孩子节点
节点结构:
-
n为关键字个数,n+1孩子指针。
-
结点中按关键字大小顺序排列,ki<ki+1;
-
pi为该节点的孩子指针,满足
-
p0 指向节点关键字<k0
-
pi指向节点关键字大于等于[ki,ki+1]
-
pn指向节点关键字>kn。
-
m阶B-树结点特点:
-
非根结点:孩子个数
- 最小: [m/2] 最大:m
-
非根结点:关键字个数:
- 最小: [m/2]-1 最大:m-1
-
根节点至少2个孩子:2--m
B-树特点:
-
B-树是所有结点的平衡因子均等于0的多路查找树。所有外部结点都在同一层上。
-
在计算B-树的高度时,需要计入最底层的外部结点
-
外部结点就是失败结点,指向它的指针为空,不含有任何信息,是虚设的。一棵B树中总有n个关键字,则外部结点个数为n+1。
基本运算:
①结构体定义
#define MAXM 10 //定义B-树的最大的阶数
typedef int KeyType; //KeyType为关键字类型
typedef struct node //B-树节点类型定义
{ int keynum; //节点当前拥有的关键字的个数
KeyType key[MAXM]; //[1..keynum]存放关键字,[0]不用
struct node *parent; //双亲节点指针
struct node *ptr[MAXM];//孩子节点指针数组[0..keynum]
} BTNode;
②查找结果返回类型
typedef struct {
BTNode *pt; // 指向找到的结点的指针
int i; // 1..m-1,在结点中的关键字序号
int tag; // 标志查找成功(=1)或失败(=0)
} Result; // 在B树的查找结果类型
③查找
在一棵B-树上顺序查找关键字为k的方法为:
将k与根节点中的key[i]进行比较:
(1)若k=key[i],则查找成功;
(2)若k<key[1]
则沿着指针ptr[0]所指的子树继续查找;
(3)若key[i]<k<key[i+1]
则沿着指针ptr[i]所指的子树继续查找;
(4)若k>key[n]
则沿着指针ptr[n]所指的子树继续查找。
④插入
在查找不成功之后,需进行插入。关键字插入的位置必定在叶子结点层,有下列几种情况:
该结点的关键字个数n<m-1,不修改指针;
该结点的关键字个数 n=m-1,则需进行“结点分裂”
分裂过程
1.如果没有双亲结点,新建一个双亲结点,树的高度增加一层。
2.如果有双亲结点,将ki插入到双亲结点中。
⑤删除
B-树的删除:和插入的考虑相反,
结点中关键字的个数>[m/2]-1,直接删除
结点中关键字的个数=[m/2]-1
要从其左(或右)兄弟结点“借调”关键字
若其左和右兄弟结点均无关键字可借(结点中只有最少量的关键字),则必须进行结点的“合并”。
删除关键字k分两种情况:
<1>在非叶子结点层上删除关键字ki
-
从pi子树节点借调最大或最小关键字key代替ki。
-
pi子树中删除key
-
若子树节点关键字个数< [m/2]-1,重复步骤1
-
若删除关键字为叶子结点层,按叶子结点删除操作法
<2>在叶子结点层上删除关键字ki
-
假如b结点的关键字个数大于Min,说明删去该关键字后该结点仍满足B树的定义,则可直接删去该关键字。
-
假如b结点的关键字个数等于Min,说明删去关键字后该结点将不满足B树的定义。若可以从兄弟结点借。
-
兄弟结点最小关键字上移双亲结点
-
双亲结点大于删除关键字的关键字下移删除结点
-
-
b结点的关键字个数等Min,兄弟节点关键字个数也等于Min
-
删除关键字
-
兄弟节点及删除关键字节点、双亲结点中分割二者关键字合并一个新叶子结点
-
若双亲结点关键字个数<=Min,重复2
-
应用:
-
B-树常被用于对检索时间要求苛刻的场合,例如:
-
B-树索引是数据库中存取和查找文件(称为记录或键值)的一种方法。
-
硬盘中的结点也是B-树结构的。B-树利用多个分支(称为子树)的结点,减少获取记录时所经历的结点数,从而达到节省存取时间的目的。
-
3.B+树
定义:索引文件组织中,经常使用B-树的变形B+树。 B+树是大型索引文件的标准组织方式。
一棵m阶B+树满足下列条件:
1.每个分支节点至多有m棵子树。
2.根节点或者没有子树,或者至少有两棵子树
3.除根节点,其他每个分支节点至少有m/2棵子树
4.有n棵子树的节点有n个关键字。
5.所有叶子节点包含全部关键字及指向相应记录的指针
* 叶子节点按关键字大小顺序链接
* 叶子节点是直接指向数据文件中的记录。
6.所有分支节点(可看成是分块索引的索引表)
* 包含子节点最大关键字及指向子节点的指针。
B+树的查找:
-
直接从最小关键字开始进行顺序查找所有叶节点链接成的线性链表。
-
从B+树的根节点出发一直找到叶节点为止。
4.m阶的B+树和m阶的B-树的差异
-
非根结点关键字个数n不同、n取值范围不同
-
B+树中:一个节点n个孩子则对应n个关键字
取值范围:[m/2]≤n≤m,根节点是1≤n≤m;
-
B-树中:一个节点n个孩子则对应n-1个关键字
取值范围: [m/2]-1≤n≤m-1 ,根节点 1≤n≤m-1
*根节点孩子至少为2.
-
-
叶子结点不一样
-
B+树所有叶子节点包含了全部关键字
-
B-树叶子节点包含的关键字与其他节点包含的关键字是不重复的。
-
-
B+树中所有非叶子节点仅起到索引的作用,而在B-树中,每个关键字对应一个记录的存储地址
-
通常在B+树上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,所有叶子节点链接成一个不定长的线性链表。
五、哈希表
1.基本定义
哈希表(Hash Table)又称散列表,是除顺序表存储结构、链接表存储结构和索引表存储结构之外的又一种存储线性表的存储结构。
哈希函数h(key):把关键字为ki的对象存放在相应的哈希地址中
哈希表:存储数据记录的长度为m(m≥n)的连续内存单元
哈希冲突:对于两个关键字分别为ki和kj(i≠j)的记录,有ki≠kj,但h(ki)=h(kj)。把这种现象叫做哈希冲突(同义词冲突)。
哈希表设计:哈希表设计主要需要解决哈希冲突。实际中哈希冲突是难以避免的,主要与3个因素有关:
哈希表长度:
-
与装填因子有关。
-
装填因子α=存储的记录个数/哈希表的大小=n/m
-
α越小,冲突可能性就越小; α越大(最大可取1),冲突的可能性就越大。控制在0.6~0.9的范围内
-
-
与所采用的哈希函数有关。
-
与解决冲突方法有关。
2.哈希函数构造方法
①直接定址法
直接定址法是以关键字k本身或关键字加上某个数值常量c作为哈希地址的方法。
直接定址法的哈希函数h(k)为:h(k)=k+c
优点:计算简单,并且不可能有冲突发生
缺点:关键字分布不连续将造成内存单元的大量浪费
②除留余数法
除留余数法是用关键字k除以某个不大于哈希表长度m的数p所得的余数作为哈希地址的方法。
哈希函数h(k)为:h(k)=k mod p (mod为求余运算,p≤m)
p最好是质数(素数)。
③数字分析法
图像理解:
适合于所有关键字值都已知的情况,并需要对关键字中每一位的取值分布情况进行分析。
3.哈希冲突解决方法
<1>开放定址法
开放定址法:冲突时找一个新的空闲的哈希地址。
①线性探查法
线性探查法的数学递推描述公式为:
d0=h(k)
di=(di-1+1) mod m (1≤i≤m-1)
非同义词冲突:哈希函数值不相同的两个记录争夺同一个后继哈希地址->堆积(或聚集)现象。
线性探查法容易出现堆积
②平方探查法
平方探查法的数学描述公式为:
d0=h(k)
di=(d0± i2) mod m (1≤i≤m-1)
查找的位置依次为:d0、 d0 +1、 d0 -1 、 d0 +4、 d0 -4、...
平方探查法是一种较好的处理冲突的方法,可以避免出现堆积现象。它的缺点是不能探查到哈希表上的所有单元,但至少能探查到一半单元。
分析查找哈希表时的时间性能ASL
例子
ASL成功=(18+21+4*1)/10=1.4
步骤
假设查找m对应哈希地址为adr,查找h(adr)
查找不成功2种可能:
1.h(adr)==NULL,探测一次
2.h(adr)≠NULL,但是h(adr)!=key,继续探测adr++,直到 h(adr)==NULL
1)对于下标0:一个关键字不在有效关键字集合中,但其哈希函数值为0,采用线性探查法找到空位置,需要2次关键字比较
2)对于下标2:一个关键字不在有效关键字集合中,但其哈希函数值为2,采用线性探查法找到空位置,需要10次关键字比较
ASL不成功=(2+1+10+9+8+7+6+5+4+3+2+1+3)/13=4.692
基本运算
①结构体定义
#define MaxSize 100
#define NULLKEY -1
#define DELKEY -2
typedef char * InfoType ;
typedef struct{
int key;// 关键字域
InfoType data;//其他数据域
int count; //探查次数
}HashTable[MaxSize];
②插入及建表
int InsertHT(HashTable ha,int p,int k,int &n){
int adr,i;
adr=k % p;
if(adr==NULLKEY || adr==DELKEY) //地址为空,可插入数据
{ ha[adr].key=k;ha[adr].count=1;}
else
{ i=1;
while(ha[adr].key!=NULLKEY && ha[adr].key!=DELKEY)
{
adr=(adr+1) % m;
i++;}//查找插入位置
ha[adr].key=k;ha[adr].count=i; //找到插入位置
}
n++;
}
③查找
int SearchHT(HashTable ha,int p,int k)
{
int i=0,adr;
adr=k % p;
while(ha[adr].key!=NULLKEY && ha[adr].key!=k)
adr=(adr+1) % m;//探查下一个地址
if(ha[adr].key==NULLKEY) return -1;//地址为空,找不到
if(ha[adr].key==k) return adr; //找到关键字k
else return -1;
}
④删除
int DeleteHT(HashTable ha,int p,int k,int &n)
{ int adr;
adr=SearchHT(ha,p,k);
if(adr!=-1){
ha[adr].key=DELKEY;//逻辑删除,该元素贴删除标记
n--;
return 1; }
else return 0;
}
<2>拉链法
拉链法是把所有的同义词用单链表链接起来的方法。
图像表示:
拉链法中成功查找的ASL计算
例子
计算步骤
1)成功找到第1层的结点,均需要1次关键字比较,共9个结点
2)成功找到第2层的结点,均需要2次关键字比较,共2个结点
ASL成功=(19+22)/11=1.182
拉链法不成功查找的ASL计算
计算步骤
1)有1个结点的单链表,不成功查找需要1次关键字比较,共有7个这样的单链表
2)有2个结点的单链表,不成功查找需要2次关键字比较,共有2个这样的单链表
ASL不成功=(17+22)/13=0.846
基本运算
①结构体定义
typedef struct HashNode{
int key;
struct HashNode *next;
}HashNode,* HashTable;
HashTable ht[MAX];
②建链
void CreateHash(HashTable ht[],int n)
{
建n条带头结点的哈希链,初始化链表;
for i=1 to k:
输入数据data
InsertHash(ht,data)
}
③插入
InsertHash(ht,data)
{
计算哈希地址adr=data%P;
查找链ht[adr],存在不插入。
不存在:
data生成数据节点node
头插法插入链ht[adr]
}
④删除
InsertHash(ht,data)
{
计算哈希地址adr=data%P;
若链ht[adr]不为空:
查找链ht[adr],找到删除
}
⑤查找k
d=h(k);
p=ha[d]->next;//第d条链表头结点
while (p!=NULL && p->key!=k)
p=p->next; //在ha[d]的单链表中查找
if (p==NULL)
return 失败标记;
else
return p所指结点;
<3>链地址法和开放地址法ASL比较
链地址法<开放地址法
1.2.谈谈你对查找的认识及学习体会。
本章学习了关于查找的知识点,令我在数据结构中查找这一块的内容学习了不少新方法
从起初的顺序查找到现在的静态查找、二叉树查找,B树和哈希表在查找中的应用
在各种情景下对于链、顺序结构中的查找有了明确的方法
在应对小数据和大数据时都有适用的查找方法去应用
在当今大数据的时代,查找无疑是关键的一类知识点
在本学期之前所学的知识的结合下,新的查找算法得到了很好的理解和应用
2.PTA题目介绍(0--6分)
2.1 7-1 是否完全二叉搜索树 (30分)(2分)
2.1.1 该题的设计思路
题目分析:
- 输入样例:
-
存储方式:
-
输入第一行给出一个不超过20的正整数N。用整型变量int存储。
-
第二行给出N个互不相同的正整数,其间以空格分隔。用整型数组int[]存储。
-
解题思路:
完全二叉树的定义:
若设二叉树的深度为k,除第 k 层外,其它各层 (1~k-1) 的结点数都达到最大个数,第k 层所有的结点都连续集中在最左边,这就是完全二叉树。
方法:对树的结点进行层次遍历,当遍历到空结点即停止,这时候遍历过的结点都为连续的,如果此时记录的遍历结点数=树的所有结点树,即为完全二叉树。
时间复杂度:O(n²)
2.1.2 该题的代码与伪代码
代码截图
伪代码
#include头文件
树结点结构体定义
bool Judge(BSTree &T,int n)//判断是否为完全二叉树
{
if 树为空 返回true
else
利用队列层序遍历记录访问的结点个数
当遇到“空洞”时退出遍历循环
if 遍历记录的结点数等于树的总结点数n
返回true
else
返回false;
}
void Print(BSTree &T,int n)//层序遍历输出结点
{
将根结点放入队列中
while 队列不为空
依次取出队头结点并加入该结点的左右子树结点
end while
循环输出n个结点并利用for循环控制格式
}
void InsertBST(BSTree &T,int e)//向树中插入元素
{
if 初始树为空时
建立新结点并进行赋值和左右子树初始化
else if 键值大的插入左子树
InsertBST(T->Left,e);
else if 键值小的插入右子树
InsertBST(T->Right,e);
}
void CreateBST(BSTree &T,int a[],int n)//创建二叉搜索树
{
树初始化为空
for循环插入n个结点
}
int main()
{
数组存储输入的n个元素
创建二叉搜索树
if 该树为完全二叉树
输出层序遍历的结果
cout<<"YES"<<endl;
else
输出层序遍历的结果
cout<<"NO"<<endl;
}
2.1.3 PTA提交列表
Q1:在往树中插入新的叶子结点时没有对结点的左右子树进行置空操作。
A1:添加对新结点左右子树的初始化操作。
Q2:在判断是否完全二叉树的过程中没有实施遇空结点就停止遍历的算法,导致计数错误。
A2:重新设计while遍历循环,在层序遍历中遇到NULL即退出遍历循环。
2.1.4 本题设计的知识点
1.建立二叉树的基本算法(包括初始化新树和插入结点)
2.在二叉树的层序遍历中结合计数法判断是否完全二叉树的方法
3.层序遍历在树中的应用
2.2 7-4 整型关键字的散列映射 (25分)
2.2.1 该题的设计思路
题目分析:
- 输入样例:
-
存储方式:
-
输入第一行首先给出两个正整数N(≤1000)和P(≥N的最小素数),分别为待插入的关键字总数、以及散列表的长度。用整型变量int存储。
-
第二行给出N个整型关键字。用整型变量int循环存储。
-
解题思路:
哈希表定义:
哈希表是一种根据关键码去寻找值的数据映射结构,该结构通过把关键码映射的位置去寻找存放值的地方。
方法:建立哈希表,在插入关键字的过程中返回对应的逻辑位置并输出。
时间复杂度:O(n²)
2.2.2 该题的代码与伪代码
代码截图
伪代码
#include头文件
哈希表类型结构体定义
函数声明
int main()
{
定义整型变量N,P和哈希表HA
输入N和P的值
调用创建哈希表函数
返回0
}
void CreateHT(HashTable* HA, int N, int P)
{
动态分配哈希数组
初始化哈希表
for (i = 0;i < N;i++)
输入关键字key并调用插入函数计算adr的值
输出每个整型关键字在散列表中的位置
end for
}
int InsertHT(HashTable* HA, KeyType k, int P)
{
除留余数法计算第一趟adr的值
赋times的初始值为1
if 可插入
ha[adr].key = k;
ha[adr].count = times;
else
while 不可插入
if 关键字重复 返回adr;
线性探测法重新计算adr的值
times++;
end while
ha[adr].key = k;
ha[adr].count = times;
返回adr;
}
2.2.3 PTA提交列表
Q1:没有考虑到插入关键字重复时的插入处理,导致重复插入。
A1:遇到重复关键字就返回初始对应关键字的地址。
Q2:忽略了当指定位置为删除字段时亦可插入的情况。
A2:在插入时添加判断条件DELKEY。
2.2.4 本题设计的知识点
1.哈希表的结构体定义、创建方法和初始化操作。
2.哈希表地址运算中解决冲突的基本方法
2.3 7-5(哈希链) 航空公司VIP客户查询 (25分)
2.3.1 该题的设计思路
题目分析:
- 输入样例:
-
存储方式:
-
正整数N(≤10⁵)、K(≤500)、正整数M(≤10⁵)和飞行里程。用整型变量int存储。
-
顾客的身份证信息。用stl库中的string类来存储。
-
解题思路:
哈希链(哈希表-拉链法)定义:
采用数组和链表相结合的办法,将Hash地址相同的记录存储在一张线性表中,而每张表的表头的序号即为计算得到的Hash地址。
方法:建立哈希链,根据顾客ID后五位计算哈希地址,在遍历时不存在此ID则添加,存在则累加里程。查询信息时同取后五位计算哈希地址查找即可。
时间复杂度:O(n²)
2.3.2 该题的代码和伪代码
代码截图
伪代码
#include头文件
哈希链结点结构体定义
函数声明
int main()
{
定义整型变量n,k,m,字符串ID和哈希链HC
动态分配哈希数组HC
输入n和k的值
调用函数创建哈希链
输入m的值
循环查找m个ID并返回其在哈希链中的里程数
返回0
}
void Search(HashChain HC[], string ID)
{
计算ID在哈希链中的地址
在对应地址链中查找对应的ID
返回ID对应的里程数
找不到则输出"No Info"
}
void CreateHashChain(HashChain HC[], int n, int k)
{
初始化哈希链的各结点
for(i = 0;i < n;i++)
调用Insert函数插入飞行记录
end for
}
void Insert(HashChain HC[], int k)
{
输入飞行记录,包括用户ID和里程数
根据VIP规定修改累加前的里程数
计算ID在哈希链中的地址
在对应地址链中进行新结点的插入操作或原有结点的累加操作
}
int GetID(string ID)
{
计算并返回ID的后六位数对应的整型数
}
2.3.3 PTA提交列表
Q1:取ID后3位作为哈希地址导致部分单链过长,运行超时。
A1:取ID后六位取余作为哈希地址,并扩大哈希表长度减少单链的长度。
Q2:在适用C++中的cin和cout时耗时过长导致运行超时。
A2:改用C中的scanf和printf函数。
2.3.4 本题设计的知识点
1.哈希表中拉链法的基本操作。
2.在长字符中取得适当的关键字作为哈希地址的计算值。
3.在哈希表中控制单链的长度,防止耗时过长。
4.C++中的输入输出函数cin和cout比C中的scanf和printf更加耗时,应在不同的场景合理置换。
5.使用scanf函数对string字符串进行输入操作时,需要先使用resize函数重新申请字符空间。