二叉搜索树
又名二叉查找树,英文缩写为BST。
特点:
- 若它的左子树不空,则左子树上的所有节点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上的所有结点的值均大于它的根节点的值;
- 它的左、右子树也分别为二叉搜索树
如图所示为一个二叉搜索树:
优势:
- 有序性:若以“中序”遍历二叉树,则会产生一个所有结点关键字值的递增序列,如图所示的二叉搜索树,中序遍历的结果为:3、12、24、37、45、53、61、78、90、100;
- 高效性:插入和查找的高效性,其算法的时间复杂度为O(h),h表示树的高度,这是其它数据结构无法达到的。如:有序线性表虽然有序,但是插入算法的时间复杂度为O(n);堆的插入算法虽然时间复杂度为O(log2n),但是堆不具有有序性。
缺点:
不平衡性:二叉搜索树出现不平衡的情况,甚至出现极端情况——单链,如图,此时h=n,二叉搜索树就失去了它的效率优势,此时如果想要客服这种不平衡性,传统的优化方式为:平衡树、红-黑树
算法:
- 插入算法:
- 从根节点出发,遇键值较大向左,遇键值较小向右,直到尾端,即为插入点
- 删除算法:
-
如果删除节点只有一个子节点,那么直接将子节点连接至父节点即可
-
如果删除节点有不止一个子节点,那么用右子树中的最小值取而代之即可。找到最小值很简单,一直向左走走到尽头即可
-
平衡树
全称自平衡二叉搜索树,英文缩写为AVL。
AVL tree是一个加上了额外平衡条件的二叉搜索树,其平衡条件的建立是为了确保整棵树的深度为O(logN),。
平衡条件:直观上最佳平衡条件是每个节点的左右子树有着同样的高度,但这未免太过苛刻,我们很难插入新元素而保持这样的平衡条件。AVL树退而求其次,要求任何节点的左右子树高度相差最多1,这是一种较弱的条件,但是也可以保证对数深度的平衡状态。
自平衡算法:
发生条件:当元素插入且插入成功之后
新元素的位置无非下面四种:
- 插入点位于X的左子节点的左子树 ---- 左左
- 插入点位于X的左子节点的右子树 ---- 左右
- 插入点位于X的右子节点的左子树 ---- 右左
- 插入点位于X的右子节点的右子树 ---- 右右
其中X表示平衡状态被破坏之各节点中最深的那一个,由于节点最多有两个子节点,而平衡被打破意味着X的左右节点的高度差为2
- 情况1、4可以视为外侧插入,使用单旋转操作即可调整至平衡
如上图,左侧为旋转前,右侧为旋转后
为了调整平衡状态,我们希望将A提高一层,将C下降一层,也就是将看k1向上提高,k2向下下滑。
根据二叉搜索树的特性,我们可以知道可k2>k1,所以k2必须成为新树形中k1的右子节点。
根据二叉搜索树的特性,我们直到B子树处于k1与k2之间,那么新树形中的B子树必须落在K2的左侧。
- 情况2、3可以视为内侧插入,使用双旋转操作即可调整至平衡
如上图,左侧为旋转前,右侧为旋转后
对于内测插入,单纯的进行一次单旋转是绝对不行的,因为旋转之后还是不平衡的,不妨可以自己画图试下。
对于内测插入的情况,唯一的可能就是将k2作为新的根节点,这使得(根据二叉搜索树的规则)k1必然成为k2的左子节点,k3必然成为k2的右子节点。
双旋转也并不是新鲜的算法,就是简单的进行两次单旋转即可。
双旋转具体流程:
- k2与k1进行一次单旋转,k1就可以作为k2的左子节点出现
- k2与k3进行一次单旋转,k3就可以作为k2的右子节点出现
红黑树
英文缩写为RB-tree。
红黑树的规则:
- 每个节点不是红色就是黑色
- 根节点为黑色
- 如果节点为红,其子节点必须为黑
- 在任一节点至NULL(树尾端)的任意路径,黑节点数必须相同
根据规则我们可以分析出来以下几个执行的必然情况:
- 根据规则4,我们插入的元素必然是红色
- 元素找到插入点插入之后,如果插入节点的父节点为红,那么就不符合规则三,就必须做出改变
- 对于NULL元素,我们都视为黑色,这样我们就需要担心违反规则3.因为每一个路径到树尾端都会有一个尾端的NULL元素,所以对规则4黑色节点数相同不影响
如下图,向红黑树中插入3、8、35、75四个元素,因为新增节点必为红色,可以看出这四个元素不符合红黑树的规则,我们对这四个元素一一分析。
分析之前我们先明确一下各节点的命名:
X:新节点
P:新节点的父节点
G:新节点的父节点的父节点,即祖父节点
S:新节点的祖父节点除父节点外的另一个子节点,即伯父节点
GG:新节点的祖父节点的父节点,即曾祖父节点
-
状态1:S为黑且为外侧插入(对应插入的元素3)
对P、G做一个单旋转,并更改P、G的颜色即可
2. 状态2:S为黑且为内侧插入(对应插入的元素8)
- 对P、X做一次单旋转,并改变G、X的颜色
- 对X、G做一次单旋转,无需更改颜色
- 状态3:S为红且为外侧插入(对应插入的元素75)
- 对P、G做一次单旋转,并改变X的颜色
- 如果GG是黑色,一切搞定,如果GG是红色,那就有些麻烦,我们看状态4
- 状态4:S为红且为外侧插入(对应插入的元素35)
- 根据状态3进行相应的操作
- 如果GG为红色,还需要继续根据情况继续往上进行操作,直到不出现父子同为红色为止
我们无需分析就可以想想得到,如果一直向上层结构发展,是非常消耗时效的,因此我们可以实施一个自上而下的程序。
自上而下的程序逻辑:
在新插入元素X时,会有一个从根节点向下搜寻插入点的过程,在这个过程中,如果发现某节点N的两个子节点为红色,那么就把X改为红色,并把两个子节点改为黑色。