DS博客作业05--查找

| 这个作业属于哪个班级 | 数据结构--网络2011/2012 |
| ---- | ---- | ---- |
| 这个作业的地址 | DS博客作业05--查找 |
| 这个作业的目标 | 学习树结构设计及运算操作 |
| 姓名 | 邓宏 |

0.PTA得分截图


顺序查找是按照序列原有顺序对数组进行遍历比较查询的基本查找算法。ASL:
关键字的平均比较次数,也称平均搜索长度
ASL成功:
找到T中任一记录平均需要的关键字比较次数
ASL不成功:
在T中任一记录找不到的关键字比较次数
ASL即平均查找长度,在查找运算中,由于所费时间在关键字的比较上,所以把平均需要和待查找值比较的关键字次数称为平均查找长度。
ASL的计算与移动次数和比较次数有关,决定了该查找算法的时间复杂度

1.2 静态查找

静态查找是“真正的查找”。因为在静态查找过程中仅仅是执行“查找”的操作,即查看某特定的关键字是否在表中(判断性查找);检索某特定关键字数据元素的各种属性(检索性查找)。这两种操作都只是获取已经存在的一个表中的数据信息,不对表的数据元素和结构进行任何改变。
顺序查找:
顺序查找是按照序列原有顺序对数组进行遍历比较查询的基本查找算法。
结构体:

typedef int KeyType;
typedef char* InfoType;
typedef struct node
{
	KeyType key;			
	InfoType data;			
} Node[MaxSize];		

代码:

int Search(Node B, int n, KeyType k)//在表中查找关键字k,找不到返回-1,找到返回查找地址。n是表中元素个数
{
	int i = 0;
	while (i < n && B[i].key != k)/*按下标顺序找,直到找到k*/
	{
		i++;/*不是k则i加一*/
	}
	if (i >= n)
	{
		return -1;/*找不到则返回-1*/
	}
	else
	{
		return i+1;/*找到的时候不进入循环,所以i需要再增1*/
	}
}

时间复杂度为:O(n)
缺点:查找效率较低,特别是当待查找集合中元素较多时,不推荐使用顺序查找。
优点:算法简单而且使用面广。
二分查找:
二分查找也称折半查找,它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。
代码:

int BinSearch1(SeqList R, int low, int high, KeyType k)
{
	int mid;
	if (low <= high)		 //查找区间存在一个及以上元素
	{
		mid = (low + high) / 2;  //求中间位置
		if (R[mid].key == k) //查找成功返回其逻辑序号mid+1
			return mid + 1;
		if (R[mid].key > k)  //在R[low..mid-1]中递归查找
			BinSearch1(R, low, mid - 1, k);
		else		//在R[mid+1..high]中递归查找
			BinSearch1(R, mid + 1, high, k);
	}
        else
          return 0;
}

时间复杂度为:O(logn)
优点:折半查找的时间复杂度为O(logn),远远好于顺序查找的O(n)。
缺点:虽然二分查找的效率高,但是要将表按关键字排序。而排序本身是一种很费时的运算。既使采用高效率的排序方法也要花费O(nlgn)的时间。

1.3 二叉搜索树

1.3.1 如何构建二叉搜索树(操作)

以当前查找区域的中间位置上的记录作为根节点,左子表作为根节点的左子树,右子表作为根节点的右子树,建立一棵二分查找的判定树。
若二叉排序树为空,则在给定的一系列关键字序列中,将第一个关键字设为根节点。
若不为空,则与根节点的关键字作比较;
​ 若比根节点小,作为该根节点的左子树;
​ 若比根节点大,作为该根节点的右子树;
​ 若相等,则无需插入。

1.3.2 如何构建二叉搜索树(代码)

结构体:

typedef struct node
{
   KeyType key;
   InfoType data;
   struct node*lchild,*rchild;
}BSTNode,*BSTree;

ASL成功=每一层节点数层次数的总和/总结点数
ASL不成功=其他不存在于树中的每一层节点数(层次数-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;
     }
    }
}

