红黑树:个人理解与Python实现

红黑树:个人理解与Python实现

 

【基本事实1】

红黑树是一种平衡的二叉查找树,无论插入还是删除操作都可以在O(lg n)内实现,而一般的二叉查找树则在极端情况下会退化为线性结构。
红黑树之所以是平衡的二叉查找树,是因为每个节点都有表示其颜色的域值:红或黑,在插入和删除操作的时候依据节点的颜色向平衡的方向调整。根本原因当然是由红黑树定义所决定的:
如果一个二叉查找树满足如下条件,那么它就称作红黑树:
1.每个节点要么是红色,要么是黑色
2.根结点是黑色
3.每个叶节点(NIL)为黑色
4.如果一个节点是红色,其儿子节点一定是黑色
5.对于每个节点,从该节点到其子孙节点的所有路径上包含相同数目的黑节点

 

【个人理解1】

红黑树的定义中,我认为有这些地方要注意:
1.每个节点只有一种颜色(后面删除节点的操作时引入的“额外一重黑色”则不满足此条件,所以一直要性质1调整)
2.定义中的叶节点是指NIL,它们都是黑色,而且没有子节点。根结点的父节点也是NIL。比如只有一个根节点的红黑树,它有两个叶节点。NIL是没有值的,只是一种存在。(我在Python的实现中,把NIL值都设定为None)
3.如果一个节点是红色的,它一定不是根结点,而且一定有父节点(父节点也一定是黑色的);如果它有儿子节点则一定是黑色儿子节点(每个节点如果左右儿子都是NIL,我认为它没有儿子)
4.性质5说的就是黑高度了

 

【基本事实2】

红黑树的旋转
红黑树在INSERT和DELETE的过程中,会使用到旋转操作。红黑树有两种旋转:左旋和右旋

左旋x:从右图到左图的过程
右旋y:从左图到右图的过程

 

【个人理解2】

1.并非每个节点在INSERT或DELETE的过程中都需要旋转操作
2.左旋就是右儿子y取代父节点x,x作为y的做儿子,y原来的左儿子b成为x现在的右儿子
3.右旋就是左旋的逆向过程

 

【基本事实3】

红黑树的插入
INSERT一个值的过程,就是在二叉查找树的INSERT操作基础上,根据红黑树性质做必要的调整,比如颜色变化,比如旋转,也就是INSERT_FIXUP的过程
插入的节点一般设定为红色然后再调整

 

【个人理解3】

假设要插入的节点为z,INSERT操作就是把z放到树的最底层,然后用INSERT_FIXUP区调整。INSERT_FIXUP中要注意的是:
1.如果z的父节点是NIL(即:插入节点z之前红黑树为空),那么把z涂黑,并成为根结点(完成插入)
2.如果z的父节点是黑色的,不用调整(完成插入)
3.如果z的父节点是红色的:
3-0:如果z没有叔叔节点,那么:
3-0-0:如果z为右儿子,且z的父节点p为左儿子,则左旋z的父节点,成为3-0-1;如果z为左儿子,且z的父节点p为右儿子,则右旋z的父节点p,成为3-0-1;
3-0-1:如果z为左儿子,则“父涂黑,爷涂红”,然后如果父节点是左儿子,则“爷右旋”,否则“爷左旋”(完成插入)
3-1:如果z的叔叔节点为黑色,那么:
3-1-0:如果z是右儿子,且z的父节点p为左儿子,则左旋z的父节点,成为3-1-1;如果z为左儿子,且z的父节点p为右儿子,则右旋z的父节点p,成为3-1-1;
3-1-1:如果z是左儿子,那么“父涂黑,爷涂红”,然后如果父节点是左儿子,则“爷右旋”,否则“爷左旋”(完成插入)
3-2:如果z的叔叔节点为红色,那么“父涂黑,叔涂黑,爷涂红”,并对爷爷节点g调用INSERT_FIXUP过程
以上序号和《算法导论》中的对应关系:3-2对应case1,3-1-0对应case2,3-1-1对应case3

 

