伸展树(splay tree)的可视化
参考书籍 《[数据结构与算法分析_Java语言描述(第2版)].韦斯》code on github
按照书里面的方法,伸展树里面不能像AVL那样直接在某个节点上旋转,而必须考虑父节点和祖父节点。 所以这就加大了实现难度,如果我们不存储某个节点的父节点和祖父节点的话,那么必须在遍历的时候 找到孙子节点(这样当前节点就是祖父节点,下一个节点就是父节点)。
另外伸展树需要区分对待zig-zag和zig-zig的情况,这点书里面给了具体的示例
书里面给了一个具体示例,首先插入1-32所有数字并且确保都在左子树上(所以插入顺序必须是逆序)。 然后分别遍历1-7这些数字观察树的旋转情况。只要数字在树里面,那么每次访问数字最终都能到根节点上。 虽然我的代码跑这个结果和书里面的不太一样,但是我觉得我的更好(不过没有完整测试,可能还是有bug)
def uplift_left(r, rl):
r.left = rl.right
rl.right = r
return rl
def uplift_right(r, rr):
r.right = rr.left
rr.left = r
return rr
def left_zig_zig(r, rl, rll):
rl.left = rll.right
r.left = rl.right
rl.right = r
rll.right = rl
return rll
def left_zig_zag(r, rl, rll):
rl.right = rll.left
r.left = rll.right
rll.left = rl
rll.right = r
return rll
def right_zig_zig(r, rr, rrr):
rr.right = rrr.left
r.right = rr.left
rr.left = r
rrr.left = rr
return rrr
def right_zig_zag(r, rr, rrl):
r.right = rrl.left
rr.left = rrl.right
rrl.left = r
rrl.right = rr
return rrl
def access_tree_node(r: TreeNode, val):
if r is None or r.val == val:
return r
if val < r.val:
rl = r.left
if rl is None:
return r
elif val == rl.val:
return uplift_left(r, rl)
elif val < rl.val:
rll = rl.left
if rll is None:
return uplift_left(r, rl)
else:
rll = rl.left = access_tree_node(rll, val)
return left_zig_zig(r, rl, rll)
else:
rlr = rl.right
if rlr is None:
return uplift_left(r, rl)
else:
rlr = rl.right = access_tree_node(rlr, val)
return left_zig_zag(r, rl, rlr)
else:
rr = r.right
if rr is None:
return r
elif val == rr.val:
return uplift_right(r, rr)
elif val < rr.val:
rrl = rr.left
if rrl is None:
return uplift_right(r, rr)
else:
rrl = rr.left = access_tree_node(rrl, val)
return right_zig_zag(r, rr, rrl)
else:
rrr = rr.right
if rrr is None:
return uplift_right(r, rr)
else:
rrr = rr.right = access_tree_node(rrr, val)
return right_zig_zig(r, rr, rrr)
raise RuntimeError('unexpected condition')
为了能够很好地观察树的变化,最好能够以可视化的方式将树展示出来(最好是图片或者是动画,而不是字符)。 所以我编写了一个工具,可以将树输出成为.dot格式,然后使用graphviz输出成为图片。另外graphviz不支持 输出多个graph, 如果要将多个graph放在一个图片里面需要使用gvpack工具。具体代码可以看 tree_util.py.
驱动方式也很简单:
- 创建一个stream/stringio
- 调用 `tree_to_dot` 传入节点,graph名称和stream
- 最后将stream输出并且调用gvpack+graphviz产生图片。
def test_pprint_tree():
root = None
stream = StringIO()
for val in range(32, 0, -1):
root = insert_tree_node(root, TreeNode(val))
for x in (1,2,3,4):
root = access_tree_node(root, x)
tree_to_dot(root, 'find {}'.format(x), stream)
dot_to_graph('/tmp/example', stream.getvalue(), type='png')
test_pprint_tree()