二叉平衡搜索树AVL 学习解析 及代码实现研究
前言
之前我分析过avl树,今天我在继续分析特化的AVL树(红黑树),都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。在我们实际的用途中,用的很多;例如 TreeMap排序等等,以达到快速查找数据的效果
性质和定义
红黑树(RBT)的定义:它或者是一棵空树,或者是具有一下性质的平衡二叉查找树
- 节点非红即黑。
- 根节点是黑色
- 所有null结点称为叶子节点,且认为颜色为黑色
- 所有红节点的子节点都为黑色
- 从任意节点到其叶子节点 的所有路径上都包含相同数目的黑节点。
上图根据红黑树的基本性质,做了一个基本的红黑树展示
性质
对平衡树的一种改进,任意一个节点,它的红黑树的间隔不超过一倍,但它的左右子树高差有可能大于 1,所以红黑树不是严格意义上的平衡二叉树(AVL),但对之进行平衡的代价较低, 其平均统计性能要强于 AVL 。
- 红黑树需要维持颜色变更,因此 插入和删除保持为 O(log n))次;
- avl树需要维持任何节点的两个子树最大差别为1;因此总的来说,建树复杂度为O(NlogN)
- 红黑树是二叉排序树,因此可以采用中序遍历法,进行遍历出排序好的数据
- 在树的删除增加节点时,为保持
从插入节点进行分析
插入节点
- 先按照二叉排序树 的方式插入节点 设置为红色 我们分析过二叉排序树如何插入节点 ;
插入节点过后就有可能导致用红黑树颜色不正确,因此我们需要重新维持树型
- 插入的是根节点,直接将 节点红色涂黑
- 插入的节点的父节点是黑色,不违背任何性质,不用调整
插入节点的父节点是红色 ,就有下面几种情况
- 父节点是祖父节点的左孩子 祖父节点的另一个子节点(叔叔节点)是红色 (node.parent=node.parent.parent.leftchild node.parent.parent.right=read)
对策: 将当前节点的父节点和叔叔节点涂黑,祖父节点涂红,把当前节点指向祖父节点,从新的当前节点开始算法
n节点代表 当前节点 p 代表父亲节点 g 代表祖父节点 u代表叔叔节点
为什么这样去把原有节点的父亲节点涂,这也是为了保证红黑树的特性不变因此做的处理,需要将祖父节点涂红
- 插入节点的父节点是红色 ,父节点是祖父节点的左孩子;叔叔节点是黑色,当前节点是其父节点的右孩子
对策: 以当前节点为支点左旋,当前节点的左节点做为新的当前节点。
- 插入节点的父节点是红色 ,父节点是祖父节点的左孩子;叔叔节点是黑色,当前节点是其父节点的左孩子
对策:父节点变为黑色,祖父节点变红色,再父节点为支点进行右旋
父节点是祖父节点的右孩子,和上面情况一样,将左全部变成右即可
总的来说插入节点有一下这几种情况
- 插入的节点是根节点,则直接将节点涂黑即可
- 插入节点的父节点是黑色,则不动任何数据
- 插入节点的父节点是红色,分为下面 的情况(先判断父节点是祖父节点的左右节点,叔叔如果是黑色,则需要分类进行转换了)
父亲是祖父节点的左孩子, 祖父节点的另一个子节点(叔叔节点)是红色,则 要将颜色进行变换成红黑树的性质,并不旋转
父亲是祖父节点的左孩子, 叔叔节点是黑色,当前节点是父节点的右孩子, 当前节点左旋后,将左节点设为当前节点
父亲是祖父节点的左孩子, 叔叔节点是黑色,当前节点是父节点的左孩子, 要将颜色进行变换成红黑树的性质,然后在以父节点进行右旋,就成为最后的红黑树了
父节点是祖父节点的右孩子 ;和上面情况一样,将左全部变成右即可
整个红黑树维持红黑节点的平衡,相对于avl来说,不是要求的强平衡,在我看来,有一点弱平衡的感觉;为什么在实际的数据结构运用中,运用很多,我想就是不用特别强调平衡,既效率提升,又保持频繁插入时,数据的时间复杂度,相当于用了一些空间复杂度,来换取更高的效果
删除节点
删除节点:先进行二叉排序树的删除操作,然后已替换节点作为当前节点进行后面的平衡操作
- 当前节点是红色,直接把当前节点染成黑色,结束。
- 当前节点x是黑色 ,被删除节点是父节点的左孩子,当前节点是根节点 保持原样
- 当前节点x是黑色,被删除节点是父节点的左孩子,当前节点x的兄弟节点是红色(此时父节点和兄弟节点的子节点分为黑)
对策:把父节点染成红色,兄弟节点染成黑色,对父节点进行左旋,重新设置x的兄弟节点
- 当前节点x 的兄弟节点是黑色 ,被删除节点是父节点的左孩子,兄弟节点的两个孩子都是黑色
对策:将x的兄弟节点设为红色,设置x的父节点为新的x节点
- 当前节点x 的兄弟节点是黑色 被删除节点是父节点的左孩子,兄弟的右孩子是黑色,左孩子是红色
对策:将x兄弟节点的左孩子设为黑色,将x兄弟节点设置红色,将x的兄弟节点右旋,右旋后,重新设置x的兄弟节点。
- 当前节点x 的兄弟节点是黑色 被删除节点是父节点的左孩子,兄弟节点的右孩子是红色
对策:把兄弟节点染成当前节点父节点颜色,把当前节点父节点染成黑色,兄弟节点右孩子染成黑色,再以当前节点的父节点为支点进行左旋,算法结算
被删除节点是父节点的右孩子 把上面的左设置为右
删除节点总的来说就是下面的几种可能
- 先进行二叉排序树的删除操作,然后已替换节点作为当前节点进行后面的平衡操作
- 然后在看当前的节点如果是红色,直接涂黑即可
- 当前节点是黑色,则有下面几种情况
被删除节点是父节点的左孩子,当前节点是根节点 保持原样
被删除节点是父节点的左孩子,当前节点x的兄弟节点是红色(此时父节点和兄弟节点的子节点分为黑) ,解决办法是:把父节点染成红色,兄弟节点染成黑色,对父节点进行左旋,重新设置x的兄弟节点
被删除节点是父节点的左孩子,当前节点x 的兄弟节点是黑色,兄弟节点的两个孩子都是黑色;解决:将x的兄弟节点设为红色,设置x的父节点为新的x节点
被删除节点是父节点的左孩子,当前节点x 的兄弟节点是黑色,兄弟的右孩子是黑色,左孩子是红色 ,解决:将x兄弟节点的左孩子设为黑色,将x兄弟节点设置红色,将x的兄弟节点右旋,右旋后,重新设置x的兄弟节点。
被删除节点是父节点的左孩子,当前节点x 的兄弟节点是黑色,兄弟的右孩子是黑色,兄弟节点的右孩子是红色 解决:把兄弟节点染成当前节点父节点颜色,把当前节点父节点染成黑色,兄弟节点右孩子染成黑色,再以当前节点的父节点为支点进行左旋,算法结算
被删除节点是父节点的右孩子 把上面的左设置为右即可
操作红黑树总结分析
不知道各位在发现在保持红黑树的特性的平衡的时候:
- 在插入节点时,判断的叔叔节点和父节点的颜色, 如果父节点和叔叔节点颜色相同时,则不需要选择,直接将颜色修改为红黑数特性,然后继续走平衡;当颜色不同,这就要判断当前节点的 是左还是右,当然如果是右,节点就左旋;如果是左,会麻烦一点 ,保持红黑树特性,并进行父节点右旋;
- 其实删除节点,道理是一样的,只不过需要判断 父节点 及兄弟节点的子节点;这样一看其实红黑树还是很简单;
- 然后对于avl树的,左右平衡 操作,关注的是树的深度,一旦有一个节点的平衡因子大于1,则我们就要做平衡处理,并且使之达到平衡,插入的左子树的左子树这个平衡起来是很简单的,插入左子树的右子树,都会不断的左旋,后右旋,更改平衡因子 来达到平衡的效果
具体的可以看看 我这篇分析
遍历
这里遍历树,还是采用分治法,或者前序,中序遍历,后续遍历,这里我说一下中序遍历
public void midOrderTraverse(TreeNode root){
if(root==null){
return;
}
//LDR
midOrderTraverse(root.leftChild);
System.out.print(root.data+" ");
midOrderTraverse(root.rightChild);
}
对于二叉排序树来说,用中序来遍历是能做顺序遍历,利用递归的方式进行实现中序遍历 实现代码量是最少的,
利用 先遍历左边知道为空时,才返回打印最左边节点,在查看最左边节点右孩子肯定不存,就往上走,打印数据,在往右孩子走;以此类推,利用递归的特性
总结
整个红黑树的原理,保证二叉排序树;其次主要就是围绕着保证红黑树特性,保证它的特性;降低查询数据的时间复杂度,在代码中还是应用很多的;这篇数据结构我没有代码的实现分析,我放到了treemap里面分析;大家有兴趣可以关注我分析的treemap集合