【基本事实4】

1.红黑树删除一个节点的操作比较复杂,但也是在二叉查查找树的基础上,调用DELETE_FIXUP过程来调整
2.如果要删除的节点z,它有两个儿子,那么让z的值设定为z的后继节点y的值,然后删除y
3.如果要删除的节点z只有一个儿子x,那就让z的节点p和x成为“父子”。如果z原来是红色的,则不必调用DELETE_FIXUP过程,否则要调用
4.如果要删除的节点z没有儿子:那就直接删除z好了
5.删除节点时引入了“额外的一层黑色”,《算法导论》中文第二版P173这样说:
“在RB—DELETE中,如果被删除的节点y是黑色的,则会产生三个问题。首先,如果y原来是根结点,而y的一个红色的孩子成为了新的根,这就问犯了性质2)。其次,如果x和p[y](现在也是p[x])都是红的,就违反了性质4)。第三,删除y将导致先前包含y的任何路径上黑节点个数少1。因此,性质5)被y的一个祖先破坏了。不久者恶问题的一个办法就是把结点x视为还有额外的一重黑色。也就是说,如果将人以包含结点x的路径上黑节点个数加1,则在这种假设下,性质5)成立。当将黑节点y删除时,将其黑色“下推”至其子节点。现在为题变为结点x可能既不是红,又不是黑,从而违反了性质1)。结点x是双重黑色或红黑,这就分别给包含x的路径上黑结点个数贡献2个或1个。x的color属性仍然是RED(如果x是红黑的)或BLACK(如果x是双重黑色)。换言之,一个结点额外的黑色反映在x指向它,而不是它的color属性。”

 

【个人理解4】

1.当你想举反例推翻某个“结论”时,请注意,你的反例中的红黑树可能并不是红黑树;或者,它满足红黑树的定义,但无法判断是否能通过“每次插入一个节点”的方式生成。
2.因为有“额外一重黑色”的存在,《算法导论》中关于红黑树删除的case1中,调整前后的两幅图虽然“看上去是镜面对称”,但前者不满足性质5,调整后满足性质5
3.《算法导论》中关于红黑树删除的case2中,x现在为黑色,且有额外的一重黑色(就像是背负着子孙们的希望。。。),此时将x和w都去掉一个黑色,然后使p(x)增加额外的一层黑色。由于w原本为黑色,则现在令w为红色即可。此时令new_x=p(x),若new_x原本为红色,置黑即可结束;否则,对new_x调用DELETE_FIXUP过程
4.DELETE_FIXUP过程,调整的是DELETE(z)过程中z的左/右儿子(当z只有一个儿子时),或者z的后继的右儿子

#coding:utf8
#author:HaxtraZ
#description:红黑树,python实现
from random import randint

RED = 'red'
BLACK = 'black'

