《数据结构》中一些常见考点

已知二叉树写遍历序列 或 已知两种遍历序列构造二叉树

这考验我们对于二叉树的遍历方式的熟悉程度,之前我写过一篇非递归遍历的博客,但是为了讲解方便,这里还用递归的方式介绍吧

对于任何一个二叉树,其都可以宏观上看成由三部分构成 根节点,左子树,右边子树

输出也是由宏观到微观的次序来实现

在这里插入图片描述

前序遍历

根、左、右
先输出A,然后输出“左”的部分,待“左”全部输出完毕,再输出“右”的部分

具体“左”的部分又可以分为根、左、右
先输出B,再进行“左”,最后“右”

B的“左”为D,D可以看做是这样的结点
在这里插入图片描述
先输出“根”D,然后“左”是空,再输出“右”也是空,D这个部分算是输出完了,也就是说B的“左”输出完毕,可以进行B的“右”部分的输出,B的“右”是E为根节点的结构(类似D)

整个B输出完毕,算是A的“左”输出完毕,可以输出以C为根节点的A的“右”。

上图的遍历序列为:ABDECFG

代码如下

void BiTree::PreOrder(BiNode *bt) {
	if (bt == nullptr) {
		return;
	}
	else {
		cout << bt->data;
		PreOrder(bt->lchild);
		PreOrder(bt->rchild);
	}
}

以下就不讲这么详细了

中序遍历

左、根、右
在这里插入图片描述
还是这张图
先进行以B为根节点的“左”遍历,再输出A,再进行以C为根节点的“右”遍历
遍历形式和上一个差不多,只是输出顺序不同

上图遍历序列为:DBEAFCG

代码如下

void BiTree::InOrder(BiNode* bt) {
	if (bt == nullptr) {
		return;
	}
	else {
		PreOrder(bt->lchild);
		cout << bt->data;
		PreOrder(bt->rchild);
	}
}

后序遍历

左、右、根
在这里插入图片描述
先以B为根节点的“左”部分输出,再以C为根节点的“右”部分输出,最后在输出总体的根节点A
遍历模式参考第一个遍历详解,依旧是输出位置的差异

遍历序列为:DEBFGCA

代码如下

void BiTree::PostOrder(BiNode* bt) {
	if (bt == nullptr) {
		return;
	}
	else {
		PreOrder(bt->lchild);
		PreOrder(bt->rchild);
		cout << bt->data;
	}
}

层序遍历

不同于前面三个遍历,这种遍历方式是逐层输出,最大的问题就是一个结点输出完了其左右字数如果不及时记录,将会丢失;要求逐层输出就不能先输出字数的值,所以需要一个容器来存储地址信息
栈先进后出,无法执行同层输出,所以使用队列来存储

遍历序列为:ABCDEFG

代码如下

void BiTree::LevelOrder() {
	BiNode* Q[100], *p = nullptr;
	int front = -1, rear = -1;
	if (root == nullptr) return;//如果根节点为空,直接结束
	Q[++rear] = root;
	while (rear != front) {
		p = Q[++front];
		cout << p->data;
		if (p->lchild != nullptr) Q[++rear] = p->lchild;//已出队的结点有左子树就左子树入队
		if (p->rchild != nullptr) Q[++rear] = p->rchild;//右子树入队
	}
}

已知遍历序列构造二叉树

此处不讨论层序遍历,在前序、中序、后序遍历中至少知道两种遍历序列才能构造出来
通过比对不同遍历下元素位置的,确定相邻两个节点的关系
比如某个二叉树
前序遍历:ABDCEGF
中序遍历:DBAEGCF
由此可以确定根节点为A
那么整体框架是这样的
在这里插入图片描述
由于中序遍历可以区分左右部分各节点,所以我偏向以中序遍历的顺序为基础做猜想,其他遍历顺序来证明假设
左半部分:
中序遍历是DB,可以猜想两种情况
在这里插入图片描述或者在这里插入图片描述
但前序遍历中顺序为BD,所以B一定相对于D是根节点,所以第一种猜想成立

