查找(2) 红黑树

之前讲的二叉搜索树基本集合操作有不错的平均性能。然而当树的高度较高时,这些集合操作可能并不比在链表执行得快。红黑树可以保证在最坏情况下的基本集合操作的时间复杂度为O(lgn),红黑树是平衡搜索树中的一种。对于平衡树就讲三个比较有代表性的:红黑树、B树、跳跃表(本质上其实是树)。
《算法导论》和《算法4》对于红黑树都有不错的讲解,个人觉得《算法4》中讲解的红黑树让人更容易理解。
要想更彻底容易的理解红黑树,先来理解透2-3树。红黑树是2-3树的一种简单实现。2-3查找树作为一种比较重要的概念和思路对于后面要讲到的红黑树和B树非常重要。本文先简单讲下2-3树,再讲红黑树。


2-3树定义和性质

和二叉树不一样,2-3树的每个结点保存1个或者2个关键字。保存1个关键字的结点有指向左右孩子的指针,叫做2-结点。而保存2个关键字的结点有指向左中右三个孩子的指针,叫做3-结点。综上所以称为2-3树。下图即为一棵2-3树。
这里写图片描述
如图所示,我们发现:
①:对于2-结点X,它有一个关键字和同为2-3结点的左右两个孩子。若左孩子不为空,则左孩子的所有关键字都不大于X结点的关键字。若右孩子不为空,则右孩子的所有关键字都不小于X结点的关键字。
②:对于3-结点Y,它有两个关键字和左中右三个同为2-3结点的孩子。若左孩子不为空,则左孩子的所有关键字都不大于Y结点的最小关键字。
若中孩子不为空,则中孩子的所有关键字都不小于Y结点的最小关键字且都不大于Y结点的最大关键字。若右孩子不为空,则右孩子的所有关键字都不小于Y结点的最大关键字。

如果中序遍历2-3查找树,就可以得到排好序的序列。在一个完全平衡的2-3查找树中,根结点到每一个为空结点的距离都相同。

2-3树的查找

2-3树的查找和二叉搜索树类似,首先将待查关键字和根结点进行比较,如果相等,则查找成功。否则根据比较的条件,在其左中右子树中递归查找,如果找到的结点为空,则未找到。查找过程如下图:
这里写图片描述

2-3树的插入

因为2-3树包含2-结点和3-结点,自然而然要分两种情况讨论:

1:向2-结点插入
和二叉搜索树一样,首先要进行查找找出插入点,然后将结点插入到该点上。要保持平衡性(根结点到每个为空结点的距离都相同),需要进行必要调整。如果找到的插入点是一个2-结点的孩子结点上,那么将插入元素放入到该2-结点里使其变成一个3-结点即可。
这里写图片描述

2:向3-结点插入
这种情况稍微复杂点,可以分为3种子情况。

①:只有一个3-结点。
假设2-3树只包含一个3-结点,这个结点有两个关键字,我们可以假设这个结点能存放三个元素,暂时使其变成一个4-结点,同时他包含四个子结点。然后,我们将这个4-结点的中间元素提升,左边的结点作为其左子结点,右边的元素作为其右子结点。插入完成,变为平衡2-3查找树,树的高度从0变为1。
这里写图片描述

②:3-结点的父结点是2-结点。
向3-结点插入元素使其临时变成4-结点,提升该4-结点的中间元素合并入其父结点,使其父结点变成3-结点。
这里写图片描述

③:3-结点的父结点也是一个3-结点。
当我们插入的结点是3-结点的时候,我们将该结点拆分,中间元素提升至父结点,但是此时父结点也是一个3-结点,插入之后,父结点变成了4-结点,然后继续将中间元素提升至其父结点,直至遇到一个父结点是2-结点,然后将其变为3-结点,不再继续拆分。如果一路提升到根结点都是3-结点,那么将根结点按照情况①处理,分裂根结点,提升树的高度。
这里写图片描述

根节点的分裂 图示:
这里写图片描述

2-3树的效率分析

完全平衡的2-3查找树如下图,每个根结点到叶子结点的距离是相同的:
这里写图片描述
2-3树的查找效率与树的高度是息息相关的。
在最坏的情况下,也就是所有的结点都是2-结点,查找效率为O(lgn)
在最好的情况下,所有的节点都是3-结点,查找效率为O(log3n)约等于0.631lgn。
从距离来说,对于1百万个结点的2-3树,树的高度为12-20之间,对于10亿个节点的2-3树,树的高度为18-30之间。
对于插入来说,只需要常数次操作即可完成,因为他只需要修改与该结点关联的结点即可,不需要检查其他结点,所以效率和查找类似。


