简叙二叉树

二叉树的最大优点的就是查找效率高,在二叉排序树中查找一个结点的平均时间复杂度是O(log₂N);

在《讲透学烂二叉树(二):树与二叉/搜索/平衡等树的概念与特征》提到

二叉排序树是为了实现动态查找而设计的数据结构,它是面向查找操作的。对于目标节点的查找过程类似与有序数组的二分查找,在二叉排序树中查找一个结点的平均时间复杂度是O(log n)

设节点数目为n,树的深度为h,假设树的每层都被塞满(第L层有2^L个节点,层数从1开始),则根据等比数列公式可得h=log(n+1)。即最好的情况下,二叉查找树的查找效率为O(log n)。当二叉查找树退化为单链表时,比如,只有右子树的情况,如下图所示,此时查找效率为O(n)。

总之,二叉查找树越是“矮胖”,也就是每层尽可能地被“塞满”(每个父节点均有两个子节点)时,查找效率越高。

  • 每层都被塞满时,查找效率最高,最高为O(log n)。

  • 当二叉查找树退化为单链表时,查找效率最低,最低为O(n)。

为了解决二叉查找树退化为单链表时查找效率低下的问题,引入了平衡二叉树(AVL)。

平衡二叉树的基本操作

  • 插入:插入节点,让树平衡

  • 删除:删除节点,让树平衡

  • 旋转:旋转操作,它可以使得某一个结点提升到他父亲的位置而不破坏平衡二叉树的性质。

  • 伸展:就是把当前节点,移动至树根,或者说,把当前节点变成根节点。

更多了解旋转与伸展相关内容,推荐阅读《伸展树(Splay Tree)进阶 - 从原理到实现 》,这里我们首先将二叉树

平衡二叉树

平衡二叉树(Balanced Binary Tree)又被称为AVL树(有别于AVL算法)

在AVL中任何节点的两个儿子子树的高度最大差别为1,所以它也被称为高度平衡树,n个结点的AVL树最大深度约1.44log2n。查找、插入和删除在平均和最坏情况下都是O(logn)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。这个方案很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。但是频繁旋转会使插入和删除牺牲掉O(logN)左右的时间,不过相对二叉查找树来说,时间上稳定了很多。

平衡二叉树的常用算法有红黑树、AVL树等。在平衡二叉搜索树中,我们可以看到,其高度一般都良好地维持在O(log2n),大大降低了操作的时间复杂度。

最小二叉平衡树的节点的公式如下:

  F(n)=F(n-1)+F(n-2)+1

这个类似于一个递归的数列,可以参考Fibonacci数列,1是根节点,F(n-1)是左子树的节点数量,F(n-2)是右子树的节点数量。

平衡因子

某结点的左子树与右子树的高度或深度差即为该结点的平衡因子(BF,Balance Factor),平衡二叉树(AVL树)上所有结点的平衡因子只可能是 -1,0 或 1

下图中就标注了所有节点的平衡因子

(平衡因子计算时左子树 - 右子树 或 右子树 - 左子树 都可以,因为判断树是否平衡的条件是:每个结点的左右子树的高度之差的绝对值不超过1,只不过判断失衡以后还要判断是哪一种失衡,这就需要根据情况来选择是左-右还是右-左了)

平衡二叉树查找节点

在 AVL树 中查找与在 二叉查找树 中查找完全一样,因为AVL树总是保持平衡的,树的结构不会由于查询而改变,这里就不再赘述了

平衡二叉树插入节点

先梳理一下步骤

先来实现搜索最低失衡节点,搜索最低失衡节点是从新插入的节点(也就是叶子节点)

往上搜索(也可以说成从新增结点开始向根部回溯),搜索到的第一个平衡因子>1(|左子树高度-右子树高度|>1)的节点,作为最低失衡节点,因为是从新插入的节点往上搜索,二叉树的搜索是单向的(结构体成员中只有左右子树),单独使用一个函数来实现逆向搜索实现起来并不方便,这里就把搜索最低失衡节点的操作放到递归实现的插入操作中

搞清楚了各个节点的高度,平衡因子的计算也比较方便了,下面就是AVL树的核心操作“旋转”,不同的失衡情况有不同的旋转方式,一共有四种节点失衡情况,如下图

二叉树不平衡的四种情况

不同失衡情况下的示例二叉树,如下图(读者可能会发现“最低失衡节点的左子树的左子树还有非空节点”这个判断依据,对第二组图适用,但对于第一组图不太合适)

AVL树单旋转和双旋转

单旋转

单旋转是针对于左左和右右这两种情况的解决方案,这两种情况是对称的,只要解决了左左这种情况,右右就很好办了。图3是左左情况的解决方案,节点k2不满足平衡特性,因为它的左子树k1比右子树Z深2层,而且k1子树中,更深的一层的是k1的左子树X子树,所以属于左左情况。

