DS博客作业05--查找
0.PTA得分截图
查找题目集总得分,请截图,截图中必须有自己名字。题目至少完成总题数的2/3,否则本次作业最高分5分。没有全部做完扣1分。
1.本周学习总结(0-5分)
1.1 查找的性能指标
ASL成功、不成功,比较次数,移动次数、时间复杂度
ASL,即平均查找长度,在查找运算中,由于所费时间在关键字的比较上,所以把平均需要和待查找值比较的关键字次数称为平均查找长度。
公式:ASL=∑(i=1->n)p(i)*c(i)
-
ASL成功:
找到T中任一记录平均需要的关键字比较次数 -
ASL不成功:
在T中任一记录找不到的关键字比较次数 -
比较次数,移动次数
对于一组数据当它以哈希表,链表,二叉搜索树储存时,关键字的搜索就要通过将关键字与其中的元素进行比较才能找到
哈希表因会有冲突导致关键字存储位置不是哈希函数所对应的位置,此时的比较次数就会增加
链表因会有冲突,导致关键字不是某一条链表的第一个元素,此时要找到关键字任然要比较,即比较次数要增加 -
ASL是衡量查找算法性能好坏的重要指标。一个查找算法的ASL越大,其时间性能越差;反之,一个查找算法的ASL越小,其时间性能越好。
1.2 静态查找
分析静态查找几种算法包括:顺序查找、二分查找的成功ASL和不成功ASL。
一般就只是查询和检索操作
而动态查找就是在发现没有我们要找的数据的时候进行插入这个数据,这样称为动态查找。
1.2.1顺序查找
顺序查找(sequential search)是一种最简单的查找方法。它的基本思路是从表的一端向另一端逐个将元素的关键字和给定值k比较,若相等,则查找成功,给出该元素在查找表中的位置;若整个查找表扫描结束后仍未找到关键字等于k的元素,则查找失败。
int SeqSearch(RecType 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
}
在顺序表R[0..n-1]中查找关键字为k的元素,成功时返回找到的元素的逻辑序号,失败时返回0
ASL成功为
ASL不成功为
1.2.2二分查找
折半查找又称二分查找,是一种效率较高的查找方法。但是,折半查找要求线性表是有序表,即表中的元素按关键字有序。
基本思路是设R[low..high]是当前的查找区间,首先确定该区间的中点位置mid=[(low+ high)/2],然后将待查的k值与R[mid].key比较
(1)若k=R[mid]. key,则查找成功并返回该元素的逻辑序号。
(2)若k<R[mid]. key,则由表的有序性可知R[mid..high]. key均大于k,因此若表中存在关键字等于k的元素,则该元素必定是在位置mid左边的子表R[low..mid-1]中,故斤的查找区间是左子表R[low..mid-1].
(3)若k>R[mid]. key,则关键字为k的元素必在mid的右子表R[mid+1..high]中,即新的查找区间是右子表R[mid+1.high]。下一次查找是针对新的查找区间进行的。
int binarySearch(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;//找到就返回下标
else if(R[mid].key > k)high = mid - 1;
elss low = mid + 1;
}
return 0;//找不到返回0
}
//递归法
int BinarySearch(SeqList R,int low,int hight,KeyType k)
{
int mid;
if(low<=high){//查找的区间存在一个以上的元素
mid=(low+high)/2;//找中间位置
if(R[mid].key==k)return mid+1;//查找成功,返回序号mid+1
if(R[mid].key>k)BinarySearch(R,low,miid-1,k);//在[low…mid-1]区间递归寻找
else BinarySearch(R,mid+1,high,k); //在[mid+1…high]区间递归寻找
}
else return 0;
}
成功ASL
不成功ASL
1.3 二叉搜索树
1.3.1 如何构建二叉搜索树(操作)
在二叉搜索树中:
1.若任意结点的左子树不空,则左子树上所有结点的值均不大于它的根结点的值;
2.若任意结点的右子树不空,则右子树上所有结点的值均不小于它的根结点的值;
3.任意结点的左、右子树也分别为二叉搜索树。
- 结合一组数据介绍构建过程,及二叉搜索树的ASL成功和不成功的计算方法。
ASL成功=(1+2 * 2 + 3 * 3)/6
ASL不成功=(1 * 2 + 6 * 3)/7
ASL成功=(对应层数 * 对应层的结点个数,进行累加)/总的结点个数
ASL不成功=(对应层数 * 对应层的空结点个数,进行累加)/总的空结点个数 - 如何在二叉搜索树做插入、删除。
1.p节点为叶子结点,直接删去该结点,双亲节点相应指针域修改为NULL;
2.p结点只有左/右子树,有其左子树或者右子树代替他,双亲节点相应的指针域修改;
3.p结点既有左子树也有右子树。根据二叉排序树的特点,可以从其左子树中选择关键字最大的结点r(最右结点),用结点r的值代替结点p的值(结点值替换),保存r结点的左孩子,再删去r结点。也可以从右子树中选择关键字最小的结点l(最左结点),用结点l的值代替结点p的值(结点替换),保存l结点的右孩子,再删去结点l。
1.3.2 如何构建二叉搜索树(代码)
1.如何构建、插入、删除及代码。
插入:
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->key0
return 0;
else if(k<p->key)
return InsertBST(p->lchild,k);
else
return InsertBST(p->rchild,k);
}
构建:
BSTNode *Creat(KeyType A[],int n)
{
BSTNode *bt=NULL;
int i=0;
while(i<n)
{
InsertBST(bt,A[i]);
i++;
}
return bt;
}
删除:
int DeleteBST(BSTree &bt,KeyType k)
{
if(bt==NULL)
return 0;
else
{
if(k<p->key)
return DeleteBST(bt->lchild,k);
else if(k>bt->key0
return DeleteBST(bt->rchild,k);
else
{
Delete(bt);
return 1;
}
}
}int DeleteBST(BSTree &bt,KeyType k)
{
if(bt==NULL)
return 0;
else
{
if(k<p->key)
return DeleteBST(bt->lchild,k);
else if(k>bt->key0
return DeleteBST(bt->rchild,k);
else
{
Delete(bt);
return 1;
}
}
}
2.分析代码的时间复杂度
时间复杂度:最好:O(logn),最差:O(n)
3.为什么要用递归实现插入、删除?递归优势体现在代码哪里?
递归可以大量减少代码的量(优势),并且二叉排序树也是一种树,树的许多操作也是基于递归实现的。
1.4 AVL树
- AVL树解决什么问题,其特点是什么?
AVL树是二叉搜索树的优化版,又称平衡二叉搜索树,高度平衡树。当一棵二叉搜索树的结点一直单边插入时,这时候它的查找效率趋近于O(n),非常慢。
特点:
1.左右子树是平衡二叉树
2.所有节点的左右子树深度(高度)之差的绝对值<=1,在平衡二叉树里,用平衡因子来表示左右子树的高度之差,从而控制树的平衡
- 结合一组数组,介绍AVL树的4种调整做法。
RR:
LL:
RL:
LR:
-
AVL树的高度和树的总节点数n的关系?
AVL树结点个数最少满足的条件:O(1)=1,O(2)=2,O(H)=O(H-1)+O(H-2)+1
n个结点的AVL树满足:h=log2(N(h)+1) -
介绍基于AVL树结构实现的STL容器map的特点、用法。
可利用map形成映射进行一对一的匹配,可与树的结构进行联立,第一个可以称为关键字(key),每个关键字只能在map中出现一次,第二个可能称为该关键字的值
插入函数:
方法一insert插入
例:
users.insert(pair<long long, string>(num, code));
users.insert(pair<long long, string>(10, 5))
方法二直接插入
例:
user[10]=code;
user[10]='5'
查找函数:
map<long long, string>users;
map<long long, string>::iterator iter;
iter = users.find(num);
if (iter == users.end())//到达结尾没找到
{
证明没找到;
}
1.5 B-树和B+树
-
B-树和AVL树区别,其要解决什么问题?
AVL树:树都是内存中,适用小数据量。每个节点放在一个关键字,树的高度比较大。
B-树又称为多路平衡查找树,一个节点可以存放多个关键字,降低树的高度。可外存放,适合大数据量查找。 -
B-树定义。结合数据介绍B-树的插入、删除的操作,尤其是节点的合并、分裂的情况
B-树定义:
一棵m阶B-树或者是一棵空树,或者是满足下列要求的m叉树:
(1)每个结点至多有m个子结点;
(2)除根结点和叶结点外,其它每个结点至少有ceil(m/2)个子结点;
(3)根结点至少有两个子结点;(唯一例外的是根结点就是叶子结点)
(4)所有的叶结点在同一层;
(5)有k个子结点的非根结点恰好包含k-1个关键码,关键码按照递增次序进行排列。
注:ceil代表向上取整
B-树的查找
在一棵B-树上顺序查找关键字,将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[i],则沿着指针ptr[i]所指的子树进程查找;
5.查找某个结点,若相应指针为空,落入一个外部结点,表示查找失败;类似判断树和二叉排序树的查找。
B-树的插入
往B-树中插入一个结点,插入位置必定在叶子结点层,但是因为B-树中对关键字个数有限制,所以,插入情况分为以下两种:
1.关键字个数n<m-1,不用调整
2.关键字个数n=m-1,进行分裂;如果分裂完之后,中间关键字进入的结点中关键字个数达到n=m-1,就继续进行分裂
m阶B+树定义
①每个分支至多有m棵子树
②根节点没有子树或者至少有两颗
③除了根节点其他节点至少有m/2向上取整个子树
④有n棵子树的节点有n个关键字
⑤叶子节点包含全部关键字,以及只想相应记录的指针,而叶子节点按关键字大小顺序链接( 每个叶子节点可以看成是一个基本索引块,它的指针指向数据文件中的记录)
⑥所有分支节点(可以看成是索引的索引)中仅仅包含他的各个子节点中最大关键字以及指向子结点的指针
B+树主要解决问题
在索引文件组织中,经常使用B+树,B+树是大型索引文件的标准组织方式。
B-树和B+树的区别
这都是由于B+树和B-具有这不同的存储结构所造成的区别,以一个m阶树为例。
1.关键字的数量不同;B+树中分支结点有m个关键字,其叶子结点也有m个,其关键字只是起到了一个索引的作用,但是B-树虽然也有m个子结点,但是其只拥有m-1个关键字。
2.存储的位置不同;B+树中的数据都存储在叶子结点上,也就是其所有叶子结点的数据组合起来就是完整的数据,但是B-树的数据存储在每一个结点中,并不仅仅存储在叶子结点上。
3.分支结点的构造不同;B+树的分支结点仅仅存储着关键字信息和儿子的指针(这里的指针指的是磁盘块的偏移量),也就是说内部结点仅仅包含着索引信息。
4.查询不同;B树在找到具体的数值以后,则结束,而B+树则需要通过索引找到叶子结点中的数据才结束,也就是说B+树的搜索过程中走了一条从根结点到叶子结点的路径。
1.6 散列查找。
-
哈希表的设计主要涉及哪几个内容?
-
哈希函数的构造方法:
1.直接定址法
哈希函数:h(k)=k+c
优势:计算简单,不可能有冲突发生
缺点:关键字分布不连续,将造成内存单元的大量浪费
2.除留取余数法
用关键字k除以某个不大于哈希表长度m的数p所得的余数作为哈希地址,h(k)=k%p(这里的p最好为不大于m的质数,减少冲突的可能性)
3、数字分析法:分析一组数据,比如一组员工的出生年月日,发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。
4、平方取中法:当无法确定关键字中哪几位分布较均匀时,可以先求出关键字的平方值,然后按需要取平方值的中间几位作为哈希地址。这是因为:平方后中间几位和关键字中每一位都相关,故不同关键字会以较高的概率产生不同的哈希地址。
5、折叠法:将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。数位叠加可以有移位叠加和间界叠加两种方法。移位叠加是将分割后的每一部分的最低位对齐,然后相加;间界叠加是从一端向另一端沿分割界来回折叠,然后对齐相加
6、随机数法:选择一随机函数,取关键字的随机值作为散列地址,即H(key)=random(key)其中random为随机函数,通常用于关键字长度不等的场合。 -
结合数据介绍哈希表的构造及ASL成功、不成功的计算
-
结合数据介绍哈希链的构造及ASL成功、不成功的计算
-
哈希冲突解决
1.开放定址法
在出现哈希冲突时,再哈希表中找到一个新的空闲位置存放元素,例如:要存放关键字k,d=h(k),而地址d已经被其他元素占用了,那么就在d地址附近寻找空闲位置进行填入。开放定址法分为线性探测和平方探测等
2.线性探测
线性探测是从发生冲突的地址d0开始,依次探测d0的下一个地址(当到达哈希表表尾时,下一个探测地址为表首地址0),直到找到一个空闲的位置单元为止,探测序列为:d=(d+i)%m,所以,我们在设置哈希表的长度时一定要大等于元素个数,保证每个数据都能找到一个空闲单元插入 -
哈希表ASL:
AVL成功=总的探测次数和/有效元素个数
AVL不成功=(从0开始到第p个单元格的不成功探测次数和,例如p为7,但建了大于7的单元格,只要计算0到6的单元格不成功探测次数即可)/p -
拉链法
拉链法是把所有的同义词用单链表链接起来的方法,所有哈希地址为i元素对应的结点构成一个单链表,哈希表地址空间为0~m-1,地址为i的单元是一个指向对应单链表的头结点。这种方法中,哈希表的每个单元中存放的不再是元素本身,而是相应同一词单链表的首结点指针。
优点:
处理冲突简单,无堆积现象,非同义词不会发生冲突;
结点空间时动态申请的,空间利用效率高;
在链表中,删除结点操作方便。
2.PTA题目介绍(0--5分)
介绍3题PTA题目
2.1 是否完全二叉搜索树(2分)
本题务必结合完全二叉搜索树经过层次遍历后在队列的特点去设计实现。结合图形介绍。
2.1.1 伪代码(贴代码,本题0分)
将数据插入空的二叉搜索树
if <树为空>
建立新的节点
并对节点赋值为num
令左右孩子均为null
else if <num大于节点数据>
插入左孩子中
else if<num小于节点数据>
插入右孩子中
层次遍历输出结果树的层序遍历结果
判断该树是否为完全二叉树
if <树为空>
不是
令p为最后入队的元素
while(p)
{
p出列
p的左右孩子入列
令p为队头元素
}
当p为空时,弹出
while 队列不为空
{
if<队头元素不为空>
{
不是
}
弹出队列
}
是
}
2.1.2 提交列表
2.1.3 本题知识点
树的层次遍历
队列的相关库函数
2.2 航空公司VIP客户查询(2分)
本题结合哈希链结构设计实现。请务必自己写代码,学习如何建多条链写法。
2.2.1 伪代码(贴代码,本题0分)
void CreateHashChain(HashChain HC[], int n, int k)
初始化哈希链hc
for i=0 to n
Insert(HC, k)插入关键字
end for
void Insert(HashChain HC[], int k)
输入ID和飞行里程
数据处理并计算哈希地址
while 遍历对应的单链表
if 查找到对应的ID then break
else p = p->next
end while
if 没查找到
申请节点并复制
头插法插入关键字
else
累加飞行里程
end for
void Search(HashChain HC[], string ID)
计算哈希地址
while 遍历哈希地址对应的单链表
if 找到 then 输出总飞行里程
else p = p->next
end while
未找到,输出No Info
2.2.2 提交列表
2.2.3 本题知识点
1.练习哈希链的创建和查找,创建就是计算哈希地址,然后插入对应的单链表中;查找也得先计算哈希地址,然后在单链表中查找对应的信息
2.使用cin和cout比起使用scanf和pritf来输入输出,要更加耗时,在时间限制严格的题目中,应该使用scanf和pritf来输入输出
3.创建哈希链的过程中,应该注意不要让单链表的长度过长,否则查找和插入都将耗时过长
2.3 基于词频的文件相似度(1分)
本题设计一个倒排索引表结构实现(参考课件)。单词作为关键字。本题可结合多个stl容器编程实现,如map容器做关键字保存。每个单词对应的文档列表可以结合vector容器、list容器实现。
2.3.1 伪代码(贴代码,本题0分)
for i=1 to N i++
while 读取一行的内容,当内容为#就停止循环
令top=0;
for j=0 to s.length() j++
if 内容不是字母即为分割符
then if 单词长度小于3 则不记录
end if
continue
end if
if 单词长度大于10
then 值记录前10
end if
else 加结束符 单词数+1
top=0
end if
else 是字母,大写该小写
需要队组后一个单词判断
计算相似度
for i=0 to m-1 i++
输入两个单词 x y
map<string,int>::iterator it1,it2;//两个迭代器,类似指针
分子,分母=两个文件所有单词-相同的
用for 循环队两个文件进行遍历
if (it1->first<it2->first)it1++;//it1字母小了, 向后移动
else if it2字母小了, 向后移动
else 一样
同时移动
2.3.2 提交列表
2.3.3 本题知识点
使用了map函数
string s 字符串型变量
利用getchar()吸收回车
map<string,int>::iterator it1,it2;//两个迭代器,类似指针