数据结构-树的基本概念
数据结构-树的基本概念
1.树 : 一般以链表的方式存储。
(1)树可以发散为生活中的各种可能。比如机器人要实现围棋,需要列出各种可能。
(2)树的遍历方式:
深度优先: 使用递归实现 - 最先根节点,然后所有左边再所有右边。
前序:根->左->右
中序:左->根->右
后序:左->右->根
广度优先:使用队列实现 - 最先根节点,然后一层一层扩散;
优先级优先:使用于现实中的业务场景。应用在推荐算法、深度学习等。
优缺点:
深度优先:不全部保留结点,占用空间少;有回溯操作(即有入栈、出栈操作),运行速度慢。
广度优先:保留全部结点,占用空间大; 无回溯操作(即无入栈、出栈操作),运行速度快。
(3)概念:
度数:每个节点所有子树的个数
层数
高度:树的最大层数。
森林:移去根节点就是森林
2.二叉树:最多只有两个子节点(度数<=2)
2.1 由于N叉树N越大浪费的指针的比例越大,所以一般使用二叉树。
n叉树有m个节点,那么空指针: n * m -(m-1) = m(n-1) +1 ,浪费率为 (m(n-1) +1)/(m*n) = 1 - 1/n + 1/mn 约等于 1- 1/n
比如:n = 2 需要指针约1/2
n = 3 需要指针约2/3
n = 4 需要指针约3/4
2.2 高度为h的二叉树最大总节点数为 2^h-1
层数为K的节点数最多为2^(k-1),即2的(k-1)次方
2.3 满二叉树:节点个数都满了。高度为h,那么树的总节点数为 2^h - 1
完全二叉树:和满二叉树的区别 - 最后一层可以不满,但是必须满足顺序- 先有左节点才能有右节点。
严格二叉树:非叶子节点必须有左右。
斜二叉树(一根棍子):没有左节点的叫右斜二叉树;没有右节点的叫左斜二叉树
2.4 二叉树的存储:一般采用链表。
(1)数组存储:当成满二叉树处理,使用索引值表示节点。
数组存储的劣势:增删数据麻烦、接近满二叉树才能节省空间。
注:完全二叉树也比较适合用数组存储,能缓解浪费空间的问题。但是仍然要考虑场景是否需要频繁插入和删除。
(2)链表存储:节点增删容易,缺点是难以找到父节点,除非在每一个节点中增加一个父字段。
2.5 线索二叉树:n节点的二叉树会有n-1个链接,剩下n+1个空链接,可以让它分别指向前一个节点和后一个节点。
线索化的过程就是遍历二叉树的过程。在遍历的过程中,检查当前结点的左、右指针域是否为空,若为空,将它们改为指向前驱结点或后继结点的线索。
当线索化二叉树以后,遍历二叉树的时间复杂度虽然仍然是O(n),但常数因子要比算法小(速度更快),且不需要使用堆栈处理。
因此如果程序中所用的二叉树需要经常遍历或查找结点在遍历所得的线性序列的前驱和后继,则应采用线索链表作为二叉树的存储结构。
2.6 霍夫曼树(优化二叉树):根据数据出现的频率来构建二叉树,可以用于数据压缩。
3.树-二叉搜索树Binary Search Tree(排序二叉树、有序二叉树、二叉查找树)
ps:因为普通的二叉树遍历复杂度是O(n),和链表一样。所以要更规范的二叉树。
特点:左子树的所有节点均小于它的根节点
右子树的所有节点均小大它的根节点
左右子树也分别为二叉搜索树。(这就是重复性)
中序遍历:属于升序遍历
查询:O(log2 n)
插入:O(log2 n) - 查询比链表的O(n)有很大优势。
删除:O(log2 n)
查找到相关节点:如果是叶子节点,直接删除;
如果不是叶子节点,那么删除后,原来的位置从右边选择最小的节点来代替。
缺点:左边节点都被删完了,那么可能变成所有节点组成一条直线(相当于链表)。
4.树-平衡二叉树 (Balanced Binary Tree) - 又称AVL树(AVL是发明者的名字):性能保证的前提
背景:二叉树删除节点,极端情况下会变成一条线(相当于链表),这样查询复杂度和链表一样为O(n)
平衡二叉树是采用二分法思维把数据按规则组装成一个树形结构的数据,保证数据平衡的情况下查找数据的速度近于二分法查找
操作:每次插入或删除时,都要处理成平衡的。
(1)平衡因子:左子树比 - 右子树的高度:保持在[1 0 -1]之间。
叶子节点为0.
(2)旋转操作:新增或删除后调整
左旋:针对右右子树
右旋:针对左左子树
左右旋:针对左右子树
右左旋:针对右左子树
带子树的旋转:
不足:需要额外存储信息、且调整次数比较频繁。
5.树-近似平衡二叉树 - 红黑树: 能保证左右子树的高度差小于两倍。
5.1 背景:解决平衡二叉树调整次数太频繁的问题。
(1)根节点是黑色
(2)每个叶子节点都是黑的(NIL节点)
(3)不能有相邻的两个红色节点
(4)任意一个节点到每个叶子的所有路径都包含相同数目的黑色节点。
以上条件可以证明出:能保证左右子树的高度差小于两倍
5.2 优势:查找、删除、插入红黑树比AVL更快。
存储的内存的信息也比AVL少。
场景:用在高级语言的库中,比如set集合
6. B树(又称B-tree树或B-树)
一棵m阶的B树满足下列条件(m阶表示节点最大有m个子树)
树中每个结点至多有m个孩子。
除根结点和叶子结点外,其它每个结点至少有m/2个孩子。
根结点至少有2个孩子(如果B树只有一个结点除外)。
所有叶结点在同一层,B树的叶结点可以看成一种外部节点,不包含任何信息。
有k个关键字(关键字按递增次序排列)的非叶结点恰好有k+1个孩子。
所有叶节点具有相同的深度
关键字数量x要满足:
根节点 1 <= x <= m-1
非根节点 Math.ceil(m/2) - 1 <= x <= m-1
子树数量(孩子)要y要满足:
每个节点的子节点个数y = x + 1
根节点: 2<=子节点个数y <= m
非根节点:Math.ceil(m/2) <= y <= m
m = 3 时,2<= y <= 3 因此3阶B树称为(2,3)树或2-3树
m = 4 时,2<= y <= 4 因此4阶B树称为(2,4)树2-3-4树
m = 5 时,3<= y <= 5 因此5阶B树称为(3,5)树
m = 6 时,3<= y <= 6 因此6阶B树称为(3,6)树
注意:此图节点对应磁盘块可能不准确,对应的应该是数据库中定义节点的页的大小。
7. B+树
叶子节点是双向链表,数据从小到大排列。
所有数据都在叶子节点上,非叶子节点放索引。
一般叶子节点关键字个数设计为一页大小来设计。
(1)对于Mysql的非主键索引:先查询非主键索引的B+树得到主键ID,再根据主键ID查询主键的B+树。
(2)在 InnoDB 中, B+ 树索引的叶子节点存储的是数据。
在 MyISAM 中,B+ 树索引的叶子节点并不存储数据,而是存储数据的文件地址。
(3)MySQL中读取的数据基本单元是16K,一次I/O读取的数据是16KB的整数倍。
非子叶用来装索引,一个Page(16K)大约可以装1000个key;
叶子节点用来装数据,一个Page(16K)大约能装200条记录。
所以三层结构的树可以装:1000 *1000 * 200 = 2亿条记录 。 而16KB * 1000*1000 = 16GB数据。
(4)如果真的去磁盘中查找节点,那么一个节点一次磁盘I/O
把第一层和第二层索引全部放进内存,大小为16K * (1 +1000) = 16MB的内存。
所以,查询时只需要一次I/0(前提是基于主键的查询,如果基于非主键索引的方式, 那么需要先查找非主键索引树,再查找主键索引树)
注意:此图节点对应磁盘块可能不准确,对应的应该是数据库中定义节点的页的大小。Mysql数据库中页的大小是固定的,InnoDB 中页的默认大小是 16KB。
8. B*树
B*树是B+树的变种,相对于B+树他们的不同之处如下:
(1)首先是关键字个数限制问题,B+树初始化的关键字初始化个数是cei(m/2),b*树的初始化个数为(cei(2/3*m))
(2)B+树节点满时就会分裂,而B*树节点满时会检查兄弟节点是否满(因为每个节点都有指向兄弟的指针)
如果兄弟节点未满则向兄弟节点转移关键字,如果兄弟节点已满,则从当前节点和兄弟节点各拿出1/3的数据创建一个新的节点出来;
优势:在B+树的基础上因其初始化的容量变大,使得节点空间使用率更高,而又存有兄弟节点的指针,
可以向兄弟节点转移关键字的特性使得B*树额分解次数变得更少;
8. LSM树(Log Structured Merge Tree,结构化合并树)
LSM树和B+树相比,LSM树牺牲了部分读性能,用来大幅提高写性能。
可以用跳表实现LSM的内存部分
9. 散列表:单记录查询性能最好的。