时间复杂度:O(n);
递归是为了保存父子关系

1.4 AVL树

在计算机科学中,AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下都是O(log n)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。

特点

由于二叉搜索树存在数据分配不平衡的现象,会导致查找时的时间复杂度提高,所以诞生了AVL解决此类问题。
AVL树本质上还是一棵二叉搜索树,它的特点是:

1.本身首先是一棵二叉搜索树。

2.带有平衡条件:每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。

也就是说,AVL树,本质上是带了平衡功能的二叉查找树(二叉排序树,二叉搜索树)。
结合一组数组,介绍AVL树的4种调整做法。

这四种失去平衡的姿态都有各自的定义:
LL:LeftLeft,也称“左左”。插入或删除一个节点后,根节点的左孩子(Left Child)的左孩子(Left Child)还有非空节点,导致根节点的左子树高度比右子树高度高2,AVL树失去平衡。

RR:RightRight,也称“右右”。插入或删除一个节点后,根节点的右孩子(Right Child)的右孩子(Right Child)还有非空节点,导致根节点的右子树高度比左子树高度高2,AVL树失去平衡。

LR:LeftRight,也称“左右”。插入或删除一个节点后,根节点的左孩子(Left Child)的右孩子(Right Child)还有非空节点,导致根节点的左子树高度比右子树高度高2,AVL树失去平衡。

RL:RightLeft,也称“右左”。插入或删除一个节点后,根节点的右孩子(Right Child)的左孩子(Left Child)还有非空节点,导致根节点的右子树高度比左子树高度高2,AVL树失去平衡。

AVL树的高度和树的总节点数n的关系

h=log2(N(h)+1)

时间复杂度:

平衡二叉树上最好最坏进行查找关键字的比较次数不会超过平衡二叉树的深度--时间复杂度为 O(log2n)
介绍基于AVL树结构实现的STL容器map的特点、用法。
头文件:

#include <map>

函数:

begin() 返回指向 map 头部的迭代器
clear() 删除所有元素
count() 返回指定元素出现的次数
empty() 如果 map 为空则返回 true
end() 返回指向 map 末尾的迭代器
erase() 删除一个元素
find() 查找一个元素
insert() 插入元素
key_comp() 返回比较元素 key 的函数
lower_bound() 返回键值>=给定元素的第一个位置
max_size() 返回可以容纳的最大元素个数
rbegin() 返回一个指向 map 尾部的逆向迭代器
rend() 返回一个指向 map 头部的逆向迭代器
size() 返回 map 中元素的个数
swap() 交换两个 map
upper_bound() 返回键值>给定元素的第一个位置
value_comp() 返回比较元素 value 的函数

map为单映射容器,所谓单映射,就是一对一映射的意思。 每种信息都以 键 -> 值 的形式被存储,由键 映射到 值,这是该类容器与vector等容器最大的不同。

1.5 B-树和B+树

B树是一种自平衡的树,能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插入数据及删除的动作,都在对数时间内完成。B树,概括来说是一个一般化的二叉查找树,可以拥有多于2个子节点。与自平衡二叉查找树不同,B树为系统大块数据的读写操作做了优化。B树减少定位记录时所经历的中间过程,从而加快存取速度。

B-树

定义:一棵m阶B-树,要么是一棵空树,要么满足以下几点要求
每个节点最多有m个孩子节点,最多有m-1个关键字
除了根节点,其他节点最少有m/2个孩子节点,至少有(m/2)-1个关键字
如果根节点不是叶子节点,那么它至少要有2个孩子节点
特点:
m阶B-树的非根节点,孩子节点最小:(m/2) 最大:(m);关键字个数 最小:[(m/2)-1] 最大:[m-1];
根节点至少2个孩子
在计算高度的时候,外部节点也要计入在内。(外部节点即失败节点,指向它的指针为空,不存储信息,是虚设的)
一棵B-树有n个节点,则它有n-1个外部节点
结构体:

