DS博客作业05--查找

| 这个作业属于哪个班级 | 数据结构--网络2011/2012 |
| ---- | ---- | ---- |
| 这个作业的地址 | DS博客作业05--查找 |
| 这个作业的目标 | 学习查找的相关结构 |
| 姓名 | 付峻霖 |

0.PTA得分截图

1.本周学习总结(0-5分)

1.1 查找的性能指标

ASL(Average Search Length)

在查找运算中,由于所费时间在关键字的比较上,所以把平均需要和待查找值比较的关键字次数称为平均查找长度。

  • 公式:ASL=∑ni=1pici
  • ASL是平均查找次数,在期末考中就考了一题哈希表,可以计算查找成功查找不成功的平均查找长度,ASL可以很直观的计算出查找的效率

1.2 静态查找

1.2.1 顺序查找

  • 顺序查找定义

就从头遍历到尾部一个一个找,找到待查找元素即查找成功,若到尾部没有找到,说明查找失败

  • 顺序表的结构体定义
typedef int ElemType;       /* ElemType类型根据实际情况而定,这里假设为int */
typedef struct
{
    ElemType data[MAXSIZE]; /* 数组,存储数据元素 */
    int length;             /* 线性表当前长度 */
}SqList;
  • 代码实现
/* 初始条件:顺序线性表L已存在 */
/* 操作结果:返回L中第1个与e满足关系的数据元素的位序。 */
/* 若这样的数据元素不存在,则返回值为0 */
int LocateElem(SqList L,ElemType e)
{
    int i;
    if (L.length==0)
            return 0;
    for(i=0;i<L.length;i++)     /* 遍历顺序表 */
    {
            if (L.data[i]==e)   /* 找到了 */
                    break;
    }
    if(i>=L.length)
            return 0;

    return i+1;
}
  • ASL分析
    成功:从头查找到尾,因此平均查找次数为(1+2+3..+n)/n=(n+1)/2
    不成功:从头找到尾都没有找到,因此查找次数就为表的长度n

1.2.2 二分查找

  • 要求是顺序表,根据顺序表构建二叉树,中间数为根,左小右大,也就是判定树
  • 例如

    如图,根据判定树求解,假设每个元素的查找概率相等,则查找成功时的ASL=(11+22+43+44)/11。第一层的一个数比较成功时所需比较次数一次,第二层的两个数需要两次,依此类推。即查找成功时比较次数:为该结点在判定树上的层次数,不超过树的深度d=log2n+1, 查找不成功的过程就是走了一条从根结点到外部结点的路径d或d-1。
  • 时间复杂度
    每次查找后,范围缩小一半,因此时间复杂度为O(logn)
  • ASL分析
    成功=每一层节点数层次数的总和/总结点数
    不成功=其他不存在于树中的每一层节点数(层次数-1)/总结点数

1.3 二叉搜索树

二叉排序树的定义:
1.或者是空树
2.或者是满足以下性质的二叉树
★若它的左子树不空,则左子树上的所有关键字的值均小于根关键字的值
★若它的右子树不空,则右子树上的所有关键字的值均大于根关键字的值

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

  • 结合一组数据介绍构建过程
    设有一个输入数据的序列是 { 46, 25, 78, 62, 12, 80 }, 试画出从空树起,逐个输入各个数据而生成的二叉搜索树。
  • 二叉搜索树的ASL成功和不成功的计算方法。
    ASL成功=每个结点的高度相加/结点总数
    例如:
    ☆ 5,4,3,2,1 五个关键字

    ASL=(1+2+3+4+5)/5=3

☆ 3,4,5,2,1关键字

ASL=(1+2+2+3+3)/5=2.2

ASL不成功=其他不存在于树中的每一层节点数(层次数-1)/总结点数
例如:

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

  • 结构体定义
/* 二叉树的二叉链表结点结构定义 */
typedef  struct BiTNode	/* 结点结构 */
{
	int data;	/* 结点数据 */
	struct BiTNode *lchild, *rchild;	/* 左右孩子指针 */
} BiTNode, *BiTree;
  • 二叉搜索树的构建操作
void CreateBST(BiTree *T, int n)/*建树函数*/
{
	int x;
	BiTree T=NULL;//树初始化为空
	for (int i = 0; i < n; i++)
	{
		cin >> x;
		InsertBST(&T, x);
	}
}
  • 二叉搜索树的插入操作
