目录
概述
在python里面有个模块pickle专门实施对对像的序列化,序列化之后得到的是一个二进制的字节流,当然我们可以用它来对树这种数据结构来进行序列化,不过如果树的结点的value是字符串,那么可以对树进行一个轻量级的序列化,这里来讲讲这个。
树的遍历
树的遍历大家都很清楚,它是树的序列化的一种形式,为了简化问题,这里只谈二叉树,因为多叉树可以以孩子兄弟链表的形式表示成一棵二叉树。根据树的中序和先序/后序遍历可以唯一确定一棵树,那么对于一棵树,如何通过它们来重建树呢?
比方说如下这棵树:
分析一下不难发现规律,假设我们有一棵树的以先序和中序,首先根据先序可以确定树的根,以这个根去中序中划分得到左右子树的中序,再以左右子树的长度(中序序列的长度)到先序里面划分得到左右子树的先序,进而得到左右子树的根,把它们作为树的根的左右孩子,然后再对左右子树的先序和中序继续进行上述操作,依次类推,最终就能建成一棵树。
class Node:
leftchld = None
rightchld = None
def __init__(self, val = "null"):
self.val = val
def create_tree(preorder, inorder):
preorder = preorder.split()
inorder = inorder.split()
def split_inorder(inorder_range, val):
for i in range(*inorder_range):
if inorder[i] == val:
break
return (inorder_range[0], i), (i + 1, inorder_range[1])
def split_preorder(preorder_range, leftlen, rightlen):
left_range = preorder_range[0] + 1, preorder_range[0] + 1 + leftlen
right_range = left_range[1], left_range[1] + rightlen
return left_range, right_range
def create(preorder_range, inorder_range):
counts = inorder_range[1] - inorder_range[0]
if counts <= 0:
return None
val = preorder[preorder_range[0]]
parent = Node(val)
inorder_left_range, inorder_right_range = split_inorder(inorder_range, val)
preorder_left_range, preorder_right_range = \
split_preorder(preorder_range, inorder_left_range[1] - inorder_left_range[0], \
inorder_right_range[1] - inorder_right_range[0])
parent.leftchld = create(preorder_left_range, inorder_left_range)
parent.rightchld = create(preorder_right_range, inorder_right_range)
return parent
return create((0, len(preorder)), (0, len(inorder)))
root = create_tree("A B C D E F G", "C D B E A F G")
def preorder_traverse(root):
if not root:
return
yield root.val
yield from preorder_traverse(root.leftchld)
yield from preorder_traverse(root.rightchld)
def inorder_traverse(root):
if not root:
return
yield from inorder_traverse(root.leftchld)
yield root.val
yield from inorder_traverse(root.rightchld)
print(" ".join([i for i in preorder_traverse(root)]))
print(" ".join([i for i in inorder_traverse(root)]))
带子树边界的先序序列
树的遍历这种形式的序列化,不好之处在于需要两个遍历序列来表示,有没有一个序列的表示呢?
是有的,让我们再来看看根据树的两个遍历序列来rebuild一棵树的时候在做什么,稍微观察一下就会发现,它是在不断得根据子树的中序来确定子树的先序边界,既如此,那么如果我们在生成树的先序时就加上这样的边界,那么不就可以只根据带子树边界的先序序例唯一表示一棵树,就像A(B(C(,D),E), F(,G))这种形式。生成带子树边界的先序序列的逻辑很简单,在树的先序遍历的逻辑上简单修改一下即得。
def tree_serialize(root):
treestr = ""
if root is None:
return treestr
if root.val != "null":
treestr += root.val
if root.leftchld is None and root.rightchld is None:
return treestr
treestr += "("
treestr += tree_serialize(root.leftchld)
treestr += ","
treestr += tree_serialize(root.rightchld)
treestr += ")"
return treestr
print(tree_serialize(root))
如何根据带子树边界的先序序列来重建一棵树呢?因为先序序列是按照根、左子树、右子树的顺序从左至右排列,从左至右扫描序列重建树必然也是按照先重建根结点,再重建左子树,最后重建右子树的顺序来实现,用递归去实现很简单。
def create_tree(treestr):
cursor = 0
def getval():
nonlocal cursor
start = cursor
while cursor < len(treestr):
if treestr[cursor] in ('(', ')', ','):
break
cursor += 1
return treestr[start:cursor].strip()
def create():
nonlocal cursor
val = getval()
print(val)
if not val:
return None
root = Node(val)
#one node tree
if cursor >= len(treestr):
return root
#leaf node
if treestr[cursor] in (',', ')'):
return root
if treestr[cursor] == '(':
cursor += 1
root.leftchld = create()
if treestr[cursor] == ',':
cursor += 1
root.rightchld = create()
if treestr[cursor] == ')':
cursor += 1
return root
return create()
root = create_tree("A(B(C(,D),E),F(,G))")
print(" ".join([i for i in preorder_traverse(root)]))
print(" ".join([i for i in inorder_traverse(root)]))
带子树边界的先序序列算法很容易推广到多叉树的情形,对上面代码稍作修改就行。
其他特殊的树/图的序列化
还有一些特殊的树的序列表达式,比如四则运算表达式,也可以根据它们来rebuild为一棵语法树,还有一些序列表达式,根据它们来build得到一个图,比如根据正则表达式build得到一个FA(有穷状态自动机),有兴趣的读者自己去研究。