插入
- 接前面
- 红黑树的删除
- 234树的层序打印
- 代码
- 插入前
- 《算法导论》只有四种情形
- 插入3结点情形
- 情形一:直接插入
- 情形二:反色递归调用
- 情形三四
- 验证
- 一个个地插入
- 升层
- 加一减一插入
- 批量插入
- 后话
接前面
红黑树的删除
会对红黑树和234树有基本的了解。
重点234树与红黑树同构。
234树的层序打印
打印红黑树和对应的234树,展现插入操作升层的情形,以验证理论知识,更好地理解红黑树。
代码
实现了红黑树的插入和删除操作。
插入前
找到插入位置的叶子结点,只有父结点是红色的情形才要调整。情形并不多,但是插满时要向上调整,怎么升层操作呢?《算法导论》第13章有讲RB-INSERT-FIXUP,跟删除调整一样只有四种情形。
《算法导论》只有四种情形
【插入结点的父结点为一左子结点】
算法导论4情形 | 调整 |
---|---|
case0:在黑结点下插入 | 直接插入 |
case1:在红结点下插入,且该结点有红色兄弟结点 (插入结点的叔结点) | 原4-结点的3个结点反色 |
case2: 插入3结点,「<」形在红结点下插入,且该结点无红色兄弟结点 (插入结点的叔结点)插入结点为右子结点: | 下段左旋>反色>上段右旋 |
case3:插入3结点,「/」形在红结点下插入,且该结点无红色兄弟结点 (插入结点的叔结点)插入结点为左子结点: | 反色>上段右旋 |
2结点,直接插入。4结点,直接反色向上调整,递归调用调整自己。
插入3结点情形
直接常数时间复杂度内调整,不用向上调整。列出了所有情形。
情形一:直接插入
添加新结点的操作在插入前完成的,这里直接判断返回就可以了。向上调整走到这里的时候也不用添加新结点,就是直接返回。
if p.color == BLACK:
return
剩下三种情形,要在有父接点的情形。而且还有镜像的代码,像前面图表是插入结点的父结点为左子结点,还有右右结点的情形。它们都要写在这个 i f if if下面。
if p.parent:
g = p.parent
if g.right == p:# 结点w为x叔结点
w = g.left
else:
w = g.right
情形二:反色递归调用
if w and w.color == RED:#调用回来
w.PAINT(BLACK)
p.PAINT(BLACK)
g.PAINT(RED)
if g.parent:#回溯后,不能转到下面的情况调整。
p = g.parent
x = g
self.INSERT_FIXUP(p,x)
else:#到根了
g.PAINT(BLACK)
return
情形三四
w = = N o n e o r w . c o l o r = = B L A C K w == None or w.color == BLACK w==Noneorw.color==BLACK,空结点也是黑色的。跟情形二是互斥的,不能走了上面再走这里,要用 e l i f elif elif。图片和表格里说的反色有时是对234树来说的,所以 x x x旋转作根后3结点的三个 k e y key key都要反色,比如 x . l e f t . P A I N T ( R E D ) x.left.PAINT(RED) x.left.PAINT(RED)
#3.插入3结点,「<」形;在红结点下插入,无红叔,插入结点为右子结点: 下段左旋>反色>上段右旋
elif w == None or w.color == BLACK:
if p == g.left:#插入结点的父结点为一左子结点
if x == p.right:
self.LEFT_ROTATE(p)
x.left.PAINT(RED)#x作根后3结点的三个key都要反色
x.PAINT(BLACK)
g.PAINT(RED)
self.RIGHT_ROTATE(g)
else:
#4.插入3结点,「/」形,插入结点为左子结点: 反色>上段右旋
p.PAINT(BLACK)
g.PAINT(RED)
self.RIGHT_ROTATE(g)
else:#插入结点的父结点为一右子结点
if x == p.left:#插入结点插入后为左子结点:下段右旋>反色>上段左旋
self.RIGHT_ROTATE(p)
x.right.PAINT(RED)#x作根后3结点的三个key都要反色
x.PAINT(BLACK)
g.PAINT(RED)
self.LEFT_ROTATE(g)
else:
#插入结点插入后为右子结点: 反色>上段左旋
p.PAINT(BLACK)
g.PAINT(RED)
self.LEFT_ROTATE(g)
验证
还是用《算法与数据结构》平衡二叉排序树的列子。插入【27, 73, 10, 5, 18, 41, 99, 51, 25】
一个个地插入
>>> rb.INSERT(27)
>>> t=tree234(rb.root)
>>> t.print()
结点类型和值(2, [27])
>>> rb.INSERT(73)
>>> t=tree234(rb.root)
>>> t.print()
结点类型和值(3, [27, 73])
>>> rb.INSERT(10)
>>> t=tree234(rb.root)
>>> t.print()
结点类型和值(4, [10, 27, 73])
>>> rb.breadth_fisrt_level_traversal(rb.root)
(27, black) (10, red) (73, red)
>>> rb.INSERT(5)
>>> rb.breadth_fisrt_level_traversal(rb.root)
(27, black) (10, black) (73, black) (5, red)
>>> t=tree234(rb.root)
>>> t.print()
结点类型和值(4, [10, 27, 73])
结点类型和值(2, [5])
升层
上面插入五的时候升层了。上面把升层前后红黑树的层序遍历也打印出来了。
>>> rb.INSERT(18)
>>> t=tree234(rb.root)
>>> t.print()
结点类型和值(4, [10, 27, 73])
结点类型和值(2, [5])
结点类型和值(2, [18])
>>> rb.INSERT(41)
>>> rb.INSERT(99)
>>> t=tree234(rb.root)
>>> t.print()
结点类型和值(4, [10, 27, 73])
结点类型和值(2, [5])
结点类型和值(2, [18])
结点类型和值(2, [41])
结点类型和值(2, [99])
>>> rb.breadth_fisrt_level_traversal(rb.root)
(27, black) (10, black) (73, black) (5, red) (18, red) (41, red) (99, red)
>>> rb.INSERT(51)
>>> rb.breadth_fisrt_level_traversal(rb.root)
(27, black) (10, black) (73, red) (5, red) (18, red) (41, black) (99, black) (51, red)
>>> t=tree234(rb.root)
>>> t.print()
结点类型和值(3, [27, 73])
结点类型和值(4, [5, 10, 18])
结点类型和值(3, [41, 51])
结点类型和值(2, [99])
>>> rb.INSERT(25)
>>> rb.breadth_fisrt_level_traversal(rb.root)
(27, black) (10, red) (73, red) (5, black) (18, black) (41, black) (99, black) (25, red) (51, red)
>>> t=tree234(rb.root)
>>> t.print()
结点类型和值(4, [10, 27, 73])
结点类型和值(2, [5])
结点类型和值(3, [18, 25])
结点类型和值(3, [41, 51])
结点类型和值(2, [99])
>>> rb.INSERT(28)
>>> t=tree234(rb.root)
>>> t.print()
结点类型和值(4, [10, 27, 73])
结点类型和值(2, [5])
结点类型和值(3, [18, 25])
结点类型和值(4, [28, 41, 51])
结点类型和值(2, [99])
加一减一插入
都插入进去也才两层。一个数加一,一个数再减一地插入吧。【28, 72, 11, 4…】
插入到72的时候升三层了,但是忘记打印升层前后的红黑树了。删除刚插入的接点,可以回到插入前的状态吗?应该回到之前的状态吗?这就是后话了。
>>> rb.INSERT(72)
>>> t=tree234(rb.root)
>>> t.print()
结点类型和值(4, [10, 27, 73])
结点类型和值(2, [5])
结点类型和值(3, [18, 25])
结点类型和值(3, [28, 41])
结点类型和值(2, [99])
结点类型和值(3, [51, 72])
>>> rb.DELETE(72)
>>> t=tree234(rb.root)
>>> t.print()
结点类型和值(4, [10, 27, 73])
结点类型和值(2, [5])
结点类型和值(3, [18, 25])
结点类型和值(3, [28, 41])
结点类型和值(2, [99])
结点类型和值(2, [51])
批量插入
从上面的打印情况看,是没有回到之前的状态的。那把28加到列表最后一起插入吧。
a = [27, 73, 10, 5, 18, 41, 99, 51, 25, 28]
rb = RBT()
for v in a:
rb.INSERT(v)
>>> rb.breadth_fisrt_level_traversal(rb.root)
(27, black) (10, red) (73, red) (5, black) (18, black) (41, black) (99, black) (25, red) (28, red) (51, red)
>>> t=tree234(rb.root)
>>> t.print()
结点类型和值(4, [10, 27, 73])
结点类型和值(2, [5])
结点类型和值(3, [18, 25])
结点类型和值(4, [28, 41, 51])
结点类型和值(2, [99])
>>> rb.INSERT(72)
>>> rb.breadth_fisrt_level_traversal(rb.root)
(27, black) (10, black) (73, black) (5, black) (18, black) (41, red) (99, black) (25, red) (28, black) (51, black) (72, red)
>>> t=tree234(rb.root)
>>> t.print()
结点类型和值(4, [10, 27, 73])
结点类型和值(2, [5])
结点类型和值(3, [18, 25])
结点类型和值(3, [28, 41])
结点类型和值(2, [99])
结点类型和值(3, [51, 72])
插入72后升到了三层的234树。前面提到的1到50插入后是6层的。
后话
红黑树是红一层黑一层的吗?