/*  当二叉排序树T中不存在关键字等于key的数据元素时, */
/*  插入key并返回TRUE,否则返回FALSE */
int InsertBST(BiTree *T, int key) 
{  
	BiTree p,s;
	if (!SearchBST(*T, key, NULL, &p)) /* 查找不成功 */
	{
		s = (BiTree)malloc(sizeof(BiTNode));
		s->data = key;  
		s->lchild = s->rchild = NULL;  
		if (!p) 
			*T = s;			/*  插入s为新的根结点 */
		else if (key<p->data) 
			p->lchild = s;	/*  插入s为左孩子 */
		else 
			p->rchild = s;  /*  插入s为右孩子 */
		return TRUE;
	} 
	else 
		return FALSE;  /*  树中已有关键字相同的结点,不再插入 */
}
  • 二叉搜索树的删除操作
/* 从二叉排序树中删除结点p,并重接它的左或右子树。 */
int Delete(BiTree *p)
{
	BiTree q,s;
	if((*p)->rchild==NULL) /* 右子树空则只需重接它的左子树(待删结点是叶子也走此分支) */
	{
		q=*p; *p=(*p)->lchild; free(q);
	}
	else if((*p)->lchild==NULL) /* 只需重接它的右子树 */
	{
		q=*p; *p=(*p)->rchild; free(q);
	}
	else /* 左右子树均不空 */
	{
		q=*p; s=(*p)->lchild;
		while(s->rchild) /* 转左,然后向右到尽头(找待删结点的前驱) */
		{
			q=s;
			s=s->rchild;
		}
		(*p)->data=s->data; /*  s指向被删结点的直接前驱(将被删结点前驱的值取代被删结点的值) */
		if(q!=*p)
			q->rchild=s->lchild; /*  重接q的右子树 */ 
		else
			q->lchild=s->lchild; /*  重接q的左子树 */
		free(s);
	}
	return TRUE;
}
  • 分析代码的时间复杂度
    时间复杂度:最好:O(logn),最坏:O(n)
  • 为什么要用递归实现插入、删除?递归优势体现在代码哪里?
    1.递归可以使算法大大简化,代码足够精简(意味着不好理解)
    2.保留父子关系,便于删除和插入顺利找到父亲和孩子

1.4 AVL树

  • 1.AVL树解决什么问题,其特点是什么?
    ★AVL能解决二叉搜索树存在数据分配不平衡的问题,她将二叉搜索树调整为经可能的矮,这样平均查找长度越小,查找速度越快。
    ★平衡二叉树是二叉排序树的改进版,也就是说平衡二叉树是二叉排序树的一个特殊情况
    ★我们将每个结点的平衡因子的绝对值都不超过1二叉排序树称为平衡二叉树
    ★平衡因子:左子树高度-右子树高度

  • 2.结合一组数组,介绍AVL树的4种调整做法。
    输入关键字序列{16,3,7,11,9,26,18,14,15},构造一颗AVL树。 有调整要写成调整类型及结果

    no picture you say a J8,4种调整做法图中已经画的非常清楚了,我这就不再进行过多的文字赘述

  • 3.AVL树的高度和树的总节点数n的关系?
    高度为 h 的 AVL 树,最多为满二叉树,节点数 N 最多2^h − 1; 最少类似于斐波那契数列,满足N(h)=N(h− 1) +N(h− 2) + 1 的计算公式

  • 4.介绍基于AVL树结构实现的STL容器map的特点、用法。

Map是STL的一个关联容器,翻译为映射,数组也是一种映射。如:int a[10] 是int 到 int的映射,而a[5]=25,是把5映射到25。数组总是将int类型映射到其他类型。这带来一个问题,有时候希望把string映射成一个int ,数组就不方便了,这时就可以使用map。map可以将任何基本类型(包括STL容器)映射到任何基本类型(包括STL容器)。

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

1.5 B-树和B+树

1.5.1 B-树和AVL树区别,其要解决什么问题?

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

1.5.2 B-树定义。结合数据介绍B-树的插入、删除的操作,尤其是节点的合并、分裂的情况

  • B-树定义

也叫B树,即平衡多路查找树,m阶B树表示节点可拥有的最多m个孩子,2-3树是3阶B树,2-3-4树是4阶B树。多叉树可以有效降低树的高度,h=log_m(n),m为log的底数。