为使树恢复平衡,我们把k2变成这棵树的根节点,因为k2大于k1,把k2置于k1的右子树上,而原本在k1右子树的Y大于k1,小于k2,就把Y置于k2的左子树上,这样既满足了二叉查找树的性质,又满足了平衡二叉树的性质。

这样的操作只需要一部分指针改变,结果我们得到另外一颗二叉查找树,它是一棵AVL树,因为X向上一移动了一层,Y还停留在原来的层面上,Z向下移动了一层。整棵树的新高度和之前没有在左子树上插入的高度相同,插入操作使得X高度长高了。因此,由于这颗子树高度没有变化,所以通往根节点的路径就不需要继续旋转了。

双旋转

对于左右和右左这两种情况,单旋转不能使它达到一个平衡状态,要经过两次旋转。双旋转是针对于这两种情况的解决方案,同样的,这样两种情况也是对称的,只要解决了左右这种情况,右左就很好办了。图4是左右情况的解决方案,节点k3不满足平衡特性,因为它的左子树k1比右子树Z深2层,而且k1子树中,更深的一层的是k1的右子树k2子树,所以属于左右情况。

为使树恢复平衡,我们需要进行两步,第一步,把k1作为根,进行一次右右旋转,旋转之后就变成了左左情况,所以第二步再进行一次左左旋转,最后得到了一棵以k2为根的平衡二叉树。

首先要确定中心结点,即最小失衡结点A,其平衡因子的绝对值为2,主要有四种不平衡的情况:

(1)在A的左儿子B的左子树插入,又称为LL—右旋转;

(2)在A的左儿子C的右子树插入P,又称为LR—左右旋转;

(3)在A的右儿子C的左子树插入P,又称为RL—右左旋转;

(4)在A的右儿子B的右子树插入,又称为RR—左旋转。

要记住两个重要节点,一个是失衡结点,另一个是失衡结点的儿子,该儿子在失衡路径上,旋转操作则是依据失衡结点的儿子为中心,对失衡结点进行下移动。在这四种失衡情况中(1)和(4)两种情况是对称的,这两种情况的旋转算法是一致的,只需要经过一次旋转就可以达到目标,我们称之为单旋转,(2)和(3)两种情况也是对称的,这两种情况的旋转算法也是一致的,需要进行两次旋转,我们称之为双旋转。

二叉树左右旋转讲解

在进行旋转操作时,首先要找到最小失衡结点,判断失衡的类型,然后选择旋转的类型,如何判断呢?根据上面的图片中的结点A,BF为2确定为左儿子左边L,根据左儿子的BF为-1,则确定为R,此时属于不平衡情况(2),使用双旋转,下面详细介绍单旋转和双旋转的四种旋转方式。

1、LL右旋转

P下移,占据C的右儿子空穴,C的右儿子称为P的左儿子

2、RR左旋转

P下移,占据C的左儿子空穴,C的左儿子作为P的右儿子。

3、LR左右旋转

双旋转分为两步:左旋转,以P的儿子C作为失衡结点,Q的右儿子q,Q下移,占据q的左儿子,q的左儿子左儿子作为Q的右儿子,q作为P的左儿子。

右旋转,P下移,作为p的右儿子,q的右儿子作为P的左儿子。

4、RL右左旋转

右旋转,P的右儿子C作为新的失衡结点Q,Q的左儿子q,Q下移,作为q的右儿子,q的右儿子作为Q的左儿子,q作为P的右儿子。

左旋转,P下移,占据q的左儿子,q的左儿子作为P的右儿子。

先梳理一下步骤

平衡树,旋转代码实现