class RBT:
    def __init__(self):
       # self.items = []
        self.root = None
        self.zlist = []

    def LEFT_ROTATE(self, x):
        # x是一个RBTnode
        y = x.right
        if y is None:
            # 右节点为空,不旋转
            return
        else:
            beta = y.left
            x.right = beta
            if beta is not None:
                beta.parent = x

            p = x.parent
            y.parent = p
            if p is None:
                # x原来是root
                self.root = y
            elif x == p.left:
                p.left = y
            else:
                p.right = y
            y.left = x
            x.parent = y

    def RIGHT_ROTATE(self, y):
        # y是一个节点
        x = y.left
        if x is None:
            # 右节点为空,不旋转
            return
        else:
            beta = x.right
            y.left = beta
            if beta is not None:
                beta.parent = y

            p = y.parent
            x.parent = p
            if p is None:
                # y原来是root
                self.root = x
            elif y == p.left:
                p.left = x
            else:
                p.right = x
            x.right = y
            y.parent = x

    def INSERT(self, val):

        z = RBTnode(val)
        y = None
        x = self.root
        while x is not None:
            y = x
            if z.val < x.val:
                x = x.left
            else:
                x = x.right

        z.PAINT(RED)
        z.parent = y

        if y is None:
            # 插入z之前为空的RBT
            self.root = z
            self.INSERT_FIXUP(z)
            return

        if z.val < y.val:
            y.left = z
        else:
            y.right = z

        if y.color == RED:
            # z的父节点y为红色,需要fixup。
            # 如果z的父节点y为黑色,则不用调整
            self.INSERT_FIXUP(z)

        else:
            return

    def INSERT_FIXUP(self, z):
        # case 1:z为root节点
        if z.parent is None:
            z.PAINT(BLACK)
            self.root = z
            return

        # case 2:z的父节点为黑色
        if z.parent.color == BLACK:
            # 包括了z处于第二层的情况
            # 这里感觉不必要啊。。似乎z.parent为黑色则不会进入fixup阶段
            return

        # 下面的几种情况,都是z.parent.color == RED:
        # 节点y为z的uncle
        p = z.parent
        g = p.parent  # g为x的grandpa
        if g is None:
            return
            #   return 这里不能return的。。。
        if g.right == p:
            y = g.left
        else:
            y = g.right

        # case 3-0:z没有叔叔。即:y为NIL节点
        # 注意,此时z的父节点一定是RED
        if y == None:
            if z == p.right and p == p.parent.left:
                # 3-0-0:z为右儿子,且p为左儿子,则把p左旋
                # 转化为3-0-1或3-0-2的情况
                self.LEFT_ROTATE(p)
                p, z = z, p
                g = p.parent
            elif z == p.left and p == p.parent.right:
                self.RIGHT_ROTATE(p)
                p, z = z, p

            g.PAINT(RED)
            p.PAINT(BLACK)
            if p == g.left:
                # 3-0-1:p为g的左儿子
                self.RIGHT_ROTATE(g)
            else:
                # 3-0-2:p为g的右儿子
                self.LEFT_ROTATE(g)

            return

        # case 3-1:z有黑叔
        elif y.color == BLACK:
            if p.right == z and p.parent.left == p:
                # 3-1-0:z为右儿子,且p为左儿子,则左旋p
                # 转化为3-1-1或3-1-2
                self.LEFT_ROTATE(p)
                p, z = z, p
            elif p.left == z and p.parent.right == p:
                self.RIGHT_ROTATE(p)
                p, z = z, p

            p = z.parent
            g = p.parent

            p.PAINT(BLACK)
            g.PAINT(RED)
            if p == g.left:
                # 3-1-1:p为g的左儿子,则右旋g
                self.RIGHT_ROTATE(g)
            else:
                # 3-1-2:p为g的右儿子,则左旋g
                self.LEFT_ROTATE(g)

            return

        # case 3-2:z有红叔
        # 则涂黑父和叔,涂红爷,g作为新的z,递归调用
        else:
            y.PAINT(BLACK)
            p.PAINT(BLACK)
            g.PAINT(RED)
            new_z = g
            self.INSERT_FIXUP(new_z)

    def DELETE(self, val):
        curNode = self.root
        while curNode is not None:
            if val < curNode.val:
                curNode = curNode.left
            elif val > curNode.val:
                curNode = curNode.right
            else:
                # 找到了值为val的元素,正式开始删除

                if curNode.left is None and curNode.right is None:
                    # case1:curNode为叶子节点:直接删除即可
                    if curNode == self.root:
                        self.root = None
                    else:
                        p = curNode.parent
                        if curNode == p.left:
                            p.left = None
                        else:
                            p.right = None

                elif curNode.left is not None and curNode.right is not None:
                    sucNode = self.SUCCESOR(curNode)
                    curNode.val, sucNode.val  = sucNode.val, curNode.val
                    self.DELETE(sucNode.val)

                else:
                    p = curNode.parent
                    if curNode.left is None:
                        x = curNode.right
                    else:
                        x = curNode.left
                    if curNode == p.left:
                        p.left = x
                    else:
                        p.right = x
                    x.parent = p
                    if curNode.color == BLACK:
                        self.DELETE_FIXUP(x)

                curNode = None

        return False

    def DELETE_FIXUP(self, x):
        p = x.parent
        # w:x的兄弟结点
        if x == p.left:
            w = x.right
        else:
            w = x.left

        # case1:x的兄弟w是红色的
        if w.color == RED:
            p.PAINT(RED)
            w.PAINT(BLACK)
            if w == p.right:
                self.LEFT_ROTATE(p)
            else:
                self.RIGHT_ROTATE(p)

        if w.color == BLACK:
            # case2:x的兄弟w是黑色的,而且w的两个孩子都是黑色的
            if w.left.color == BLACK and w.right.color == BLACK:
                w.PAINT(RED)
                if p.color == BLACK:
                    return
                else:
                    p.color = BLACK
                    self.DELETE_FIXUP(p)

            # case3:x的兄弟w是黑色的,而且w的左儿子是红色的,右儿子是黑色的
            if w.left.color == RED and w.color == BLACK:
                w.left.PAINT(BLACK)
                w.PAINT(RED)
                self.RIGHT_ROTATE(w)

            # case4:x的兄弟w是黑色的,而且w的右儿子是红
            if w.right.color == RED:
                p.PAINT(BLACK)
                w.PAINT(RED)
                if w == p.right:
                    self.LEFT_ROTATE(p)
                else:
                    self.RIGHT_ROTATE(p)

    def SHOW(self):
        self.DISPLAY1(self.root)
        return self.zlist

    def DISPLAY1(self, node):
        if node is None:
            return
        self.DISPLAY1(node.left)
        self.zlist.append(node.val)
        self.DISPLAY1(node.right)

    def DISPLAY2(self, node):
        if node is None:
            return
        self.DISPLAY2(node.left)
        print node.val,
        self.DISPLAY2(node.right)

    def DISPLAY3(self, node):
        if node is None:
            return
        self.DISPLAY3(node.left)
        self.DISPLAY3(node.right)
        print node.val,