★一颗m阶的B树需要满足下列条件:
①每个结点最多有m-1个关键字。
②根结点最少可以只有1个关键字。
③非根结点至少有m/2-1个关键字。
④每个结点中的关键字都按照从小到大的顺序排列,每个关键字的左子树中的所有关键字都小于它,而右子树中的所有关键字都大于它。
⑤所有叶子结点都位于同一层,或者说根结点到每个叶子结点的长度都相同。

  • B-树的插入
    ★思路:插入的时候,我们需要记住一个规则:判断当前结点key的个数是否小于等于m-1,如果满足,直接插入即可,如果不满足,将节点的中间的key将这个节点分为左右两部分,中间的节点放到父节点中即可。

★举例:在5阶B树中,结点最多有4个key,最少有2个key(注意:下面的节点统一用一个节点表示key和value)。
插入18,70,50,40

插入22

插入22时,发现这个节点的关键字已经大于4了,所以需要进行分裂
分裂后结果如下

接着再分别插入23,25,39

分裂后,得到最终结果

  • B-树的删除
    ★思路:在B-树中删除节点时,可能会发生向兄弟节点借元素,和孩子节点交换元素,甚至节点合并的过程。
    ★举例:我们以下面的5阶树为基础,每个节点内元素个数为2~4个,依次删除8、16、15、4这4个元素。

    删除8,因为删除8后,不破坏树的性质,所以直接删除即可。

    然后删除16,这导致该节点只剩下一个13节点,不满足节点内元素个数为2~4个的要求了。所以需要调整。这里可以向孩子借节点,把17提升上来即可,得到下图。这里不能和兄弟节点借节点,因为从3,6节点中把6借走后,剩下的3也不满要求了。另外,也不能把孩子中的15提升上来,那样会导致剩下的14不满足要求。

    然后删除15,删除15后同样需要调整。调整的方式是,18上升,17下降到原来15的位置,得到下图。

    然后删除元素4,删除4后该节点只剩下5,需要调整。可是它的兄弟节点也都没有多余的节点可借,所以需要进行节点合并。节点合并时,方式会有多种,我们选择其中的一种即可。这里,我们选择父节点中的3下沉,和1,2,以及5进行合并,如下图。

    这次调整后,导致6不符合要求了。另外,6非根节点,但只有2个孩子,也不符合要求。需要继续调整。调整的方式是,将10下沉,和6,以及13,18合并为根节点
    调整完后得到最终结果

1.5.3 B+树定义,其要解决问题

  • B+树定义

B+树是应数据库所需而出现的一种 B 树的变形树。

★一棵m阶的B+树需要满足下列条件:
①每个分支结点最多有m棵子树(子结点)
非叶根结点至少有两棵子树,其他每个分支结点至少有m/2棵子树
③结点的子树个数关键字个数相等
④所有叶结点包含全部关键字及指向相应记录的指针,而且叶结点中将关键字按大小顺序排列,并且相邻叶结点按大小顺序相互链接起来。
⑤所有分支结点(可看成是索引的索引)中仅包含它的各个子结点(即下一级的索引块)中关键字的最大值及指向其子结点的指针

  • 解决的问题

B树只适合随机检索B+树同时支持随机检索和顺序检索,因此B+树比B树更适合实际应用中操作系统的文件索引数据库索引 **
数据库索引要采用B+树,因为B+树的叶子节点使用指针顺序连接在一起,只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树效率太低**。

1.6 散列查找。

1.6.1 哈希表的设计主要涉及哪几个内容?

  • 定义

哈希表又叫散列表,是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

  • 哈希函数常用构造方法
  1. 直接定址法
    直接定址法是以关键字k本身加上某个常数c作为哈希地址的方法。
    哈希函数为h(k)=k+c
    该方法的特点是哈希函数计算简单,若关键字分布不连续将造成内存单元大量浪费。
  2. 除留取余法
    除留余数法是用关键字k除以某个不大于哈希表长度m的整数p所得的余数作为哈希地址。
    哈希函数为h(k)=k mod p
    该方法选取的p最好为不大于m的素数,从而尽量减少发生哈希冲突的可能性。

1.6.2 结合数据介绍哈希表的构造及ASL成功、不成功的计算