//AVL树节点信息
template<class T>
class TreeNode
{public:TreeNode():lson(NULL),rson(NULL),freq(1),hgt(0){}T data;//值int hgt;//高度unsigned int freq;//频率TreeNode* lson;//指向左儿子的地址TreeNode* rson;//指向右儿子的地址
};
//AVL树类的属性和方法声明
template<class T>
class AVLTree
{private:TreeNode<T>* root;//根节点void insertpri(TreeNode<T>* &node,T x);//插入TreeNode<T>* findpri(TreeNode<T>* node,T x);//查找void insubtree(TreeNode<T>* node);//中序遍历void Deletepri(TreeNode<T>* &node,T x);//删除int height(TreeNode<T>* node);//求树的高度void SingRotateLeft(TreeNode<T>* &k2);//左左情况下的旋转void SingRotateRight(TreeNode<T>* &k2);//右右情况下的旋转void DoubleRotateLR(TreeNode<T>* &k3);//左右情况下的旋转void DoubleRotateRL(TreeNode<T>* &k3);//右左情况下的旋转int Max(int cmpa,int cmpb);//求最大值public:AVLTree():root(NULL){}void insert(T x);//插入接口TreeNode<T>* find(T x);//查找接口void Delete(T x);//删除接口void traversal();//遍历接口};
//计算节点的高度
template<class T>
int AVLTree<T>::height(TreeNode<T>* node)
{if(node!=NULL)return node->hgt;return -1;
}
//求最大值
template<class T>
int AVLTree<T>::Max(int cmpa,int cmpb)
{return cmpa>cmpb?cmpa:cmpb;
}
//左左情况下的旋转
template<class T>
void AVLTree<T>::SingRotateLeft(TreeNode<T>* &k2)
{TreeNode<T>* k1;k1=k2->lson;k2->lson=k1->rson;k1->rson=k2;k2->hgt=Max(height(k2->lson),height(k2->rson))+1;k1->hgt=Max(height(k1->lson),k2->hgt)+1;
}
//右右情况下的旋转
template<class T>
void AVLTree<T>::SingRotateRight(TreeNode<T>* &k2)
{TreeNode<T>* k1;k1=k2->rson;k2->rson=k1->lson;k1->lson=k2;k2->hgt=Max(height(k2->lson),height(k2->rson))+1;k1->hgt=Max(height(k1->rson),k2->hgt)+1;
}
//左右情况的旋转
template<class T>
void AVLTree<T>::DoubleRotateLR(TreeNode<T>* &k3)
{SingRotateRight(k3->lson);SingRotateLeft(k3);
}
//右左情况的旋转
template<class T>
void AVLTree<T>::DoubleRotateRL(TreeNode<T>* &k3)
{SingRotateLeft(k3->rson);SingRotateRight(k3);
}
//插入
template<class T>
void AVLTree<T>::insertpri(TreeNode<T>* &node,T x)
{if(node==NULL)//如果节点为空,就在此节点处加入x信息{node=new TreeNode<T>();node->data=x;return;}if(node->data>x)//如果x小于节点的值,就继续在节点的左子树中插入x{insertpri(node->lson,x);if(2==height(node->lson)-height(node->rson))if(x<node->lson->data)SingRotateLeft(node);elseDoubleRotateLR(node);}else if(node->data<x)//如果x大于节点的值,就继续在节点的右子树中插入x{insertpri(node->rson,x);if(2==height(node->rson)-height(node->lson))//如果高度之差为2的话就失去了平衡,需要旋转if(x>node->rson->data)SingRotateRight(node);elseDoubleRotateRL(node);}else ++(node->freq);//如果相等,就把频率加1node->hgt=Max(height(node->lson),height(node->rson));
}
//插入接口
template<class T>
void AVLTree<T>::insert(T x)
{insertpri(root,x);
}
//查找
template<class T>
TreeNode<T>* AVLTree<T>::findpri(TreeNode<T>* node,T x)
{if(node==NULL)//如果节点为空说明没找到,返回NULL{return NULL;}if(node->data>x)//如果x小于节点的值,就继续在节点的左子树中查找x{return findpri(node->lson,x);}else if(node->data<x)//如果x大于节点的值,就继续在节点的左子树中查找x{return findpri(node->rson,x);}else return node;//如果相等,就找到了此节点
}
//查找接口
template<class T>
TreeNode<T>* AVLTree<T>::find(T x)
{return findpri(root,x);
}
//删除
template<class T>
void AVLTree<T>::Deletepri(TreeNode<T>* &node,T x)
{if(node==NULL) return ;//没有找到值是x的节点if(x < node->data){Deletepri(node->lson,x);//如果x小于节点的值,就继续在节点的左子树中删除xif(2==height(node->rson)-height(node->lson))if(node->rson->lson!=NULL&&(height(node->rson->lson)>height(node->rson->rson)) )DoubleRotateRL(node);elseSingRotateRight(node);}else if(x > node->data){Deletepri(node->rson,x);//如果x大于节点的值,就继续在节点的右子树中删除xif(2==height(node->lson)-height(node->rson))if(node->lson->rson!=NULL&& (height(node->lson->rson)>height(node->lson->lson) ))DoubleRotateLR(node);elseSingRotateLeft(node);}else//如果相等,此节点就是要删除的节点{if(node->lson&&node->rson)//此节点有两个儿子{TreeNode<T>* temp=node->rson;//temp指向节点的右儿子while(temp->lson!=NULL) temp=temp->lson;//找到右子树中值最小的节点//把右子树中最小节点的值赋值给本节点node->data=temp->data;node->freq=temp->freq;Deletepri(node->rson,temp->data);//删除右子树中最小值的节点if(2==height(node->lson)-height(node->rson)){if(node->lson->rson!=NULL&& (height(node->lson->rson)>height(node->lson->lson) ))DoubleRotateLR(node);elseSingRotateLeft(node);}}else//此节点有1个或0个儿子{TreeNode<T>* temp=node;if(node->lson==NULL)//有右儿子或者没有儿子node=node->rson;else if(node->rson==NULL)//有左儿子node=node->lson;delete(temp);temp=NULL;}}if(node==NULL) return;node->hgt=Max(height(node->lson),height(node->rson))+1;return;
}
//删除接口
template<class T>
void AVLTree<T>::Delete(T x)
{Deletepri(root,x);
}
//中序遍历函数
template<class T>
void AVLTree<T>::insubtree(TreeNode<T>* node)
{if(node==NULL) return;insubtree(node->lson);//先遍历左子树cout<<node->data<<" ";//输出根节点insubtree(node->rson);//再遍历右子树
}
//中序遍历接口
template<class T>
void AVLTree<T>::traversal()
{insubtree(root);
}

JavaScript代码,暂未实现

平衡二叉搜索树的分类

平衡的二叉搜索树一般分为两类:

