红黑树边学边做--python3实现与可视化

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_35603331/article/details/81950143

一.红黑树性质和应用

  1. 每个节点或是红色的,或是黑色的

  2. 根节点是黑色的

  3. 每个叶节点(NIL)是黑色的

  4. 如果一个节点是红色的,则它的两个子节点都是黑色的

  5. 对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。

这五条性质决定了红黑树最长的分支的深度最多是最短分支深度的2倍,因为最短的就是每个节点都是黑的,最长的最多就是红黑相间嘛。这使得红黑树始终处于一个大致平衡的状态,保证了性能。
总而言之,红黑树的最大特点就是即使在最坏情况下,插入,查找,删除等操作的时间复杂度仍然都是O(logn),并且有多少数据就占用多少内存,相比hashmap更为节省内存。

二.红黑树python3实现与可视化

1).准备工作

首先需要安装python3,我这里的环境是windows7,python3.6。之后需要安装一个可视化模块graphviz,详情参照上一篇博客
https://blog.csdn.net/qq_35603331/article/details/81591949

2).python实现与讲解

首先直接贴代码

1.python实现

BLACK = 0
RED = 1
from graphviz import Digraph


class Rbtree:
    root = None
    name = None

    def __init__(self, name='rbtree'):
        self.name = name

    def insert(self, point):
        if self.root is None:
            point.color = BLACK
            self.root = point
            return
        self.root.add_child(point)

    def find(self, key):
        if self.root is None:
            print('该树为空树!')
            return None
        return self.root.find(key)

    # def select(self, point):
    # def print(self):
    def view(self):
        graph = Digraph(self.name)
        if self.root is not None:
            self.root.draw(graph)
            graph.view(cleanup=True)


class RbPoint:
    parent = None
    left = None
    right = None
    color = -1  # 节点颜色 0 黑 1红
    key = None
    tree = None

    def __init__(self, key, tree):
        self.key = key

    def view(self):
        pg = Digraph('find')
        pg.node(str(self.key), style='filled', color=('red' if self.color == RED else 'BLACK'), fontcolor='white',
                shape='circle')
        pg.view()

    def draw(self, graph):
        if self.color == BLACK:
            s = 'black'
        else:
            s = 'red'
        # 画出自己这个点,包括值和颜色
        graph.node(str(self.key), style='filled', color=('red' if self.color == RED else 'BLACK'), fontcolor='white',
                   shape='circle')
        if self.left is not None:
            graph.edge(str(self.key), str(self.left.key))
            self.left.draw(graph)
        if self.right is not None:
            graph.edge(str(self.key), str(self.right.key))
            self.right.draw(graph)

    def change_color(self):
        if self.color == BLACK:
            self.color = RED
        else:
            self.color = BLACK

    def rotate(self, child):
        # 和左孩子进行旋转
        if child == self.left:
            print('从左往右旋')
            if self.parent is not None:
                if self.parent.left == self:
                    self.parent.left = child
                else:
                    self.parent.right = child
            child.parent = self.parent
            self.parent = child
            self.left = child.right
            child.right = self
            if child.parent is None:
                # 该节点变为根节点
                print('根节点变更')
                tree.root = child
        # 和右孩子进行旋转
        else:
            print('从右往左旋')
            if self.parent is not None:
                if self.parent.left == self:
                    self.parent.left = child
                else:
                    self.parent.right = child
            child.parent = self.parent
            self.right = child.left
            child.left = self
            self.parent = child
            if child.parent is None:
                # 该节点变为根节点
                print('根节点变更')
                tree.root = child

    def find(self, key):
        print('当前节点值:', self.key, '查找值:', key)
        if key == self.key:
            return self
        if key < self.key:
            if self.left is None:
                return None
            else:
                return self.left.find(key)
        else:
            if self.right is None:
                return None
            else:
                return self.right.find(key)

    def add_child(self, child):
        if child.key < self.key:
            if self.left is None:
                self.left = child
                child.parent = self
                print('键为', child.key, '的节点插入到键为', self.key, '的节点的左孩子处')
                self.adjust(child)
            else:
                self.left.add_child(child)
            return

        if child.key > self.key:
            if self.right is None:
                self.right = child
                child.parent = self
                print('键为', child.key, '的节点插入到键为', self.key, '的节点的右孩子处')
                self.adjust(child)
            else:
                self.right.add_child(child)

    def adjust(self, child):
        def handle1(g, p):
            g.rotate(p)
            g.change_color()
            p.change_color()
            print('状况1调整完毕')

        def handle2(g, p, n):
            p.rotate(n)
            # 状况2->状况1
            g.rotate(n)
            n.change_color()
            g.change_color()
            print('状况2')

        def handle3(g, p, u):
            print('状况3')
            p.change_color()
            u.change_color()
            g.change_color()
            if g.parent is not None:
                g.parent.adjust(g)
            else:
                # g为根节点
                g.color = BLACK

        def handle4(g, p):
            p.change_color()
            g.change_color()
            if g.parent is not None:
                g.parent.adjust(g)
            else:
                # g为根节点
                g.color = BLACK

        print('开始调整')
        # 子节点默认红色
        child.color = RED
        # 根据p节点(父节点)颜色判断是否需要调整
        if self.color == BLACK:
            # 黑色,不需要调整
            return

        # 父节点也为红色,必须调整
        # 父节点为红色,G节点(父节点的父节点)必存在且必为黑色
        # 状况1:U节点(叔叔节点)为黑色,n节点(新增加的节点)外侧插入
        # 状况2:u为黑色,n内侧插入
        # 状况3:u为红色
        g = self.parent
        if self == g.left:
            # 父节点为祖父节点的左孩子,叔叔节点为祖父节点的右孩子
            u = g.right
            if u is None or u.color == BLACK:
                # u节点为黑色,状况1或2
                if child == self.left:
                    # 状况1
                    handle1(g, self)
                else:
                    # 状况2
                    handle2(g, self, child)
            else:
                # u节点为红色,状况3
                handle3(g, self, u)
        # 孩子节点为父节点的左孩子,状况1
        else:
            # 父节点为祖父节点的右孩子
            u = g.left
            if u is None or u.color == BLACK:
                if child == self.right:
                    # 状况1
                    handle1(g, self)
                else:
                    # 状况2
                    handle2(g, self, child)
            else:
                # 状况3,u节点为红色
                handle3(g, self, u)


