树的序列化

目录

        概述

         树的遍历

       带子树边界的先序序列

        其他特殊的树/图的序列化


        概述

        在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(有穷状态自动机),有兴趣的读者自己去研究。

  • 14
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

__空无一人__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值