继续看右半部分:
中序遍历EGCF是右边的整体,而前序遍历中是CEGF,这四个节点C在最前面,所以再次利用中序遍历分左右
整个右边的结构大致如下
在这里插入图片描述
F必然是C的右节点了,剩下EG需要判定父子关系,中序关系是EG
要么在这里插入图片描述要么在这里插入图片描述
前序遍历也是EG,所以E为根节点,假设2成立

综合一下所有的推断就是这个图
在这里插入图片描述

构造哈夫曼树以及求WPL

叶结点的权值(weight)是对叶结点赋予的一个有意义的数值量,设二叉树具有n个带权值的叶结点,从根节点到各个叶结点的路径长度与相应叶结点权值的乘积值和称为二叉树的带权路径长度(weigthed path length)即WPL
如下图的WPL为5×1+4×2+2×2=17
在这里插入图片描述

除此以外2 、4 、 5这三个数还有许多构建二叉树的方法,如
在这里插入图片描述

其中WPL最小的二叉树称为最优二叉树,也称哈夫曼树
想要做到WPL最小,必须使确指越大的叶结点越靠近根节点

小知识点:哈夫曼树各节点的度不是0就是2(区别于满二叉树,满二叉树要求所有叶结点都在最下面一层

构造二叉排序树或者AVL树

二叉排序树的定义

二叉排序树又称二叉查找数,是一个空二叉树或者具有以下性质
1、若左子树非空,则左子树上值均小于根节点值
2、若右子树非空,则右子树上值均大于根节点
3、左右子树也都是二叉排序树

下图为一棵普通的二叉树
在这里插入图片描述

二叉排序树的插入操作

我发现代码还是没有流程图直观,下面大多是用流程图来解释吧
在这里插入图片描述
如果是初始构造排序树,那就循环调用插入函数,直到所有数据都被插入为止

平衡二叉树的定义(AVL并不是这个树的首字母,而是发明这个数据结构的人的名字Adelson-Velsky-Landis Tree)

平衡二叉树是一棵空树(空的果然是万能的)或者是具有以下性质的树
1、根节点的左子树和右子树深度(就是到根节点的路径长度)最多相差1
2、根节点的左右子树都是平衡二叉树

平衡因子是该结点左子树与右子树的深度之差,在一棵平衡二叉树中,平衡因子只可能是-1,0,1

最小不平衡子树

在平衡二叉树的构造过程中,以距离插入点最近的,平衡因子绝对值大于1的结点作为根节点那个子树称为“最小不平衡子树”,这个子树的根节点称为最小不平衡点(也称关键不平衡点)
在插入某个结点导致平衡二叉树失衡时,只需要调整最小不平衡子树即可。

在构造或者插入结点时,出现最小不平衡子树,需要进行调整,具体调整步骤我在另一篇博客有具体说明
概括起来说分为4种不平衡
1、LL
左子树左结点插入导致不平衡,整体向右旋转即可
2、LR
左子树右节点插入导致不平衡,先向左局部旋转成LL型,再按照LL旋转
3、RR
右子树右节点插入导致不平衡,整体向左旋转即可
4、RL
右子树左节点插入导致不平衡,先向右局部旋转成RR型,再按照RR旋转

构造散列表,求ASL

平均查找长度(Average Searching Length简称ASL)

是用来衡量当前存储结构的查找效率的一项指标公式为
在这里插入图片描述
Pi是查找这个数据的概率
Ci是查找这个数据的比较次数

散列表函数

直接定位法

散列函数是关键码的线性函数
比如存储数据关键码为{10,30,50,80,90}
散列函数H(key)=key/10
在这里插入图片描述
特点:不会产生冲突
缺点:空间浪费大
适用范围:关键码集合不是很大,连续性较好的情况

除留余数法

选取适当的正整数p,以关键码除以p的余数作为散列地址即
H(key)=key mod p
这里对取p就有要求,如果p不是素数,那么必然含有因子m n,那么所有含m n因子的数字都会整除,这就制造了冲突。如果散列表长为m,一般选p为小于等于m的最大素数
特点:简单常用,对关键码无特别要求
缺点:冲突多,需额外处理
适用范围:广泛

处理冲突的方法

开放地址法

如果当前位置冲突,那么就找下一个空的散列地址,如果还是冲突那就继续下一位
比如要存储{47,7,29,16,92,3},散列表长度为11,散列函数H(key)=key mod 11
H(47)=3,H(7)=7无冲突,直接存入
在这里插入图片描述

H(29)=7,应放入7的位置,但已经有数字了,存入下一个8的位置
在这里插入图片描述
H(16)=5,H(92)=4,无冲突,直接存入
在这里插入图片描述
H(3)=3,发生冲突,下一位4也有数,下一位5还是有数字,只能到6的位置
在这里插入图片描述
那么这张表最终是这样的
在这里插入图片描述
其成功查找的ASL为(5×1+2×1+4×1)/6=11/6
(查找长度为1的个数×1查找长度为2的个数×2+查找长度为4的个数×4)/总数据的个数

查找失败(当前存储无需要的数据)的ASL为(1+1+1+7+6+5+4+3+2+1)/11=31/11
所有当前位置比对的次数之和(如果当前位置下一位有数就要一直比对到没数据为止) / 列表长度

拉链法(开散列表)

基本思想是将所有散列地址相同的记录(关键码同义的记录存储在一个单链表中)存储在一个单链表中
比如上面的那道题,要存储{47,7,29,16,92,3},其中“3”取模运算后结果与47、92、16同义
其就可以用如下单链表连接
在这里插入图片描述
注意是单链表的头插法因为尾插法对待插入数据位置的链表长度未知,所以插入效率会低于头插法
所以{47,7,29,16,92,3}插入开散列表(表长依旧为11,散列函数H(key)=key mod 11)后应该是

在这里插入图片描述
其成功查找的ASL为(2×1+2×2+1×3+1×4)/6=13/6
失败查找ASL为(9×1+4+2)/6=15/6=5/2

已知图求邻接矩阵存储示意图

对于图的存储常用邻接矩阵,用一个一位数组存储图中的顶点,用一个二维数组存储图中的边(即各个顶点之间的邻接关系)
比如下图
在这里插入图片描述

可以用如下的矩阵表示
在这里插入图片描述
如果没有箭头,则会行程一个对称矩阵

Prim算法 Kruskal算法求最小生成树

Prim算法

归纳:从指定节点开始,以最短路径为方向(如果出现环则放弃该路径),遍历整个图的所有顶点,最终路径数等于节点数-1
具体步骤csdn大佬文章更详细(其实是我懒得画图)

Kruskal算法

归纳:先对边的权值排序,按照从小到大的顺序选取最短路径(出现环则放弃该路径),遍历整个图所有顶点,最终路径数等于节点数-1
具体步骤csdn大佬文章更详细

Dijkstra算法求单源最短路径

从单源v0开始,从待定路径中找到当前最短路径,如果当前路径比已知的最短路径还要短,则“刷新”v0

堆排序

堆排序逻辑结构是一个完全二叉树,物理存储结构还是一个一位数组
比如这个序列
在这里插入图片描述
对应的二叉树如下
在这里插入图片描述

这显然是没有顺序的堆,所以要

构建初始堆

以构建小根堆为例,从序号最大非叶结点开始(即总结点数/2,即使总结点数为奇数,/2向下取整,所得数字依然是最大非叶结点)依次向前,待操作与子节点比较,子节点较小就交换数据,调整完还需继续比对交换后的子节点子节点的子节点,若还是子节点小,就继续交换
待操作结点一直到根节点位置,方可停止排序

比如上图序号最大非叶结点就是11,11>65,无需交换
11前一个是67,67<64
在这里插入图片描述
交换位置
在这里插入图片描述

再往前是30,30<11
在这里插入图片描述
交换位置
在这里插入图片描述

30<65,所以无需继续交换
再向前一个就是根节点50,50<11
在这里插入图片描述
交换位置
在这里插入图片描述
50和子节点比,20更小,继续交换
在这里插入图片描述
最终得到了一个小根堆
在这里插入图片描述
根节点也就是0号元素就是这个序列中最小的数据
交换0号元素与最后一个元素
那么最后一位就是第一个有序的元素,前n-1个元素继续执行上述构建小根堆算法,直到无序元素仅剩一个(一定是最大元素)
就可以得到一个降序的序列

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值