if __name__ == '__main__':
    tree = Rbtree('t2')
    for i in range(20, -1, -1):
        p = RbPoint(i, tree)
        tree.insert(p)
    tree.view()

如果环境配置没有问题的话应该可以直接运行,会生成如下图片
这里写图片描述
(当然实际上生成的是个pdf)
这就是我们构建的一个简单红黑树了,具体构建过程如下:

if __name__ == '__main__':
    tree = Rbtree('t2')
    for i in range(20, -1, -1):
        p = RbPoint(i, tree)
        tree.insert(p)
    tree.view()

也就是把20到0的数字依次插进红黑树而已

2.思路讲解

这里比较关键的就是两个类,Rbtree和Rbpoint,分别表示红黑树和红黑树的节点,通过红黑树完成插入,查找等操作,红黑树始终持有根节点root,从而可以调用root的查找,插入方法来完成具体操作。
其中Rbpoint的属性left,right,partent分别表示左右孩子和父亲,key表示标识这个节点的关键字,用来比较不同节点的大小,是排序的依据。之后Rbpoint实现了find,add_child和adjust这几个比较关键的方法。find就是查找某个节点的位置,并返回这个节点,add_child则是插入新节点到以当前节点为根的树中,adjust就是插入新节点以后对红黑树进行调整。
重点说一下add_child的思路,先比较新节点和当前节点的大小关系,如果新节点小,看看当前节点左孩子是否为空,为空就插入,然后进行调整,如果不为空,就继续调用左孩子的add_child方法插入这个新节点,不断递归,新节点比当前节点大的情况同理。
然后是插入后进行调整。我们默认新插入的节点都是红节点,插入后就是判断不同情况,进行调整。
如果插入的位置是根节点,那么直接把新节点涂成黑色即可。如果插入位置的父节点是黑色,那么什么也不用做。
此外就剩下三种情况了,我们只要先判断出这三种情况,然后进行调整即可。
情况1:
U节点(叔叔节点)为黑色,n节点(新增加的节点)外侧插入
这种情况下直接将爷爷节点和父节点进行右旋处理,然后变换原先爷爷节点和父节点颜色即可,同时注意可能造成的root节点变更,需要及时更新。
这里写图片描述
情况2:u为黑色,n内侧插入
这种情况首先将新节点和父节点左旋处理,这就变成了情况1,然后将爷爷节点和新节点左旋(因为新节点已经到了原来父节点的位置),再分别变换颜色即可
这里写图片描述
状况3:u为红色
直接将父节点和叔叔节点都变为黑色,爷爷节点变为红色,然后把爷爷节点视为新插入的节点,继续调整即可。
这里写图片描述
注意:没有叔叔节点则按照叔叔节点为黑色处理,但是要注意空指针的问题。此外,在左旋和右旋操作时,root节点可能会变化,需要注意维护Rbtree中的root始终指向根节点。

3.旋转操作

这里写图片描述
具体实现见代码中的rotate方法

三.尾声

这里的红黑树实现只实现了插入,有兴趣的话也可以尝试进一步实现删除等操作。

猜你喜欢

转载自blog.csdn.net/qq_35603331/article/details/81950143