2-3树的实现是很复杂的,而在2-3查找树上的基础上改进红黑树不但具有不错的效率,而且实现起来也比较简单,应用也很广泛。

红黑树的定义、表示和查找

红黑树是对2-3树中的3-结点添加额外的信息,将结点之间的链接分为红色和黑色链接。其中红色链接用来连接两个2-结点来表示一个3-结点,这里统一规定红色链接左倾,即一个2-结点是另一个2-结点的左子结点,好处在于查找的时候不用做任何修改,和普通二叉查找树相同。而黑色链接则用来连接普通的2-3结点。
这里写图片描述
从图中可以看出红黑树本质上就是2-3树的另一种表现形式。
根据以上描述,我们对红黑树做以下约束以便实现简单化:
①:只具有红色链接或黑色链接。
②:根结点总是黑色。
③:一个结点不能有两个红色链接,且红色链接左倾。
④:整个树完全黑色平衡,即从根结点到所有叶结点的路径上,黑色链接的个数都相同。

表示和查找
我们给每一个结点加个新的表示颜色的标记。该标记指示该节点指向其父节点的颜色。

RED = True
BLACK = False

class RBTreeNode:
    def __init__(self,key=None,left=None,right=None,color=RED):
        self.key = key
        self.left = left
        self.right = right
        self.color = color

class RedBlackTree:
    def __init__(self):
        self.root = None

    def getRoot(self):
        return self.root

    #红黑树的查找与二叉树是一样,不用做太大修改。
    #但是红黑树比一般的二叉树有更好的平衡。
    def search(self,key):
        p = self.getRoot()
        while p and key is not p.key:
            if key < p.key:
                p = p.left
            else:
                p = p.right
        return p
    #红黑树的遍历跟二叉搜索树也是一样的,这里选用中根递归遍历法实现
    def _inOrderRec(self,r):
        if r:
            self._inOrderRec(r.left)
            print(r.key,end=' ')
            self._inOrderRec(r.right)

    def inOrderRec(self):
            self._inOrderRec(self.getRoot())

这里写图片描述

红黑树的平衡化操作

在插入结点之后,为了使得树保持平衡,需要做一些平衡化的操作。在介绍插入操作之前,首先说说这些平衡化操作。

旋转

  1. 左旋
    左旋操作如下图:
    这里写图片描述
    左旋操作用于将一个向右倾斜的红色链接旋转为向左链接。该操作实际上是将红线链接的两个节点中的一个较大的节点移动到根节点上。
    def _leftRotate(self,h):
        x = h.right
        h.right = x.left
        x.left = h
        x.color = h.color
        h.color = RED
        return x

左旋动画效果如下:
这里写图片描述

2.右旋
而右旋是左旋的逆操作
这里写图片描述

    def _rightRotate(self,h):
        x = h.left
        h.left = x.right
        x.right = h
        x.color = h.color
        h.color = RED
        return x

右旋的动画效果图如下:
这里写图片描述

颜色反转

当出现一个临时4-结点,即一个结点的两个子结点皆为红色的时候,按照如下图变换:
这里写图片描述
图中把E对子节点的连线设置为黑色,自己的颜色设置为红色即完成转换。

    def _flipColor(self,h):
        h.left.color = BLACK
        h.right.color = BLACK
        h.color = RED

红黑树的插入

插入结点的各种情况讨论

情况1:向单个2-结点中插入新的结点

这里写图片描述

如图所示这种情况的下操作步骤为:
①:标准的二叉树遍历,新插入的结点标记为红色。
②:如果新插入的结点是父亲结点的右子结点,需要进行左旋操作。

情况1扩展:向树底部的2-结点插入新结点
这里写图片描述
与前面情况1讨论的方法也是相同的。

情况2:往一个3-结点插入新结点
先假设一种最简单的情况,我们往一个只有两个结点构成的3-结点底部插入新结点。
根据插入元素和已知元素的大小,又可分为如下情况:
这里写图片描述

①:待插入结点比现有的两结点都大,将待插入结点连接到右边子树上,然后将中间结点提升。这样根节点的左右子树都是红色的结点,只需要进行一次颜色反转操作即可。
②:待插入的结点比现有的两结点都小,将新结点添加到最左侧,然后对其进行右旋操作,使中间结点成为根节点。样就转换到了第①种情况,这时候只需要再进行一次颜色反转即可。
③:待插入的结点位于两个结点之间,将新结点插入到左侧结点的右子结点,然后进行左旋操作。操作完之后就变成第②种情况了,再进行一次右旋,变成第①种情况,最后再进行一次颜色反转即可完成平衡操作。

