小白学算法-数据结构和算法教程: 序列化和反序列化二叉树_二叉树

序列化和反序列化二叉树

序列化是将树存储在文件中,以便以后可以恢复。必须保持树的结构。反序列化是从文件中读回树。

小白学算法-数据结构和算法教程: 序列化和反序列化二叉树_序列化_02

以下是该问题的一些更简单的版本:

如果给定的树是二叉搜索树?

如果给定的二叉树是二叉搜索树,我们可以通过存储前序或后序遍历来存储它。对于二叉搜索树,仅前序或后序遍历就足以存储结构信息。 

如果给定的二叉树是完全树?

如果除了最后一层之外的所有层都被完全填满,并且最后一层的所有节点都尽可能保留(二叉堆是完整的二叉树),则二叉树是完整的。对于完整的二叉树来说,层序遍历足以存储树。我们知道第一个节点是根节点,接下来的两个节点是下一级节点,接下来的四个节点是第二级节点,依此类推。 

如果给定的二叉树是满树?

完整二叉树是二叉树,其中每个节点都有 0 或 2 个子节点。序列化此类树很容易,因为每个内部节点都有 2 个子节点。我们可以简单地存储前序遍历,并为每个节点存储一个位来指示该节点是内部节点还是叶节点。

普通二叉树如何存储?

一个简单的解决方案是存储中序和预序遍历。 

该解决方案需要二叉树大小两倍的空间。我们可以通过存储预序遍历和 NULL 指针标记来节省空间。 

  • 存储每个节点的所有可能的子节点。
  • 如果没有子节点,则为该子节点推送-1 。
  • 将这个前序遍历放入文件中。

例子:

输入:
     12
    /
13
输出: 12 13 -1 -1 -1

输入:
      20
    / \
8 22 
输出: 20 8 -1 -1 22 -1 -1 

输入:
         20
       /    
     8     
   / \
4 12 
    / \
10 14
输出: 20 8 4 -1 -1 12 10 -1 -1 14 -1 -1 -1 

输入:
            20
           /    
         8     
       /
   10
  /
5
输出: 20 8 10 5 -1 -1 -1 -1 -1 

输入:
          20
            \
             8
              \   
               10
                 \
                  5   

输出: 20 -1 8 -1 10 -1 5 -1 -1

如何反序列化上面的序列化呢?

反序列化可以通过简单地从文件中逐一读取数据并继续添加子项直到达到 -1 来完成。如果两个子级都为 NULL,则返回到父级。

下面是上述代码的实现:

class TreeNode:
	def __init__(self, x):
		self.val = x
		self.left = None
		self.right = None

class BinaryTree:
	def __init__(self):
		self.root = None

	# Encodes a tree to a single string.
	def serialize(self, root):
		if not root:
			return None

		stack = [root]
		l = []

		while stack:
			t = stack.pop()

			# If current node is NULL, store marker
			if not t:
				l.append("#")
			else:
				# Else, store current node
				# and recur for its children
				l.append(str(t.val))
				stack.append(t.right)
				stack.append(t.left)

		return ",".join(l)

	# Decodes your encoded data to tree.
	def deserialize(self, data):
		if not data:
			return None

		global t
		t = 0
		arr = data.split(",")
		return self.helper(arr)

	def helper(self, arr):
		global t
		if arr[t] == "#":
			return None

		# Create node with this item
		# and recur for children
		root = TreeNode(int(arr[t]))
		t += 1
		root.left = self.helper(arr)
		t += 1
		root.right = self.helper(arr)
		return root

	# A simple inorder traversal used
	# for testing the constructed tree
	def inorder(self, root):
		if root:
			self.inorder(root.left)
			print(root.val, end=" ")
			self.inorder(root.right)

# Driver code
if __name__ == '__main__':
	# Construct a tree shown in the above figure
	tree = BinaryTree()
	tree.root = TreeNode(20)
	tree.root.left = TreeNode(8)
	tree.root.right = TreeNode(22)
	tree.root.left.left = TreeNode(4)
	tree.root.left.right = TreeNode(12)
	tree.root.left.right.left = TreeNode(10)
	tree.root.left.right.right = TreeNode(14)

	serialized = tree.serialize(tree.root)
	print("Serialized view of the tree:")
	print(serialized)
	print()

	# Deserialize the stored tree into root1
	t = tree.deserialize(serialized)

	print("Inorder Traversal of the tree constructed from serialized String:")
	tree.inorder(t)

上述解决方案需要多少额外空间?

如果有 n 个键,则上述解决方案需要 n+1 个标记,在键很大或键具有与其关联的大数据项的情况下,这可能比简单解决方案(存储键两次)更好。

可以进一步优化吗?

上述解决方案可以通过多种方式进行优化。如果我们仔细观察上面的序列化树,我们可以观察到所有叶节点都需要两个标记。一种简单的优化是 

  • 为每个节点存储一个单独的位,以指示该节点是内部节点还是外部节点。 
  • 这样我们就不必为每个叶节点存储两个标记,因为叶子可以通过额外的位来识别。 
  • 我们仍然需要一个标记来标记具有一个子节点的内部节点。 

例如,在下图中,字符'用于指示内部节点集位,' / '用作NULL标记。

小白学算法-数据结构和算法教程: 序列化和反序列化二叉树_子节点_03

请注意,二叉树中的叶子节点总是多于内部节点(叶子节点的数量是内部节点的数量(度为 2)加 1,因此这种优化是有意义的。

如何序列化N叉树?

N 叉树中,没有指定的左子节点或右子节点。我们可以为每个节点存储一个“子节点结束”标记。下图显示了序列化,其中“)”用作子标记的末尾。我们很快就会介绍 N 叉树的实现。

小白学算法-数据结构和算法教程: 序列化和反序列化二叉树_二叉树_04