class RBTnode:
    '''红黑树的节点类型'''
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None
        self.parent = None

    def PAINT(self, color):
        self.color = color

def zuoxuan(b, c):
    a = b.parent
    a.left = c
    c.parent = a
    b.parent = c
    c.left = b

if __name__ == '__main__':
    rbt=RBT()
    b = []

    for i in range(100):
        m = randint(0, 500)
        rbt.INSERT(m)
        b.append(m)

    a = rbt.SHOW()
    b.sort()
    equal = True
    for i in range(100):
        if a[i] != b[i]:
            equal = False
            break

    if not equal:
        print 'wrong'
    else:
        print 'OK!'

  PS:这篇文章刚写好的时候,代码中是有错误的,而且DELETE_FIXUP()也没有给出;分析中也有小错误。不过现在已经改正了,代码中通过随机生成的数字,用系统的排序和我写的红黑树的排序对比,发现是结果是一样的,所以可以认为前面的算法分析是正确的。不过,速度上其实还是有点慢的,比如我用红黑树去排序,用在一道codeforces的题目中取代系统的sort,结果就会超时。

时间: 2024-09-21 09:18:20

红黑树:个人理解与Python实现的相关文章

C#与数据结构--树论--红黑树(Red Black Tree)(上)

介绍 今天我们来介绍另一种平衡二叉树:红黑树(Red Black Tree),红黑树由Rudolf Bayer于1972年发明,当时被称为平衡二叉B树(symmetric binary B-trees),1978年被Leonidas J. Guibas 和 Robert Sedgewick改成一个比较摩登的名字:红黑树. 红黑树和之前所讲的AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能.自从红黑树出来后,AVL树就被放到了博物馆里,据说是红黑树有

红黑树解法的why而非how