  严格维护平衡的,树的高度控制在log2n,使得每次操作都能使得时间复杂度控制在O(logn),例如AVL树,红黑树;

  非严格维护平衡的,不能保证每次操作都控制在O(logn),但是每次操作均摊时间复杂度为O(logn),例如伸展树。

平衡二叉树之红黑树

红黑树是一种自平衡二叉查找树,又称之为"对称二叉B树"。设平衡二叉树的深度为N,则N%2=0结点为黑色,N%2=1结点为红色。这些约束确保了红黑树的关键特性: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。

红黑树的自平衡操作:

因为每一个红黑树也是一个特化的二叉查找树,因此红黑树上的只读操作与普通二叉查找树上的只读操作相同。然而,在红黑树上进行插入操作和删除操作会导致不再符合红黑树的性质。恢复红黑树的性质需要少量(O(logn))的颜色变更(实际是非常快速的)和不超过三次树旋转(对于插入操作是两次)。虽然插入和删除很复杂,但操作时间仍可以保持为O(logn) 次。

我们首先以二叉查找树的方法增加节点并标记它为红色。如果设为黑色,就会导致根到叶子的路径上有一条路上,多一个额外的黑节点,这个是很难调整的(违背性质5)。但是设为红色节点后,可能会导致出现两个连续红色节点的冲突,那么可以通过颜色调换(color flips)和树旋转来调整。下面要进行什么操作取决于其他临近节点的颜色。同人类的家族树中一样,我们将使用术语叔父节点来指一个节点的父节点的兄弟节点。注意:

性质1和性质3总是保持着。

性质4只在增加红色节点、重绘黑色节点为红色,或做旋转时受到威胁。

性质5只在增加黑色节点、重绘红色节点为黑色,或做旋转时受到威胁。

红黑树的插入操作:

假设,将要插入的节点标为N,N的父节点标为P,N的祖父节点标为G,N的叔父节点标为U。在图中展示的任何颜色要么是由它所处情形这些所作的假定,要么是假定所暗含的。

  • 情形1: 该树为空树,直接插入根结点的位置,违反性质1,把节点颜色有红改为黑即可。

  • 情形2: 插入节点N的父节点P为黑色,不违反任何性质,无需做任何修改。在这种情形下,树仍是有效的。性质5也未受到威胁,尽管新节点N有两个黑色叶子子节点;但由于新节点N是红色,通过它的每个子节点的路径就都有同通过它所取代的黑色的叶子的路径同样数目的黑色节点,所以依然满足这个性质。

    注: 情形1很简单,情形2中P为黑色,一切安然无事,但P为红就不一样了,下边是P为红的各种情况,也是真正难懂的地方。

  • 情形3: 如果父节点P和叔父节点U二者都是红色,(此时新插入节点N做为P的左子节点或右子节点都属于情形3,这里右图仅显示N做为P左子的情形)则我们可以将它们两个重绘为黑色并重绘祖父节点G为红色(用来保持性质4)。现在我们的新节点N有了一个黑色的父节点P。因为通过父节点P或叔父节点U的任何路径都必定通过祖父节点G,在这些路径上的黑节点数目没有改变。但是,红色的祖父节点G的父节点也有可能是红色的,这就违反了性质4。为了解决这个问题,我们在祖父节点G上递归地进行上述情形的整个过程(把G当成是新加入的节点进行各种情形的检查)。比如,G为根节点,那我们就直接将G变为黑色(情形1);如果G不是根节点,而它的父节点为黑色,那符合所有的性质,直接插入即可(情形2);如果G不是根节点,而它的父节点为红色,则递归上述过程(情形3)。