typedef int KeyType;		//关键字类型
typedef struct node
{
	int keynum;/*节点当前存储的关键字个数*/
	KeyType key[MAX];			//关键字域
	struct node* parent;/*双亲节点指针*/
	struct node* ptr[MAX];/*孩子节点指针数组*/
}BTNode;

B-树和AVL树区别

AVL树结点仅能存放一个关键字,树的敢赌较高,而B-树的一个结点可以存放多个关键字,降低了树的高度,可以解决大数据下的查找

B-树定义

B-tree树即B树,B即Balanced,平衡的意思。因为B树的原英文名称为B-tree,而国内很多人喜欢把B-tree译作B-树,其实,这是个非常不好的直译,很容易让人产生误解。如人们可能会以为B-树是一种树,而B树又是另一种树。而事实上是,B-tree就是指的B树。特此说明。
插入:
(1)如果该结点的关键字个数没有到达2个,那么直接插入即可;

(2)如果该结点的关键字个数已经到达了2个,那么根据B树的性质显然无法满足,需要将其进行分裂

删除:
首先需要明确一点:删除非叶子结点必然会导致不满足B树性质

那么可以这样处理:被删关键字为该结点中第i个关键字key[i],则可从指针son[i]所指的子树中找出最小关键字Y,代替key[i]的位置,然后在叶结点中删去Y。 因此,把在非叶结点删除关键字k的问题就变成了删除叶子结点中的关键字的问题了,

那么B树的删除操作就变成了删除叶子结点中的关键字问题了。
(1)被删关键字Ki所在结点的关键字数目不小于ceil(m/2),则只需从结点中删除Ki和相应指针Ai,树的其它部分不变


(2)被删关键字Ki所在结点的关键字数目等于ceil(m/2)-1,则需调整。

(3)被删关键字Ki所在结点和其相邻兄弟结点中的的关键字数目均等于ceil(m/2)-1,假设该结点有右兄弟,且其右兄弟结点地址由其双亲结点指针Ai所指。则在删除关键字之后,它所在结点的剩余关键字和指针,加上双亲结点中的关键字Ki一起,合并到Ai所指兄弟结点中(若无右兄弟,则合并到左兄弟结点中)。如果因此使双亲结点中的关键字数目少于ceil(m/2)-1,则依次类推。


B+树定义:

常被用来对检索时间要求苛刻的场景,适用于大型文件索引
一棵m阶B+树满足条件:

每个分支节点至少有m棵子树
根节点或没有子树,或者至少有两棵子树
除根节点,其他每个分支节点至少有m/2棵子树
有n棵子树的节点有n个关键字。
所有叶子节点包含全部关键字及指向相应记录的指针(关键字按顺序)
所有分支节点包含子节点最大关键字及指向子节点的指针

1.6 散列查找。

哈希表的设计主要涉及哪几个内容?
结合数据介绍哈希表的构造及ASL成功、不成功的计算
结合数据介绍哈希链的构造及ASL成功、不成功的计算

2.PTA题目介绍(0--5分)

介绍3题PTA题目

2.1 是否完全二叉搜索树(2分)

思路:按照层次顺序遍历这颗树的过程中,对于任意一节点x:
如果x有右子树,没有左子树,这肯定不是完全二叉树
如果x有左子树,没有右子树或x左右子树都没有,那么剩余的所有节点一定要为叶子节点

2.1.1 伪代码(贴代码,本题0分)

定义一个二叉树T;
定义一个队列Q;
定义数组A;
If(树为空)返回真 end if;
定义节点p且指向树根节点;
初始化队列;
p入队;
While(队列不为空){
出队;
                  对头元素赋值给p;
                  If(p为空) break;end if
                  p的左子树入队;
                  p的右子树入队;
}end 
While(队列不为空){
出队;
                  队头指针指向p;
                  数值赋给数组A;
                  If(p)为空  返回错误;end if
}
If(数组为升序)
     返回真;
Else
     返回假;
End if

2.1.2 提交列表