0 初衷 很多介绍红黑树的文章如同算法导论书中那样,都是上来直接给出一些分类情况,以及每个分类情况下的处理办法,而没有着重讲述为什么这么分类,为什么这个分类下执行这些操作,即只介绍了how,没有重点给出why.本篇文章的重点就在于解释why. 这样可能就导致一种现象:我按照这些分类以及分类下的操作办法,的确完整的走通想通了整个算法过程,感觉应该理解红黑树了,但是可能过了几个月后就忘记如何分类的了,忘记分类下如何操作的了. 归根到底是我们没有找出最本质的东西,比如说插入节点时遇到父子是红红的问题,

数据结构和算法05 之红-黑树(看完包懂~)

(友情提示,红-黑树是基于二叉搜索树的,如果对二叉搜索树不了解,可以先看看:二叉搜索树 )                从第4节的分析中可以看出,二叉搜索树是个很好的数据结构,可以快速地找到一个给定关键字的数据项,并且可以快速地插入和删除数据项.但是二叉搜索树有个很麻烦的问题,如果树中插入的是随机数据,则执行效果很好,但如果插入的是有序或者逆序的数据,那么二叉搜索树的执行速度就变得很慢.因为当插入数值有序时,二叉树就是非平衡的了,排在一条线上,其实就变成了一个链表--它的快速查找.插入和删除指

java集合类TreeMap和TreeSet及红黑树

看这篇博客前,我觉得很有必要先看下我之前的几篇博客 Red-Black Trees(红黑树)                                         (TreeMap底层的实现就是用的红黑树数据结构) 探索equals()和hashCode()方法                                 (TreeMap/TreeSet实现使用到的核心方法) java中的HashTable,HashMap和HashSet      (同为java集合类,对比下他们

java中treemap和treeset实现(红黑树)

TreeMap 的实现就是红黑树数据结构,也就说是一棵自平衡的排序二叉树,这样就可以保证当需要快速检索指定节点. TreeSet 和 TreeMap 的关系 为了让大家了解 TreeMap 和 TreeSet 之间的关系,下面先看 TreeSet 类的部分源代码: public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializab

红黑树深入剖析及Java实现

红黑树是平衡二叉查找树的一种.为了深入理解红黑树,我们需要从二叉查找树开始讲起. BST 二叉查找树(Binary Search Tree,简称BST)是一棵二叉树,它的左子节点的值比父节点的值要小,右节点的值要比父节点的值大.它的高度决定了它的查找效率. 在理想的情况下,二叉查找树增删查改的时间复杂度为O(logN)(其中N为节点数),最坏的情况下为O(N).当它的高度为logN+1时,我们就说二叉查找树是平衡的. BST的查找操作 T  key = a search key  Node ro

什么是红黑树?

       写这篇红黑树算法的目的:一是为了自己学习的总结:二是能够与大家一起交流沟通一起努力.文中有些内容学习自<算法导论>一书,部分来自于维基百科,我会在文中标注出来,有不明白的地方可以通过留言大家一起沟通.        首先,什么是红黑树呢? 红黑树是一种"平衡的"二叉查找树,它是一种经典高效的算法,能够保证在最坏的情况下动态集合操作的时间为O(lgn).红黑树每个节点包含5个域,分别为color,key,left,right和p. color是在每个节点上增加的

数据结构之红黑树

概述 红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组.它是在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees).红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能. 二叉查找树(BST) 二叉查找树(Binary Search Tree,简称BST)是一棵二叉树,它的左子节点的值比父节点的值要小,右

C语言实现红黑树的实例代码_C 语言

因为看内核的时候感觉红黑树挺有意思的,所以利用周末的时间来实现一下玩玩.红黑树的操作主要是插入和删除,而删除的时候需要考虑的情况更多一些.具体的操作就不在这里罗嗦了,百度文库里面有一个比较有好的文章,已经说的很明白了. 在看具体的操作的时候有的人可能感觉有些情况是没有考虑到的(如果没有这种感觉的人很有可能根本没有仔细地想).但是那些"遗漏"的情况如果存在的话,操作之前的红黑树将违反那几个规则. 写代码的时候很多次因为少考虑情况而导致错误,细节比较多,刚开始rb_node中没有指向父节点