  • 情形4: 父节点P是红色而叔父节点U是黑色或缺少,新节点N是其父节点的左子节点,而父节点P又是其父节点G的左子节点。在这种情形下,我们进行针对祖父节点G的一次右旋转; 在旋转产生的树中,以前的父节点P现在是新节点N和以前的祖父节点G的父节点。我们知道以前的祖父节点G是黑色,否则父节点P就不可能是红色(如果P和G都是红色就违反了性质4,所以G必须是黑色)。我们切换以前的父节点P和祖父节点G的颜色,结果的树满足性质4。性质5也仍然保持满足,因为通过这三个节点中任何一个的所有路径以前都通过祖父节点G,现在它们都通过以前的父节点P。在各自的情形下,这都是三个节点中唯一的黑色节点。

  • 情形5: 父节点P是红色而叔父节点U是黑色或缺少,并且新节点N是其父节点P的右子节点而父节点P又是其父节点的左子节点。在这种情形下,我们进行一次左旋转调换新节点和其父节点的角色; 接着,我们按情形4处理以前的父节点P以解决仍然失效的性质4。注意这个改变会导致某些路径通过它们以前不通过的新节点N(比如图中1号叶子节点)或不通过节点P(比如图中3号叶子节点),但由于这两个节点都是红色的,所以性质5仍有效。

注: 插入实际上是原地算法,因为上述所有调用都使用了尾部递归。

红黑树的删除操作:

如果需要删除的节点有两个儿子,那么问题可以被转化成删除另一个只有一个儿子的节点的问题。对于二叉查找树,在删除带有两个非叶子儿子的节点的时候,我们找到要么在它的左子树中的最大元素、要么在它的右子树中的最小元素,并把它的值转移到要删除的节点中。我们接着删除我们从中复制出值的那个节点,它必定有少于两个非叶子的儿子。因为只是复制了一个值,不违反任何性质,这就把问题简化为如何删除最多有一个儿子的节点的问题。它不关心这个节点是最初要删除的节点还是我们从中复制出值的那个节点。

我们只需要讨论删除只有一个儿子的节点(如果它两个儿子都为空,即均为叶子,我们任意将其中一个看作它的儿子)。如果我们删除一个红色节点(此时该节点的儿子将都为叶子节点),它的父亲和儿子一定是黑色的。所以我们可以简单的用它的黑色儿子替换它,并不会破坏性质3和性质4。通过被删除节点的所有路径只是少了一个红色节点,这样可以继续保证性质5。另一种简单情况是在被删除节点是黑色而它的儿子是红色的时候。如果只是去除这个黑色节点,用它的红色儿子顶替上来的话,会破坏性质5,但是如果我们重绘它的儿子为黑色,则曾经通过它的所有路径将通过它的黑色儿子,这样可以继续保持性质5。

需要进一步讨论的是在要删除的节点和它的儿子二者都是黑色的时候,这是一种复杂的情况。我们首先把要删除的节点替换为它的儿子。出于方便,称呼这个儿子为N(在新的位置上),称呼它的兄弟(它父亲的另一个儿子)为S。在下面的示意图中,我们还是使用P称呼N的父亲,SL称呼S的左儿子,SR称呼S的右儿子。

如果N和它初始的父亲是黑色,则删除它的父亲导致通过N的路径都比不通过它的路径少了一个黑色节点。因为这违反了性质5,树需要被重新平衡。有几种情形需要考虑:

  情形1: N是新的根。在这种情形下,我们就做完了。我们从所有路径去除了一个黑色节点,而新根是黑色的,所以性质都保持着。

  注意: 在情形2、5和6下,我们假定N是它父亲的左儿子。如果它是右儿子,则在这些情形下的左和右应当对调。

  情形2: S是红色。在这种情形下我们在N的父亲上做左旋转,把红色兄弟转换成N的祖父,我们接着对调N的父亲和祖父的颜色。完成这两个操作后,尽管所有路径上黑色节点的数目没有改变,但现在N有了一个黑色的兄弟和一个红色的父亲(它的新兄弟是黑色因为它是红色S的一个儿子),所以我们可以接下去按情形4、情形5或情形6来处理。

  情形3: N的父亲、S和S的儿子都是黑色的。在这种情形下,我们简单的重绘S为红色。结果是通过S的所有路径,它们就是以前不通过N的那些路径,都少了一个黑色节点。因为删除N的初始的父亲使通过N的所有路径少了一个黑色节点,这使事情都平衡了起来。但是,通过P的所有路径现在比不通过P的路径少了一个黑色节点,所以仍然违反性质5。要修正这个问题,我们要从情形1开始,在P上做重新平衡处理。

  情形4: S和S的儿子都是黑色,但是N的父亲是红色。在这种情形下,我们简单的交换N的兄弟和父亲的颜色。这不影响不通过N的路径的黑色节点的数目,但是它在通过N的路径上对黑色节点数目增加了一,添补了在这些路径上删除的黑色节点。

