数据结构-二叉树、堆、图
二叉树、堆
一、线索二叉树
- 规律:在有n个节点的链式二叉树中必定存在 n+1 个空指针
- 链式二叉树中有很多的空指针,可以让这些空指针指向前一个节点\后一个节点,从而在有序遍历(中序遍历)二叉树时,不需要使用递归而通过循环即可以完成,并且效率要比递归快得多
- 一定是搜索二叉树
线索二叉树的结构
typedef struct TreeNode {
int data; // 数据域
struct TreeNode* left;
bool lflag; // 左子树是否是线索 为真时,左子树是线索 指向前一个节点
struct TreeNode* right;
bool rflag; // 右子树是否是线索 为真时,右子树是线索 指向下一个节点
} TreeNode;
构建线索二叉树
- 首先需要有一颗搜索二叉树,然后通过中序遍历并生成线索,通过检查右子树是否为空来决定是否生成线索,让右子树指向下一个节点。
- 当构成线索二叉树后 ,可以通过循环遍历的方式有序遍历二叉树,不需要中序递归遍历也可以
二、选择树
- 是一种完全二叉树,把待比较的数据存储在最后一层,根节点的值是左右子树中其中一个,是它们的最大值或最小值,选择树的功能是快速地找出最大值或最小值
三、堆
堆结构介绍
大顶堆(大根堆):根节点的值比左右子树都大,同时左右子树都满足该规则
小顶堆(小根堆):根节点的值比左右子树都小,同时左右子树都满足该规则
堆结构是一种特殊的完全二叉树,它与堆内存是两种概念
堆结构的根节点一定是整棵树中的最大值、最小值
堆结构如何存储:
首先堆结构是一种完全二叉树,并且需要在使用的时候频繁地找双亲节点进行比较,所以链式不好找双亲节点,因此堆结构非常适合用顺序存储,通过二叉树性质5来实现找双亲:
性质5:
有一个n个结点的完全二叉树,结点按照从上到下从左到右的顺序排序为1~n。
1、i > 1时,i/2就是它的双亲结点。
2、i*2是i的左子树,当i*2>n时,则i没有左子树。
3、2*i+1是i的右子树,2*i+1>n时,则i没有右子树
堆排序
- 借助堆结构,先把待排序数组先调整成大顶堆或者小顶堆结构,然后堆顶与末尾元素交换,从堆顶到末尾元素的前一个元素之间重新调整成堆结构,重复该步骤,最后当堆中只剩一个元素时,待排序数组就有序了。
- 可以循环实现、也可以递归实现
- 堆结构还是优先队列的底层逻辑
四、平衡二叉树(AVL树)
- 前提一定是搜索二叉树,对于根节点的左右子树的高度差不能超过1,并且所有子树都要循序这个要求
- 如果一个搜索二叉树呈现或接近单支状,它的查找效率很低,很接近链表,因此如果能让它平衡时,查找效率最高
- 由于节点的位置要受到相互之间值的影响,并且在往平衡二叉树中添加节点或者删除节点前,二叉树本身是平衡的,所以只可能在最后操作的节点附近不满足平衡条件,因此需要在该过程中对该节点进行判断并调整。
- 因此一棵平衡二叉树因为添加操作导致不平衡的原因,总结就四种:
第一种:
x y
/ \ / \
y t1 z x
/ \ / \ / \
z t2 t3 t4 t2 t1
/ \
t3 t4 以y为轴 右旋转
第二种:
x y
/ \ / \
t1 y x z
/ \ / \ / \
t2 z t1 t2 t3 t4
/ \
t3 t4 以y为轴 左旋转
第三种:
x x z
/ \ / \ / \
y t1 z t1 y x
/ \ / \ / \ / \
t2 z y t4 t2 t3 t4 t1
/ \ / \
t3 t4 t2 t3
先以z为轴左旋转 再以z为轴右旋转 达到平衡
第四种:
x x z
/ \ / \ / \
t1 y t1 z x y
/ \ / \ / \ / \
z t2 t3 y t1 t3 t4 t2
/ \ / \
t3 t4 t4 t2
先以z为轴右旋转 再以z为轴左旋转 达到平衡
删除节点
- 待删除的节点是叶子节点,直接删除
- 待删除节点的左子树或者右子树为空,则使用非空节点替换
- 待删除节点左右子树非空,则根据左右子树的高度,选择高的一边子树,如果是左子树高,选择左子树中的最大节点赋值给待删除节点,然后再左子树中继续删除该最大节点,相当于继续处理情况1或情况2;如果是右子树高,则在右子树选择最小值节点继续同样处理。
- 删除后可能导致不平衡,需要重新调整平衡
平衡二叉树的优点:
避免了二叉搜索树呈现单支状,让其能以最佳的效率进行查找操作 O(log2n)
平衡二叉树的缺点:
在插入、删除操作时,为了达到平衡需要进行大量的左旋、右旋操作、计算高度,所以此时操作速度慢
因此AVL树适合在数据量大并且数据量比较稳定,没有太多的插入、删除操作,适合大量的查找操作。
五、红黑树
- 是一种自平衡的有序二叉树,不是根据树的高来调整平衡、而是树节点的颜色来调整
红黑树的特性:
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
红黑树的优点:
插入、删除操作的效率比AVL高
缺点:
没有AVL那么均匀,查找效率略低于AVL树,但是因为性质5决定了不至于太差,综合而言,综合性能优秀,所以应用场景很多
哈夫曼树
相关基础概念
路径长度:从一个节点到另一个节点之间的路径条数目
从根节点到第L层节点路径长度是L-1
树的路径长度: 从根节点出发到每个节点的路径长度之和
节点的权:如果给树中每个节点赋予一个某种含义的数值,该数值称为该节点的权值
节点的带权路径长度:从根节点到该节点的路径长度与该节点的权值的乘积
树的带权路径长度:所有叶子节点的带权路径长度之和 WPL
成绩: <60 60~69 70~79 80~89 90~100
等级 E D C B A
比例 5% 30% 40% 15% 10%
普通带权二叉树的WPL:
WPL=1*5+2*30+3*40+4*15+4*10 = 285
哈夫曼树的WPL:
WPL=40+2*30+3*15+4*5+4*10 = 205
WPL是评价一棵带权二叉树优劣重要标准
哈夫曼树:一定是一棵WPL最小的带权二叉树
构建哈夫曼树:
1、把n个带权节点先存储一个集合F中,每个节点的左右子树都为空
2、从F中选取根节点的权值最小的两个节点作为左右子树构成一棵新的二叉树,左小右大,这颗二叉树的根节点的权值等于两个子树权值之和
3、从F中删除刚刚取出来的两个节点,并把新形成的二叉树根节点放入F中
4、重复步骤2、3,直到F中只剩下一棵树,就是哈夫曼树
六、哈弗曼编码
目的:当年是为了解决远距离通信传输内容最优解问题
代发送文字:ABBCD EEAFD
方法1:转换成二进制 001 总共要发送30个二进制数
方法2:
1、根据字母出现频率,构建哈夫曼树
假设:A 28 B 5 C 7 D 18 E 30 F 12、
2、规定哈夫曼树中左分支为0,右分支为1,从根节点出发到叶子节点经过的路径分支组成的0和1的有序序列就是该叶子节点的哈弗曼编码
A 10 B 0110 C 0111 D 00 E11 F010
ABBCD EEAFD哈夫曼编码:100110011001110011111001000 总共27字符
作用:数据压缩、文件压缩也是一种解法
图
一、图的介绍
图是一种比较复杂的数据结构,在线性表中数据元素之间仅有线性关系,每个元素只有一个直接前驱和直接后继(元素之间只存在一对一关系),在树形结构中元素之间有着明显的层次关系,每一层的元素只能和下层的多个元素有关系(元素之间存在一对多关系),而在图形结构中,任意两个结点之间都可能有关系(元素之间存在多对多关系)。
二、图的定义和术语
顶点:图中的数据元素被称为顶点,一般使用V表示图的顶点的有穷非空集合。
弧:两个顶点之间的关系记作<v,w>,表示能从顶点v到达顶点w,也就是v能到w,但w不一定能到v,我们称v为弧尾或初始点,称w为弧头或终端点。
有向图:由弧+顶点构成的图叫有向图,也就是顶点之间是单行道。
边:两个顶点之间的关系记录(v,w),表示既能从v到w,也能从w到v,我们称v和w之间的关系是一条边。
无向图:由边+顶构成的图有无向图,顶点之间的双行道。
注意:一般使用G代表图,V顶点的集合,VR代表弧或边的集合,n代表顶点的数目,e代表边或弧的数目,我们不讨论顶点到自己的边或弧。
完全图(无向完全图):在无向图中,e的取值范围是0 ~ n*(n-1)/2,如果无向图的边的数量达到最大值这种无向图称称为完全图,即任意两个顶点之间存在边。
有向完全图:在有向图中,e的取值范围是0 ~ n(n-1),如果有向图的弧的数量达到最大值这种有向图称称为有向完全图,即任意两个顶点之间存在方向相反的弧。
稀疏图和稠密图:如果图中的边和弧很少,e<nlogn 这种图被称为稀疏图,反之称为稠密图。
权和网:如果图中的顶点到另一个顶点需要代价(距离或耗费),那么在表示边或弧的时候需要附加数据,附加的数据就叫做权,带权的图通常被称为网,这也是互联网的由来。
子图:假定有两个图G1和G2,如果G1的顶点集合是G2的顶点集合的子集,且G1的边或弧集合是G2的边或弧集合的子集,则称G1是G2的子图。
邻接点:在无向图中如果有一条边(v,w),则v,w两个顶点互为邻接点,即v和w相邻接,边(v,w)依附于顶点v,w,或者说(v,w)和顶点v、w相关联。
顶点v的度:在无向图中与顶点v相关联的边的数量。
顶点v的入度和出度:在有向图中,以顶点v作为弧头的弧的数量称为顶点的入度,以顶点v作为弧尾的弧的数量称为顶点的出度。
路径:从顶点v到达顶点w所经历的顶点序叫做路径,路径的长度就是边或弧的数目。
回路或环:起始点和终点相同的路径称为回路或环。
简单路径:路径中顶点不重复出现的路径称为简单路径。
简单回路或简单环:起始点和终点相同且其余顶点不重复出现,被称为简单回路或简单环。
连通图:在无向图中,从顶点v到顶点w有路径,则称v和w是连通的,如果图中任意两个顶点都是连通的,则称图为连通图。如果一个图中有n个顶点那么至少需要n-1条边才能达到连通图。
生成树:仅需要n-1边的连通图叫生成树,如果再配合上权重,代价最小的叫最小生成树
连通分量:G1和G2都是连通图,且G1是G2的子图,则称G1是G2的连通分量或极大连通子图。
强连通图:在有向图中,如果任意一对顶点都双向连通,则称图为强连通图。
强连通分量:G1和G2都是强连通图,且G1是G2的子图,则称G1是G2的强连通分量或极大强连通子图。
三、图的存储结构:
邻接矩阵:
- 使用一维数组存储n个顶点
- 使用二维数组存储顶点之间的边或者弧
char V[7] = {A,B,C,D,E,F,G};
char e[7][7]
A B C D E F G
A 0 1 1 1 0 0 0
B 1 0 0 1 0 0 0
C 1 0 0 0 0 1 0
D 1 1 0 0 1 0 1
E 0 0 0 1 0 1 0
F 0 0 1 0 1 0 1
G 0 0 0 1 0 1 0
对于二维数组e[i][j]的值为1,表示顶点V[i]与顶点V[j]之间存在边
因为不考虑顶点自己到自己的边,因此左对角线必定为0
如果存储的是无向图,数值会沿对角线对称,可以进行压缩矩阵
邻接矩阵优点:
- 计算出度、入度方便,操作简单,只需要操作顺序表
邻接矩阵缺点:
- 当图是稀疏图时,会非常浪费存储内存
邻接表:
边结构:
顶点下标
下一条边地址
顶点结构:
顶点数据
指向该顶点第一条边的指针
图结构:
由顶点结构元素组成的数组
顶点数量
优点:
存储边是通过单链表存储,所以不会浪费内存,并且计算出度方便
缺点:
计算入度比较麻烦,需要遍历所有顶点的边链表
十字链表:
-
用于存储有向图的链式结构+顺序结构
弧:
弧尾下标
弧头下标
指向弧尾相同的下一条弧
指向弧头相同的下一条弧
顶点
数据
指向第一条出度的弧
指向第一条入度的弧
图:
顶点顺序表
顶点数量
优点:计算出度、入度比较方便,并也节约内存
邻接多重表:
- 是一种存储无向图的一种顺序+链式结构
边:
与边阾接的两个顶点的下标 i j
i_next 指向下一条与i阾接的边
j_next 指向下一条与j阾接的边
四、图的遍历
深度优先(DFS)
- 对每一个可能的分支路径深入到不能再深入为止,而且每个结点只能访问一次。
- 不全部保留结点,占用空间少;有回溯操作(即有入栈、出栈操作),运行速度慢
广度优先(BFS)
- 又叫层次遍历,从上往下对每一层依次访问,在每一层中,从左往右(也可以从右往左)访问结点,访问完一层就进入下一层,直到没有结点可以访问为止
- 需要借助队列实现,需要额外内存协助
- 保留全部结点,占用空间大; 无回溯操作(即无入栈、出栈操作),运行速度快
- 一般需存储产生的所有结点,占用的存储空间要比深度优先搜索大得多,因此,程序设计中,必须考虑溢出和节省内存空间的问题