python 红黑树_红黑树:个人理解与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,结果就会超时。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值