Binary Search Tree 二叉搜索树
Definition(Binary Search Tree)
二叉搜索树可以是一棵空树,如果它是非空的树,那么其满足以下的条件:
-
所有的节点都有一个值(key),且任意两个不同的节点的值是不同的。
即所有的值都是唯一的
-
某节点的左子树的所有节点的值都小于该节点的值
-
某节点的右子树的所有节点的值都大于该节点的值
-
根节点的左子树和右子树都是二叉搜索树(递归)
带索引的二叉搜索树
即除了 左儿子,右儿子,值 之外,还加入一个索引leftSize
,用于记录该节点的左子树的节点数量 + 1
二叉搜索树的增删改查
查找
与当前节点不断比较,如果比当前节点的值小,则递归到该节点的左子树,如果比当前节点的值大,则递归到该节点的右子树,如果相同则返回当前节点。
private BinaryNode find(Comparable x, BinaryNode t) { |
时间复杂度:, 为二叉搜索树的高度
findMax() 和 findMin()
找到该二叉搜索树的最小值和最大值
- 最小值即不断朝左子树走到尽头即可
- 最大值即不断朝右子树走到尽头即可
findMin() 递归算法
private BinaryNode findMin(BinaryNode t) { |
findMax() 非递归算法
private BinaryNode findMax(BinaryNode t) { |
时间复杂度:, 为二叉搜索树的高度
插入
首先如果在树中找到了这个值,就不用插入了(值唯一)
与查找算法的思路类似,如果比当前节点大就往右子树走,比当前节点小就往左子树走,直到走的节点为空,那么就插入到该地方
private BinaryNode insert(Comparable, BinaryNode t) { |
删除
对要删的节点 ,有三种情况:
- 是树叶
- 有两棵非空子树
- 有一棵非空子树
private BinaryNode remove(Comparable x, BinaryNode t) { |
时间复杂度:, 为二叉搜索树的高度
二叉搜索树的高度
个节点的二叉搜索树,情况最好的情况下是一棵完全二叉树,树的高度为 ,最坏的情况是直接变成了一条线
AVL Tree(自平衡的二叉搜索树)
目的:为了降低二叉搜索树的高度,降低时间复杂度
Definition(AVL Tree)
-
AVL Tree是一棵二叉搜索树
-
所有节点满足:
可做可不做,做了可以加速某些算法的速度
Balance factor = 右子树高度 - 左子树高度( 三种情况)
个节点的 AVL Tree的高度为 ,查询算法相同,时间复杂度相同。
AVL的插入
如果插入到高度较高的子树,就旋转。
- 插入到外侧的情况:单旋转。子树与树根互换,其余子树按顺序排列
- 插入到内侧的情况:双旋转。
**注意:**调整之后,树的高度不变,调整不会影响到以A为根的子树以外的节点。
**如何调整:**回溯,通过不断判断某个子树的结构是不是符合AVL,如果符合就回到其父节点判断一棵更大的子树是不是符合。
或者这么说:插入一个新节点后,需要从插入位置沿通向根的路径回溯,检查各节点左右子树的高度差,如果发现某点高度不平衡则停止回溯。
注意分清外侧和内侧:只需要看两层,即是该节点的左子树的左子树(外侧),还是该节点的左子树的右子树(内侧)。还是该节点的右子树的左子树(内侧),或者是该节点的右子树的右子树(外侧)。以及注意当前检查节点的选择(回溯的节点)
时间复杂度:
Code
public class AVLTree { |
AVL树的删除
AVL树的删除和二叉搜索树的删除是相同的,只是在删除之后需要调整高度,调整高度的方法也是和上面的插入。
B-Trees
m-路搜索树
Definition
- 路搜索树可能是一颗空树,如果它不为空的话,那么它是一棵满足以下性质的树:
-
二叉搜索树扩展而来扩展搜索树(通过将外节点替换为空指针得到)。每个内部节点都最多有 个子节点和 个元素(被称为关键码)
-
每个有 个元素的节点都有 个子节点
-
对任意有 个关键码的节点来说:
路搜索树的节点个数(最多):
因为每个节点最多有 个关键码,则关键码个数最多有 个。
-
- 路搜索树如果高度为 ,那么它的关键码个数在 到 之间
-
- 路搜索树如果有 个节点,那么它的高度在 和 之间
B树
B树是一棵高度平衡的 -路搜索树
- 二叉搜索树 平衡的二叉搜索树(AVL树)
- 路搜索树 平衡的 叉搜索树(B-树)
Definition
- 树根至少要有两个子女(即关键码至少要有一个)
- 所有内节点(除了树根)至少要有 个节点
- 所有外节点都在同一层(level)
特殊的B树
- 二阶B树是一棵满二叉树
- 三阶B树,每个节点有1或2个关键码
性质
-
所有外节点都在同一层
-
外节点的数量 = 所有节点的关键码的数量 + 1
- :第 层的节点数量
- :
external\ node\ number = k_{h-1} + k_{h-2} + \dots + k_0 + 1 = n+ 1
B树的查找
相似于 路搜索树的查询算法
实际运用场景: 树不一定会放在内存中,有可能会放在磁盘(外存)上。每次从磁盘上载入 树的一个节点。(访问磁盘比遍历节点数据的时间还要长很多)
阶 树的一次查询,访问磁盘次数最多有 次( 为B树的树高)
B树的插入
- 叶节点的关键码个数 ,则直接插入到该叶节点的关键码的对应位置即可
- 叶节点的关键码个数 (已经满了),此时要将该节点分裂成两个节点,并将中间节点拉到父节点
插入操作:最多访问: 次磁盘( 为树高, 次为分裂次数,其中 )
- :查询的时候从磁盘读取节点
- :将分裂成两部分的节点写回到磁盘
- :写回新的节点(分裂到尽头有两种可能性:
- 如果树根也分裂了,那么会形成一个新的树根,该节点也需要写回到磁盘
- 如果树根没有分裂,那么最顶部的节点会插入一个关键码,也要写回到磁盘中
B树的删除
Cases(leaf):
- 删除的关键码所在的节点的关键码个数很多,可以直接删除
- 删除后该节点的关键码的个数 ,那么首先考虑从邻居借
如何从邻居借?
- 将左邻居的最大值拉到父节点,然后将对应的父节点的值拉到该节点
- 将右邻居的最小值拉到父节点,然后将对应的父节点的值拉到该节点
记得要将邻居被拉到父节点的关键码旁边的指针拉到空出来的节点
如果邻居节点也不够怎么办?
将两个节点做合并(树叶节点),然后将父节点对应的两个子节点所在指针中间的关键码拉下来,形成新的节点。
中间节点的删除
B树里关键码和指针的保存方法
线性表