  情形5: S是黑色,S的左儿子是红色,S的右儿子是黑色,而N是它父亲的左儿子。在这种情形下我们在S上做右旋转,这样S的左儿子成为S的父亲和N的新兄弟。我们接着交换S和它的新父亲的颜色。所有路径仍有同样数目的黑色节点,但是现在N有了一个黑色兄弟,他的右儿子是红色的,所以我们进入了情形6。N和它的父亲都不受这个变换的影响。

  情形6: S是黑色,S的右儿子是红色,而N是它父亲的左儿子。在这种情形下我们在N的父亲上做左旋转,这样S成为N的父亲(P)和S的右儿子的父亲。我们接着交换N的父亲和S的颜色,并使S的右儿子为黑色。子树在它的根上的仍是同样的颜色,所以性质3没有被违反。但是,N现在增加了一个黑色祖先: 要么N的父亲变成黑色,要么它是黑色而S被增加为一个黑色祖父。所以,通过N的路径都增加了一个黑色节点。

  此时,如果一个路径不通过N,则有两种可能性:

  • 它通过N的新兄弟。那么它以前和现在都必定通过S和N的父亲,而它们只是交换了颜色。所以路径保持了同样数目的黑色节点。

  • 它通过N的新叔父,S的右儿子。那么它以前通过S、S的父亲和S的右儿子,但是现在只通过S,它被假定为它以前的父亲的颜色,和S的右儿子,它被从红色改变为黑色。合成效果是这个路径通过了同样数目的黑色节点。

  在任何情况下,在这些路径上的黑色节点数目都没有改变。所以我们恢复了性质4。在示意图中的白色节点可以是红色或黑色,但是在变换前后都必须指定相同的颜色。

红黑树实现源码

#define BLACK 1
#define RED 0using namespace std;class bst {
private:struct Node {int value;bool color;Node *leftTree, *rightTree, *parent;Node() {color = RED;leftTree = NULL;rightTree = NULL;parent = NULL;value = 0;}Node* grandparent() {if (parent == NULL) {return NULL;}return parent->parent;}Node* uncle() {if (grandparent() == NULL) {return NULL;}if (parent == grandparent()->rightTree)return grandparent()->leftTree;elsereturn grandparent()->rightTree;}Node* sibling() {if (parent->leftTree == this)return parent->rightTree;elsereturn parent->leftTree;}};void rotate_right(Node *p) {Node *gp = p->grandparent();Node *fa = p->parent;Node *y = p->rightTree;fa->leftTree = y;if (y != NIL)y->parent = fa;p->rightTree = fa;fa->parent = p;if (root == fa)root = p;p->parent = gp;if (gp != NULL) {if (gp->leftTree == fa)gp->leftTree = p;elsegp->rightTree = p;}}void rotate_left(Node *p) {if (p->parent == NULL) {root = p;return;}Node *gp = p->grandparent();Node *fa = p->parent;Node *y = p->leftTree;fa->rightTree = y;if (y != NIL)y->parent = fa;p->leftTree = fa;fa->parent = p;if (root == fa)root = p;p->parent = gp;if (gp != NULL) {if (gp->leftTree == fa)gp->leftTree = p;elsegp->rightTree = p;}}void inorder(Node *p) {if (p == NIL)return;if (p->leftTree)inorder(p->leftTree);cout << p->value << " ";if (p->rightTree)inorder(p->rightTree);}string outputColor(bool color) {return color ? "BLACK" : "RED";}Node* getSmallestChild(Node *p) {if (p->leftTree == NIL)return p;return getSmallestChild(p->leftTree);}bool delete_child(Node *p, int data) {if (p->value > data) {if (p->leftTree == NIL) {return false;}return delete_child(p->leftTree, data);} else if (p->value < data) {if (p->rightTree == NIL) {return false;}return delete_child(p->rightTree, data);} else if (p->value == data) {if (p->rightTree == NIL) {delete_one_child(p);return true;}Node *smallest = getSmallestChild(p->rightTree);swap(p->value, smallest->value);delete_one_child(smallest);return true;}}void delete_one_child(Node *p) {Node *child = p->leftTree == NIL ? p->rightTree : p->leftTree;if (p->parent == NULL && p->leftTree == NIL && p->rightTree == NIL) {p = NULL;root = p;return;}if (p->parent == NULL) {delete  p;child->parent = NULL;root = child;root->color = BLACK;return;}if (p->parent->leftTree == p) {p->parent->leftTree = child;} else {p->parent->rightTree = child;}child->parent = p->parent;if (p->color == BLACK) {if (child->color == RED) {child->color = BLACK;} elsedelete_case(child);}delete p;}void delete_case(Node *p) {if (p->parent == NULL) {p->color = BLACK;return;}if (p->sibling()->color == RED) {p->parent->color = RED;p->sibling()->color = BLACK;if (p == p->parent->leftTree)rotate_left(p->sibling());elserotate_right(p->sibling());}if (p->parent->color == BLACK && p->sibling()->color == BLACK&& p->sibling()->leftTree->color == BLACK && p->sibling()->rightTree->color == BLACK) {p->sibling()->color = RED;delete_case(p->parent);} else if (p->parent->color == RED && p->sibling()->color == BLACK&& p->sibling()->leftTree->color == BLACK && p->sibling()->rightTree->color == BLACK) {p->sibling()->color = RED;p->parent->color = BLACK;} else {if (p->sibling()->color == BLACK) {if (p == p->parent->leftTree && p->sibling()->leftTree->color == RED&& p->sibling()->rightTree->color == BLACK) {p->sibling()->color = RED;p->sibling()->leftTree->color = BLACK;rotate_right(p->sibling()->leftTree);} else if (p == p->parent->rightTree && p->sibling()->leftTree->color == BLACK&& p->sibling()->rightTree->color == RED) {p->sibling()->color = RED;p->sibling()->rightTree->color = BLACK;rotate_left(p->sibling()->rightTree);}}p->sibling()->color = p->parent->color;p->parent->color = BLACK;if (p == p->parent->leftTree) {p->sibling()->rightTree->color = BLACK;rotate_left(p->sibling());} else {p->sibling()->leftTree->color = BLACK;rotate_right(p->sibling());}}}void insert(Node *p, int data) {if (p->value >= data) {if (p->leftTree != NIL)insert(p->leftTree, data);else {Node *tmp = new Node();tmp->value = data;tmp->leftTree = tmp->rightTree = NIL;tmp->parent = p;p->leftTree = tmp;insert_case(tmp);}} else {if (p->rightTree != NIL)insert(p->rightTree, data);else {Node *tmp = new Node();tmp->value = data;tmp->leftTree = tmp->rightTree = NIL;tmp->parent = p;p->rightTree = tmp;insert_case(tmp);}}}void insert_case(Node *p) {if (p->parent == NULL) {root = p;p->color = BLACK;return;}if (p->parent->color == RED) {if (p->uncle()->color == RED) {p->parent->color = p->uncle()->color = BLACK;p->grandparent()->color = RED;insert_case(p->grandparent());} else {if (p->parent->rightTree == p && p->grandparent()->leftTree == p->parent) {rotate_left(p);rotate_right(p);p->color = BLACK;p->leftTree->color = p->rightTree->color = RED;} else if (p->parent->leftTree == p && p->grandparent()->rightTree == p->parent) {rotate_right(p);rotate_left(p);p->color = BLACK;p->leftTree->color = p->rightTree->color = RED;} else if (p->parent->leftTree == p && p->grandparent()->leftTree == p->parent) {p->parent->color = BLACK;p->grandparent()->color = RED;rotate_right(p->parent);} else if (p->parent->rightTree == p && p->grandparent()->rightTree == p->parent) {p->parent->color = BLACK;p->grandparent()->color = RED;rotate_left(p->parent);}}}}void DeleteTree(Node *p) {if (!p || p == NIL) {return;}DeleteTree(p->leftTree);DeleteTree(p->rightTree);delete p;}
public:bst() {NIL = new Node();NIL->color = BLACK;root = NULL;}~bst() {if (root)DeleteTree(root);delete NIL;}void inorder() {if (root == NULL)return;inorder(root);cout << endl;}void insert(int x) {if (root == NULL) {root = new Node();root->color = BLACK;root->leftTree = root->rightTree = NIL;root->value = x;} else {insert(root, x);}}bool delete_value(int data) {return delete_child(root, data);}
private:Node *root, *NIL;
};