2.1.3 本题知识点

二叉树的层次遍历可以用于是否是完全二叉树的判断,由遍历结果可以看出,完全二叉树的数据集中在一起,不会出现空的现象

2.2 航空公司VIP客户查询(2分)

本题结合哈希链结构设计实现。请务必自己写代码,学习如何建多条链写法。

2.2.1 伪代码(贴代码,本题0分)

定义链表;
定义哈希链;
构造哈希链表;
初始化哈希链表;
定义节点p;
链表第一个节点赋值给p;
While(p不为空&&关键字与p指向的数据域相等)
{
p指向p的后继;
      返回p;
}end
输入:查找关键字;
for(遍历变量i未超过客户数)
{
if(查找成功)
{
返回;
}
else
{
未找到;
}end 
if
}end for
End

2.2.2 提交列表

2.2.3 本题知识点

1.练习哈希链的创建和查找,创建就是计算哈希地址,然后插入对应的单链表中;查找也得先计算哈希地址,然后在单链表中查找对应的信息
2.使用cin和cout比起使用scanf和pritf来输入输出,要更加耗时,在时间限制严格的题目中,应该使用scanf和pritf来输入输出
3.创建哈希链的过程中,应该注意不要让单链表的长度过长,否则查找和插入都将耗时过长
4.使用scanf来对string进行输入时,应先使用resize方法重新申请空间,因为默认申请的空间可能不足以容纳数据,比如该题的ID;同时语句的格式也应该注意,应该使用scanf("%s", &ID[0]);,而非scanf("%s", ID);,后者的格式无法输入

2.3 基于词频的文件相似度(1分)

2.3.1 伪代码(贴代码,本题0分)

定义int类型变量的两个文件内编号 file_a,file_b作为待查找的文件编号
定义变量类型为<string,int[]>的map容器构建单词索引表
根据单词在单词索引表中构建映射
声明一个map容器且对应类型都是long long的名称为vip
声明一个迭代器ster
定义整型变量IDNum存省份证号
定义字符型变量tail存身份证最后一位
定义distance存里程数
输入n,k//n代表n组数,k代表最低里程
while n-- do
    输入IDNum,tail,distance
    若tail为x则将IDNum置为负数
    iter为IDNum在map中的位置
    if 此IDNum不在vip中 do
        若distance小于最低里程k,则置为k
        将IDNum和distance的对应存入vip中
    else do    //IDNum存在
        若distance小于最低里程k,则置为k
        在该用户拥有的里程数基础上加上distance
    end if
end while
输入m
while m-- do//查找身份证所包含的里程数
    输入IDNum和tail
    若tail为x则将IDNum置为负数
    找IDNum在vip中的位置
    找到输出里程数,找不到输出No Info
end while

2.3.2 提交列表

2.3.3 本题知识点

1.倒排索引表的构建:由于本题不需要考虑一个文档中存在多个重复单词,所以只需要用set集合来存放文档数据即可;否则可以定义一个二维数组string a[][]或动态数组vettora[]来存放文档信息。
对于字母与非字母的判断:如果是C语言的话,可以在头文件中加入<ctype.h>,如果是C++的话就在头文件中加入,然后用库中的函数isalpha即可快速判断字符是不是字母。
2.对于相同单词不同大小写的情况,这里可以统一转换为大写或小写,方便后续判断。
3.单词的提取:要从主串中提取子串单词,可以使用string库中的substr(子串起始位置,子串结尾位置)来提取单词,但需要知道单词的首字母位置和尾字母位置,所以需要加一条循环语句来找单词的尾字母位置。
4.对于相似单词的判断:可以用string库中的count函数来判断该单词在文档中出现过几次。因为本题不考虑一个文档中存在多个重复单词,所以doc[q].count[it]只会有两个值,如果是1的话说明文档q中存在单词it;如果是0的话说明文档q中不存在单词it。

posted @ 2021-06-15 18:57  年少不知头发贵  阅读(63)  评论(0编辑  收藏  举报