例如关键字序列{18,2,10,6,78,56,45,50,110,8}这样一组数据,散列函数为:H(key)=key)mod 11,处理冲突采用线性探测法,装填(载)因子为0.77。
首先根据装填因子求出散列表长度为13,画图后,对每个关键字取余11,把他们一个一个放到散列表中,如果有冲突,则采取线性探测法

1.6.3 结合数据介绍哈希链的构造及ASL成功、不成功的计算

例如关键字序列(25,10,8,27,32,68)这样一组数据,散列表的长度为8,散列函数H(k)=k mod 7
用链地址法的好处是不会存在冲突的现象,当出现俩数据取余后的值相同时,则头插法插在同一条链上。

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

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

  • 什么是完全二叉搜索树呢?
    通俗点来讲,就是完全二叉树+二叉搜索树,需要同时满足这两种树的条件,所以要求
    ①生成结点的顺序是严格按照从上到下,从左往右的顺序来构建的二叉树。
    ②任意一个结点,左子树所有结点的键值要大于结点,右子树所有结点的键值要小于结点
  • 判断一棵树是否是完全二叉树的基本思路
    ①如果树为空,则直接返回错
    ②如果树不为空:层序遍历二叉树
    ★如果一个结点左右孩子都不为空,则pop该节点,将其左右孩子入队列;
    ★如果遇到一个结点,左孩子为空,右孩子不为空,则该树一定不是完全二叉树;
    ★如果遇到一个结点,左孩子不为空,右孩子为空;或者左右孩子都为空;则该节点之后的队列中的结点都为叶子节点;该树才是完全二叉树,否则就不是完全二叉树;


如图所示上面这种从上到下,从左到右依次构建二叉树,并且满足二叉搜索树的条件,这样的树就是完全二叉搜索树。

而这种,就不是了,因为不满足完全二叉树的条件:从上到下,从左到右依次构建二叉树。

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

int levelOrder(BinTree t)   //层序遍历并判断完全二叉树
{
    如果是空树
        打印“空”
    根结点入队列
    while (队列有东西时)
        if (队列读取到空结点)      
            flag = 1;//表示接下来不再统计结点数
        else //队列头结点非空
            打印队头元素
            if (flag == 0)
                结点数++;      //统计结点数
            左结点入队列
            右结点入队列
        队列头出队列
    return 结点数;
}

2.1.2 提交列表

2.1.3 本题知识点

①用了层次遍历,因此要借用了队列来存储树中的结点
②调用了队列的相关库函数,q.push(),q.front(),q.pop()等等
③关于完全二叉搜索树概念的理解:完全+搜索

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

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

int main()
{
	for (循环输入n条数据)
		输入输入身份证,已行驶里程数据
		if (里程小于最小里程)
			低于最低里程按最低里程k计算
		if (若该身份证已被登记为会员)
			增加里程数
		else(未被登记为会员)
			则现在输入身份证及里程信息
	for (M行查询人的身份证号码)
		输入身份证
		if (该身份正存在,则输出)
			则输出
		else 该身份不存在
			输出"No Info"
}

2.2.2 提交列表

2.2.3 本题知识点

①哈希表的运用。利用哈希表来存放结点所在下标。
②调用了图的库函数,用M.count(id)求数组M中下标为id的数据是否存在
③map<string,int>M
④调用max(a,b)函数来求a b中的较大值

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

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

int main() {
	for(遍历文件) {
		while(读取一行文本 碰到#就停止)
            令top=0来分割字符串 
			for(将一行的字符串进行划分)
				if(不是字母,即为分割符)
					if(单词小于3,不记录)
						top=0;
						跳过本次循环
					if(单词大于10)
                        只记录前十
					else
                        加结束符
                        单词数+1
				else 是字母,
					就把所有 大写改小写
			本行的最后一个单词还没有进行判断,需要单独判断一下 
	//计算相似度 
	for(遍历查询总数)
		输入一对文件编号
		map<string,int>::iterator it1,it2;//两个迭代器,类似指针 
		分子,分母=两个文件所有单词-相同的 
		for(同时对两个文件遍历 )
			if(it1比it2小)
                it1++向后移动 
			else if(it2比it1小)
                it2++向后移动 
			else it2等于it1
				都向后移动
}

2.3.2 提交列表

2.3.3 本题知识点

①调用了图的库函数
②利用getchar()吸收回车
③使用了迭代器

posted @ 2021-06-14 15:04  强扭的甜瓜  阅读(485)  评论(0编辑  收藏  举报