参考文章:

算法:树和图-理论 https://blog.csdn.net/weixin_43126117/article/details/97927143

[Data Structure] 数据结构中各种树 https://www.cnblogs.com/maybe2030/p/4732377.html#_label2

你真的懂树吗?二叉树、AVL平衡二叉树、伸展树、B-树和B+树原理和实现代码详解 www.srcmini.com/1315.html

伸展树(Splay Tree)进阶 - 从原理到实现 https://www.cnblogs.com/dilthey/p/9379652.html#splay-2.1

AVL树(查找、插入、删除)——C语言 https://www.cnblogs.com/lanhaicode/p/11321243.html

转载本站文章《讲透学烂二叉树(五):分支平衡—AVL树与红黑树伸展树自平衡》,
请注明出处:https://www.zhoulujun.cn/html/theory/algorithm/TreeGraph/8288.html

讲透学烂二叉树(五):分支平衡—AVL树与红黑树伸展树自平衡相关推荐

  1. 二叉树第i层中的所有结点_讲透学烂二叉树(二):图中树的定义amp;各类型树的特征分析...

    日常中我们见到的二叉树应用有,Java集合中的TreeSet和TreeMap,C++ STL中的set.map,以及Linux虚拟内存的管理,以及B-Tree,B+-Tree在文件系统,都是通过红黑树 ...

  2. 获得无向图连通子图_讲透学烂二叉树(一):图的概念和定义—各种属性特征浅析...

    树和图的概念 图是一种特殊的数据结构,由点和边构成,它可以用来描述元素之间的网状关系,这个网状没有顺序,也没有层次,就是简单的把各个元素连接起来. 图的概念和基本性质 图(graph):图(graph ...

  3. 讲透学烂二叉树(六):二叉树的笔试题:翻转|宽度|深度

    翻转|镜像二叉树 华为面试题--将二叉树的两个孩子换位置,即左变右,右变左. 90% of our engineers use the software you wrote (Homebrew), b ...

  4. 讲透学烂二叉树(三):二叉树的遍历图解算法步骤及JS代码

    二叉树的遍历是指不重复地访问二叉树中所有结点,主要指非空二叉树,对于空二叉树则结束返回. 二叉树的遍历分为 深度优先遍历 先序遍历:根节点->左子树->右子树(根左右),有的叫:前序遍历 ...

  5. 二叉树 红黑树 B树 B+树的优缺点

    前言 在MySQL中,无论是Innodb还是MyIsam,都使用了B+树作索引结构(这里不考虑hash等其他索引).本文将从最普通的二叉查找树开始,逐步说明各种树解决的问题以及面临的新问题,从而说明M ...

  6. 五分钟搞懂什么是红黑树

    五分钟搞懂什么是红黑树(全程图解) 18-09-04 星925 + 关注 献花(2)  收藏 前戏 红黑树,对很多童鞋来说,是既熟悉又陌生.熟悉是因为在校学习期间,准备面试时,这是重点.然后经过多年的 ...

  7. 数据结构与算法--Tree(二叉树、B±树、红黑树)

    在MySQL中,索引的的实现方式中使用的最多的就是B+Tree,那么为什么要选择B+Tree呢?我们就需要从最基本的二叉查找树说起 什么是二叉树? 二叉树 = (空树) | (节点+左右子二叉树) 解 ...

  8. 五分钟搞懂什么是红黑树(全程图解)

    红黑树(又叫平衡二叉树),对很多童鞋来说,是既熟悉又陌生.熟悉是因为在校学习期间,准备面试时,这是重点.然后经过多年的荒废,如今已经忘记的差不多了.如果正在看文章的你,马上快要毕业,面临着找工作的压力 ...

  9. 种树:二叉树、二叉搜索树、AVL树、红黑树、哈夫曼树、B树、树与森林

    虽然今天不是植树节,但是我今天想种树. 文章目录 树,什么是树? 二叉树 定义 二叉树的创建 二叉树的前中后序遍历 前序遍历: 中序遍历 后序遍历 已知前序.中序遍历结果,还原二叉树 已知后序.中序遍 ...

最新文章

  1. 汇总|C++常见知识点总结,涉及文本输出、排序、生成随机数、异常处理、关联容器、printf重定向、sprintf用法、cout重定向
  2. 【视频】SQL Server 2008 R2 StreamInsight - 多源复杂事件处理
  3. 19-Realm Management Extension (RME)
  4. [原创] debian 9.3 搭建Jira+Confluence+Bitbucket项目管理工具(四) -- 安装bitbucket 5.7.0
  5. flink 4-输出
  6. singleton 类_在Java中对Singleton类进行双重检查锁定
  7. java面试题总结(三)----本文都是笔试题,值得学习
  8. Hadoop问题:The auxService:mapreduce_shuffle does not exist
  9. 支付宝认错,回应央行 18 万行政罚单!
  10. 几行Java解决图片提取文字功能
  11. StreamSets实战之路(十五)-实战篇- 数据采集与处理
  12. C:\fakepath 问题
  13. idea类和方法注释模板配置
  14. 感慨一下中年人的焦虑
  15. vue自定义步骤行程条
  16. 自相关和相关的物理意义
  17. Linux Kernel 6.0 CXL Core pci.c 详解
  18. python做数据分析时缺失值填补、缺失值填充方法汇总
  19. 【3】IMU模块:PA-IMU-460 ROS驱动 + 与GNSS时间同步
  20. 发生系统错误 5。拒绝访问。

热门文章

  1. python气象分析
  2. 腾讯csig电话面试
  3. win10:专业软件的“克星”
  4. 一元三次方程高精度实数根(C语言)
  5. Java做化工示意图,绘制化工工艺流程图,手把手教你绘制流程图
  6. 2020年六级作文:标准的鸡汤文系列
  7. 如何在Windows 7 Media Center和Player中播放FLAC文件
  8. java计算机毕业设计高校学生社团管理源程序+mysql+系统+lw文档+远程调试
  9. Android Fatal signal 6 (SIGABRT) at 0x0000271d (code=-6)
  10. gif透明背景动画_【超实用干货! 】iPad上的动画App大推荐