情况2扩展:往树底部的3-结点插入新结点
有了上述操作基础,我们来看一下向树底部的3-结点插入一个新结点的典型操作过程:
这里写图片描述

根据上图可以看出步骤如下:
①:执行标准的二叉搜索树的插入操作。新插入的结点用红色标记。
②:如果需要,对4-结点进行旋转操作。
③:如果需要,进行颜色反转操作将红色结点提升。
④:如果需要,左旋操作使红色结点左倾。
⑤:某些情况下,需要递归调用情况1和情况2。该种情况下操作过程如下图所示。
这里写图片描述

红黑树插入操作的实现

将红链接在树中向上传递
经过平衡化讨论,我们沿着插入点到根结点的路径向上移动时在所经过的每个结点中顺序完成如下操作,就能完成插入操作:
①:如果右子结点是红色的而左子结点是黑色的,进行左旋操作。
②:如果左子结点是红色的且它的左子结点也是红色,则进行右旋操作。
③:如果左右子结点都是红色,则进行颜色反转。
图示如下:
这里写图片描述

代码如下:

    def _isRed(self,h):
        if h is None:
            return False
        return h.color is RED

    def _insert(self,h,key):
        if h is None:
            h = RBTreeNode(key)
            return h
        if key < h.key:
            h.left = self._insert(h.left,key)
        elif key > h.key:
            h.right = self._insert(h.right,key)
        else:
            pass

        if self._isRed(h.right) and not self._isRed(h.left):
            h = self._leftRotate(h)
        if self._isRed(h.left) and self._isRed(h.left.left):
            h = self._rightRotate(h)
        if self._isRed(h.left) and self._isRed(h.right):
            self._flipColor(h)
        return h

     def insert(self,key):
        self.root = self._insert(self.root,key)
        self.root.color = BLACK

红黑树的性能

无论以什么顺序插入结点构造,红黑树几乎是完美平衡的。

1:在最坏的情况下,红黑树的高度不超过2lgn
2:红黑树的平均高度大约为lgn


简单完整的实现代码

RED = True
BLACK = False

class RBTreeNode:
        def __init__(self,key=None,left=None,right=None,color=RED):
                self.key = key
                self.left = left
                self.right = right
                self.color = color

class RedBlackTree:
        def __init__(self):
                self.root = None

        def getRoot(self):
                return self.root

        def search(self,key):
                p = self.getRoot()
                while p and key is not p.key:
                        if key < p.key:
                                p = p.left
                        else:
                                p = p.right
                return p

        def _inOrderRec(self,r):
                if r:
                        self._inOrderRec(r.left)
                        print(r.key,end=' ')
                        self._inOrderRec(r.right)

        def inOrderRec(self):
                self._inOrderRec(self.getRoot())

        def _leftRotate(self,h):
                x = h.right
                h.right = x.left
                x.left = h
                x.color = h.color
                h.color = RED
                return x

        def _rightRotate(self,h):
                x = h.left
                h.left = x.right
                x.right = h
                x.color = h.color
                h.color = RED
                return x

        def _flipColor(self,h):
                h.color = RED
                h.left.color = BLACK
                h.right.color = BLACK

        def _isRed(self,h):
                if h is None:
                        return False
                return h.color is RED

        def _insert(self,h,key):
                if h is None:
                        h = RBTreeNode(key)
                        return h
                if key < h.key:
                        h.left = self._insert(h.left,key)
                elif key > h.key:
                        h.right = self._insert(h.right,key)
                else:
                        pass

                if self._isRed(h.right) and not self._isRed(h.left):
                        h = self._leftRotate(h)
                if self._isRed(h.left) and self._isRed(h.left.left):
                        h = self._rightRotate(h)
                if self._isRed(h.left) and self._isRed(h.right):
                        self._flipColor(h)
                return h

        def insert(self,key):
                self.root = self._insert(self.root,key)
                self.root.color = BLACK

if __name__ == '__main__':
        rbt = RedBlackTree()
        rbt.insert(5)
        rbt.insert(7)
        rbt.insert(2)
        rbt.insert(4)
        rbt.insert(9)
        rbt.inOrderRec()
        print()
        print("search 4's position: ",rbt.search(4))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值