DS博客作业05--查找
0.PTA得分截图
1.本周学习总结(0-5分)
1.1 查找的性能指标
ASL(Average Search Length),即平均查找长度,在查找运算中,由于所费时间在关键字的比较上,所以把平均需要和待查找值比较的关键字次数称为平均查找长度。
其中n为查找表中元素个数,Pi为查找第i个元素的概率,通常假设每个元素查找概率相同,Pi=1/n,Ci是找到第i个元素的比较次数。
1.2 静态查找
顺序查找:
,查找方式为从头扫到尾,找到待查找元素即查找成功,若到尾部没有找到,说明查找失败。所以说,Ci(第i个元素的比较次数)在于这个元素在查找表中的位置,如第0号元素就需要比较一次,第一号元素比较2次......第n号元素要比较n+1次。所以Ci=i;所以
当待查找元素不在查找表中时,也就是扫描整个表都没有找到,即比较了n次,查找失败
二分查找
思路:在有序表中,取中间元素作为比较对象,若给定值与中间元素的关键字相等,则查找成功;若给定值小于中间元素的关键字,则在中间元素的左半区继续查找;若给定值大于中间元素的关键字,则在中间元素的右半区继续查找。不断重复上述查找过程,直到查找成功,或所查找的区域无数据元素,查找失败。
折半查找,要求线性表中的节点必须按关键字值的递增或递减顺序排列
代码实现:
int BinarySearch(SeqList list, KeyType kx)
{
//若找到返回该元素在表中的位置,否则返回0
int mid,low=1, high=list.n; //设置初始区间
while(low<=high)
{ /*当查找区间非空*/
mid=(low+high)/2; //取区间中点
if(kx==list.data[mid].key)
return mid; //查找成功,返回mid
else if (kx<list.data[mid].key)
high=mid-1; // 调整到左半区
else low=mid+1; // 调整到右半区
}
return 0; //查找失败,返回0
}
时间复杂度:O(log2n)
1.3 二叉搜索树
1.3.1 如何构建二叉搜索树(操作)
一组数据为48 38 65 97 13 27 76 49
ASL成功=(1+2x2+2x3+4x2+5x1)/8=3
ASL不成功=(3x2+4x2+3x4+2x5)/9=4
1.比根节点大的数,放到它的右子树中;
2.比根节点小的数,放到它的左子树中;
3.所有后续要放到数字都遵守上述规则;
结合一组数据介绍构建过程,及二叉搜索树的ASL成功和不成功的计算方法。
插入
BiTree Insert(int data, BiTree T)
{
if (!T)//找到插入位置,进行插入
{
T = (BiTree)malloc(sizeof(BiTNode));
T->data = data;
T->lchild = T->rchild = NULL;
}
else if (data > T->data)//进行右子树递归插入
T->rchild = Insert(data, T->rchild);
else if (data < T->data)//进行左子树递归插入
T->lchild = Insert(data, T->lchild);
else//插入失败
printf("元素已经存在,插入失败");
return T;
}
删除
BiTree Delete(int data, BiTree T)
{
Position Tmp;
if (!T)
printf("没有找到待删除元素");
else if (data > T->data)//进行右子树递归删除
T->rchild = Delete(data, T->rchild);
else if(data < T->data)//进行左子树递归删除
T->lchild = Delete(data, T->lchild);
//找到对应节点
else if (T->lchild && T->rchild)//被删除节点有左右两个子节点(需要转换成有一个子节点或没有子节点的情况)
{
Tmp = FindMax(T->lchild);
T->data = Tmp->data;
T->lchild = Delete(T->data, T->lchild);
}
else//被删除节点有一个子节点或没有子节点
{
Tmp = T;
if (!T->rchild)//有左节点或没有子节点
T = T->lchild;
else if (!T->lchild)//有右节点或没有子节点
T = T->rchild;
free(Tmp);
}
return T;
}
1.3.2 如何构建二叉搜索树(代码)
插入
BSTree Insert(BSTree BST, KeyType k)
{
if (!BST){
BSTree p = new BSTNode;
p->lchild= NULL;
p->rchild = NULL;
p->data = k;
return p;
}
else if (k < BST->data){
BST->lchild = Insert(BST->lchild, k);
}
else if (k > BST->data){
BST->rchild= Insert(BST->rchild, k);
}
return BST;
}
删除
int DeleteBST(BSTree &bt,KeyType k)
{
if(bt==NULL)return 0;//空树
else{
if(k<bt->key)return DeleteBST(bt->lchild,k);//在左侧递归寻找删除结点
else if(k>bt->key)return DeleteBST(bt->rchild,k);//在左侧递归寻找删除结点
else{//找到结点进行删除
Delete(bt);
return 1;
}
}
}
//从二叉树排序树中删除结点p
void Delete(BSTree &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);//既有左子树又有右子树
}
//既有左子树又有右子树的删除
void Delete1(BSTNode *p,BSTNode *&r)
{
BSTNode *q;
if(r->rchild!=NULL)Delete1(p,r->rchild);//递归找最右下节点
else{
p->key=r->key;
q=r;
r=r->lchild;
delete q;
}
}
2.分析代码的时间复杂度
最好:O(log2n)
最坏: O(n)
3.为什么要用递归实现插入、删除?递归优势体现在代码哪里?
1.递归的实现明显要比循环简单得多
2.保留父子关系,便于删除和插入顺利找到父亲和孩子
1.4 AVL树
AVL树解决什么问题,其特点是什么?
平衡二叉搜索树,它的特点是在二叉搜索树(BST)的基础上,要求每个节点的左子树和右子树的高度差至多为1。这个要求使AVL的高度h = logn,底数为2,避免了BST可能存在的单链极端情况(h = n)。
AVL树中任何节点的两个子树的高度最大差别为1
结合一组数组,介绍AVL树的4种调整做法。
AVL树的高度和树的总节点数n的关系?
设N(h)为高度为h的AVL树的最小节点数目,则N(h)=N(h-1) + N(h-2) +1; N(0)=0, N(1)=1; 类似斐波那契数列;
插入、查找和删除的性能均为log(n)
介绍基于AVL树结构实现的STL容器map的特点、用法。
(1)map的特点
map是STL的一个关联容器,它提供一对一的hash。
第一个可以称为关键字(key),每个关键字只能在map中出现一次;
第二个可能称为该关键字的值(value);
Map主要用于资料一对一映射(one-to-one)的情况,map內部的实现自建一颗红黑树,这颗树具有对数据自动排序的功能。在map内部所有的数据都是有序的。比如一个班级中,每个学生的学号跟他的姓名就存在著一对一映射的关系。
1.5 B-树和B+树
B-树和AVL树区别,其要解决什么问题?
B-树定义。
也叫B树,即平衡多路查找树,m阶B树表示节点可拥有的最多m个孩子,2-3树是3阶B树,2-3-4树是4阶B树。多叉树可以有效降低树的高度,h=log_m(n),m为log的底数。
B-树的特点:
1.任意非叶子结点最多只有 M 个儿 子, M>2
2.根结点的儿子数为 [2, M]
3.除根结点以外的非叶子结点的儿子数为 [M/2, M]
4.每个结点存放至少 M/2-1 (向上取整)和至多 M-1 个关键字, M> 2
5.非叶子结点的关键字个数 = 指向孩子的指针个数 -1 ;
6.非叶子结点的关键字: K[1], K[2], …, K[M-1] , K[i] < K[i+1]
7.非叶子结点的指针: P[1], P[2], …, P[M] ;其中 P[1] 指向关键字小于 K[1] 的子树, P[M] 指向关键字大于 K[M-1] 的子树,其它 P[i] 指向关键字属于 (K[i-1], K[i]) 的子树
8.所有叶子结点位于同一层
B-树的结构体定义
#define MAXN 10
typedef int KeyTypw;
typedef struct node{
int keynum;//节点当前拥有关键字的个数
KeyType key[MAXM];//存放关键字
struct node *parent;//双亲节点指针
struct node *ptr[MAXM];//孩子节点指针数组
}BTNode;
a)在空树中插入39
b)继续插入22,97和41
根结点此时有4个key
c)继续插入53
插入后超过了最大允许的关键字个数4,所以以key值为41为中心进行分裂,结果如下图所示,分裂后当前结点指针指向父结点,满足B树条件,插入操作结束。当阶数m为偶数时,需要分裂时就不存在排序恰好在中间的key,那么我们选择中间位置的前一个key或中间位置的后一个key为中心进行分裂即可。
d)依次插入13,21,40,同样会造成分裂,结果如下图所示。
https://images2018.cnblogs.com/blog/834468/201804/834468-20180406232714654-717185244.png
e)依次插入30,27, 33 ;36,35,34 ;24,29,结果如下图所示。
f)插入key值为26的记录,插入后的结果如下图所示。
当前结点需要以27为中心分裂,并向父结点进位27,然后当前结点指向父结点,结果如下图所示。
进位后导致当前结点(即根结点)也需要分裂,分裂的结果如下图所示。
分裂后当前结点指向新的根,此时无需调整。
g)最后再依次插入key为17,28,29,31,32的记录,结果如下图所示。
B+树定义,其要解决问题
B+树是B-树的变体,也是一种多路搜索树:
1.其定义基本与B-树同,除了:
2.非叶子结点的子树指针与关键字个数相同;
3.非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间);
4.为所有叶子结点增加一个链指针;
1.6 散列查找。
哈希表的设计主要涉及哪几个内容?
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
构造方法
1.直接取地址法
根据关键字直接加上某个常量作为地址,确定所坐在位置
优点:计算简单,并且不可能有冲突
缺点:关键字分布不是连续的将造成内存单元浪费
2. 除留余数法
用关键字k除以某个不大于哈希表长度的m的数p所得的余数作为哈希表地址,即需要一个哈希函数h(k)= k mod p(p最好为素数)
优点:得到的哈希表是大致连续的,且不会超过原来哈希表的长度,可以节省空间
缺点:余数有可能相同,会产生冲突
3.数字分析法
数字分析法是取数据元素关键字中某些取值较均匀的数字位作为哈希地址的方法。即当关键字的位数很多时,可以通过对关键字的各位进行分析,丢掉分布不均匀的位,作为哈希值。它只适合于所有关键字值已知的情况。通过分析分布情况把关键字取值区间转化为一个较小的关键字取值区间。
哈希表的设计
哈希表的设计:主要是为了解决哈希冲突。
与三个因素有关:
1.哈希表长度
2.采用的哈希函数
结合数据介绍哈希表的构造及ASL成功、不成功的计算
关键字序列:(7、8、30、11、18、9、14)
散列函数:
H(Key) = (key x 3) MOD 7
所以查找成功的计算:
如果查找7,则需要查找1次。
如果查找8,则需要查找1次。
如果查找30,则需要查找1次。
如果查找11,则需要查找1次。
如果查找18,则需要查找3次:第一次查找地址5,第二次查找地址6,第三次查找地址7,查找成功。
如果查找9,则需要查找3次:第一次查找地址6,第二次查找地址7,第三次查找地址8,查找成功。
如果查找地址14,则需要查找2次:第一次查找地址0,第二次查找地址1,查找成功。
所以,ASL成功=(1+2+1+1+1+3+3)/ 7=12/ 7
查找不成功计算
查找地址为0的值所需要的次数为3,
查找地址为1的值所需要的次数为2,
查找地址为2的值所需要的次数为1,
查找地址为3的值所需要的次数为2,
查找地址为4的值所需要的次数为1,
查找地址为5的值所需要的次数为5,
查找地址为6的值所需要的次数为4。
ASL不成功=(3+2+1+2+1+5+4)/ 7=18/ 7
结合数据介绍哈希链的构造及ASL成功、不成功的计算
2.PTA题目介绍(0--5分)
2.1 是否完全二叉搜索树(2分)
2.1.1 伪代码(贴代码,本题0分)
BinTree Insert(BinTree T, int x)//建树函数
if T为空
开辟结点
T->Data = x;
T->Left = T->Right 为 空
else
if x >根结点值
插入到左子树
else if x < 根结点值
插入到右子树
void Output(BinTree T)//输出函数
while (!q.empty())
p = q.front();
q.pop();
if (flag == 0)//控制空格
{
cout << p->Data;
flag = 1;
}
else cout << ' ' << p->Data;
if 左子树不空 输出左子树
if 右子树不空 输出右子树
bool judge(BinTree T)//判断函数
if(根节点不空) 入队根节点;
初始化状态status = true;//如果status==false,则后续所有结点都只能是叶子结点
while(队列不为空)
出队队头结点t;
if(t的左孩子不为空)
if(status == false)
else 标记status = false;
if(t的右孩子不为空)
if(status == false)
t的右孩子入队;
else 标记status = false;
end while
return true;
2.1.2 提交列表
2.1.3 本题知识点
1.完全二叉树的概念:叶子只能出现在最下面的二层、最下层的叶子一定集中在左部的连续位置、倒数第二层若有叶子结点,一定在右部连续位置、如果结点的度为1 ,则该结点只有左孩子。
2.本题大于根结点的值位于左子树,大于的值位于右子树
3.层次数遍历:借助队列来进行层序遍历,每出队一个结点就将其左孩子和右孩子入队,直到队空为止。
2.2 航空公司VIP客户查询(2分)
2.2.1 伪代码(贴代码,本题0分)
for (int i = 0; i < 个数; i++)do
输入身份证 里程数
if (里程数 < 最小里程数)
更改里程数
end if
调用Insert向H插入结点数;
end for
for(int i = 0; i < 查找次数; i++) do
调用查找函数造H中查找数据
找到数据赋值给ptr
若 ptr == NULL do
else
查找成功
end if
end for
2.2.2 提交列表
2.2.3 本题知识点
1.哈希链的构造创建和寻找,建链前需要对哈希链进行初始化和申请空间。
2.cin与cout相比于scanf和printf消耗时间长
2.3 基于词频的文件相似度(1分)
2.3.1 伪代码(贴代码,本题0分)
定义变量类型为<string,int[]>的map容器构建单词索引表
根据单词在单词索引表中构建映射
for i=0 to 文件组数
输入文件编号
for iterator=map.begin() to !map.end()遍历单词
if 单词在两个文件都出现过
修正重复单词数,合计单词数
else if 单词在其中一个文件中出现过
修正合计单词数
2.3.2 提交列表
2.3.3 本题知识点
1.对map函数函数的使用
2.拉链法构造哈希表:这里运用到了拉链法构造哈希表来保存文件内容
3.单词的提取:要从主串中提取子串单词,可以使用string库中的substr