BST
概念
二叉排序树(简称 BST),也称为二叉查找树。二叉排序树或者是一棵空树,或者是一棵具有下列特性的非空二叉树:
- 若左子树非空,则左子树上所有结点关键字值均小于根结点的关键字值。
- 若右子树非空,则右子树上所有结点关键字值均大于根结点的关键字值。
- 左、右子树本身也分别是一棵二叉排序树。
查找
二叉排序树的查找是从根结点开始,沿某一个分支逐层向下进行比
较的过程。若二叉排序树非空,将给定值与根结点的关键字比较,若相等,则查找成功;若不等,则当根结点的关键字大于给定关键字值时,在根结点的左子树中查找,否则在根结点的右子树中查找。这显然是一个递归的过程。
注:注意代码中
指针用于存储查找结点的双亲。
递归效率低于非递归。
BSTNode *BST_Search(BiTree T,ElemType key,BSTNode *&p)
{//查找函数返回指向关键字值为key的结点指针,若不存在,返回NULL
p = NULL; //p指向被查找结点的双亲,用于插入和删除操作中
while(T != NULL && key != T->data)
{
p = T; //
if(key < T->data) T = T->lchild;
else T = T->rchild;
}
return T;
}
插入
插入结点的过程:若原二叉排序树为空,则直接插入结点;否则,若关键字小于根结点关键字,则插入到左子树中,若关键字
大于根结点关键字,则插入到右子树中。
注:插入的结点一定是某个叶节点。
int BST_Insert(BiTree &T,KeyType k)
{//在二叉排序树T中插入一个关键字为k的结点
if(T == NULL) //原树为空,新插入的记录为根结点
{
T = (BiTree)malloc(sizeof(BSTNode));
T->key = k;
T->lchild = T->rchild = NULL;
return 1; //返回1,表示成功
}else if(k == T->key) //树中存在相同关键字的结点
return 0;
else if(k < T->key) //插入到T的左子树中
return BST_Insert(T->lchild,k);
else //插入到T的左子树中
return BST_Insert(T->rchild,k);
}
构造
构造一棵二叉排序树就是依次输入数据元素,并将它们插入到二叉排序树中的适当位置上的过程。具体过程是,每读入一个元素,就建立一个新结点,若二叉排序树非空,则将新结点的值与根结点的值比较,如果小于根结点的值,则插入到左子树中,否则插入到右子树中;若二叉排序树为空,则新结点作为二叉排序树的根结点。
void Create_BST(BiTree &T,KeyType str[],int n)
{//用关键字数组str[]建立一个二叉排序树
T = NULL; //初始化bt为空树
int i = 0;
while(i < n) //依次将每个元素插入
{
BST_Insert(T,str[i]);
i++;
}
}
删除
在二叉排序树删除一个结点时,不能把以该结点为根的子树上的结点都删除,必须先把被删除结点从存储二叉排序树的链表上摘下,将因删除结点而断开的二叉链表重新链接起来,同时确保二叉排序树的性质不会丢失。
删除操作的实现过程按3种情况来处理:
- 如果被删除结点
是叶结点,则直接删除,不会破坏二叉排序树的性质;
- 若结点
只有一颗左子树或右子树,则让
的子树成为
父结点的子树,替代
的位置;
- 若结点
有左、右两颗子树,则令
的直接后继(或直接前驱)替代
,然后从二叉排序树中删去这个直接后继(或直接前驱),这样就转换成了第一种第二种情况。
AVL
概念
为了避免树的高度增长过快,降低二叉排序树的性能,我们规定在插入和删除二叉树结点时,要保证任意结点的左、右子树高度差的绝对值不超过1,将这样的二叉树称为平衡二叉树,简称平衡树(AVL树)。定义结点左子树与右子树的高度差为该结点的平衡因子,则平衡二叉树结点的平衡因子的值只可能是-1、0或1。
插入
基本思想:每当在二叉排序树中插入(或删除)一个结点时,首先检查其插入路径上的结点是否因为此次操作而导致了不平衡。如果导致了不平衡,则先找到插入路径上离插入点最近的平衡因子绝对值大于1的结点A,在对以A为根的子树,在保持二叉排序树特性的前提下,调整各结点的位置关系,使之重新达到平衡。
平衡二叉树的插入过程前半部分与二叉排序树相同,但是在新结点插入后,如果造成了查找路径上某个结点不再平衡,需要做出相应的调整。一般可将失去平衡后进行调整的规律归纳为下列4种情况:
LL平衡旋转(右单旋转)
由于在结点A的左孩子(L)的左子树(L)上插入了新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要一次向右的旋转操作。将A的左孩子B向右上旋转代替A成为根结点,将A结点向右下旋转成为B的右子树的根结点,而B的原右子树则作为A结点的左子树。
RR平衡旋转(左单旋转)
由于在结点A的右孩子®的右子树®上插入了新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要一次向左的旋转操作。将A的右孩子B向左上旋转代替A成为根结点,将A结点向左下旋转成为B的左子树的根结点,而B的原左子树则作为A结点的右子树。
LR平衡旋转(先左后右双旋转)
由于在A的左孩子(L)的右子树®上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先左旋转后右旋转。先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置,然后再把该C结点向右上旋转提升到A结点的位置。
RL平衡旋(转先右后左双旋转)
由于在A的右孩子®的左子树(L)上插入新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先右旋转后左旋转。先将A结点的右孩子B的左子树的根结点C向右上旋转提升到B结点的位置,然后再把该C结点向左上旋转提升到A结点的位置。
删除
平衡二叉树的删除和二叉排序树删除一样,分为3种情况。
- 如果被删除结点
是叶结点,则直接删除,不会破坏二叉排序树的性质;
- 若结点
只有一颗左子树或右子树,则让
的子树成为
父结点的子树,替代
的位置;
- 若结点
有左、右两颗子树,则令
的直接后继(或直接前驱)替代
,然后从二叉排序树中删去这个直接后继(或直接前驱),这样就转换成了第一种第二种情况。
注:和二叉排序树不同的是,在删除后需要调整二叉树使得符合平衡二叉树的定义。
和插入操作一样,删除操作也是递归查找,然后删除,删除之后,该节点A要向父节点回溯,告诉父节点B我变矮了
(因为删除了),父节点B此时要判断自己是否也变矮了,如果删除的节点是自己的左子树中的节点(右子树同理,这里只讨论左子树情况,右子树请看代码),就要分三种情况讨论:
- 删除前父节点的左右子树高度一致;而现在左子树告诉我,左子树变矮了,则删除后需要将父节点的高度设置为右子树的高度,同时可知父节点B的父节点C回溯的时候,B的父节点C就会当做啥都没发生。
- 删除前父节点的左子树比右子树高一层,而现在左子树告诉我,左子树变矮了,则需要将父节点B设置为左右子树相等,同时可知父节点B的高度也变矮了,于是在往B的父节点C回溯的时候,C也要分
三种情况
讨论。 - 删除前父节点的左子树比右子树矮一层,而现在左子树告诉我,左子树变矮了,则需要对父节点B进行右平衡处理,而这里又要分为
两种
情况讨论来判断,右平衡处理完成后,需要判断父节点B的父节点C的左子树是否变矮:
- 父节点B(右平衡处理之前)的右子树ed左右子树等高,那么这种情况,B的父节点C的左子树不变矮。
- B的父节点C的左子树会变矮。
RBT
概念
红黑树事是一种特殊的平衡二叉树,其平衡的是从一个结点到该结点的子孙结点的所有路径上包含相同数量的黑结点。其满足以下特点:
- 每个结点或者是黑色,或者是红色。
- 根结点是黑色。
- 每个叶子结点是黑色(注意:这里叶子结点,是指为空的叶子结点!)。
- 如果一个结点是红色的,则它的子结点必须是黑色的。
- 从一个节点到该结点的子孙节点的所有路径上包含相同数目的黑结点。
插入
删除
资料
《2019 王道 数据结构》
【数据结构】平衡二叉树的插入、删除