二叉树的序列化与反序列化
序列化与反序列化
- 序列化:将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境。
- 反序列化:将序列化得到的连续比特位重构为原来的数据结构。
特点:
- 可还原性。先序列化,再反序列化,所得结果与初始一致。
- 配套性。因为序列化过程涉及分隔符的使用,所以一般针对某数据结构的序列化和反序列化算法是配套的。
LeetCode 原题链接
题目分析:
给出如下二叉树的数据结构定义:
type TreeNode struct {Val intLeft *TreeNodeRight *TreeNode}
给出一个根节点表示的二叉树,要求实现以下方法:
- func (this Codec) serialize(root TreeNode) string,即把二叉树序列化为字符串。
- func (this Codec) deserialize(data string) TreeNode,即把序列化得到的字符串反序列化为二叉树。
DFS 方法
序列化思路
序列化的过程中要插入一些分隔符(如逗号)以及用字符(如 # )替代 nil 节点,这是为了方便反序列化。
利用深度优先搜索(DFS)的方法可以这么思考(假设采用前序遍历):
- 分:即拆分问题。序列化一棵二叉树,相当于根节点+ 序列化其左子树 + 序列化其右子树。
- 治:序列化左 / 右子树是原问题的一个子问题。
- 合: 根节点 + 左子树序列化结果 +右子树序列化结果
反序列化思路
首先根据刚才的分隔符把序列化得到的字符串分隔为字符串数组。
由于是采用前序遍历实现序列化,因此反序列化也要按照前序遍历的顺序进行。
- 遇到 # 说明是一个 nil 节点。
- 遇到非 # 说明是非 nil 节点,至少还有左右孩子,继续递归构建。
代码实现
type Codec struct {
ser string
deser []string
}
func Constructor() Codec {
return Codec{}
}
// Serializes a tree to a single string.
func (this *Codec) serialize(root *TreeNode) string {
if root == nil {
return "#"
}
return strconv.Itoa(root.Val) + "," + this.serialize(root.Left) + "," + this.serialize(root.Right)
}
// Deserializes your encoded data to tree.
func (this *Codec) deserialize(data string) *TreeNode {
list := strings.Split(data, ",")
return deserializeHelper(&list)
}
func deserializeHelper(list *[]string) *TreeNode {
val, err := strconv.Atoi((*list)[0])
*list = (*list)[1:]
if err != nil {
return nil
}
root := &TreeNode{Val: val}
root.Left = deserializeHelper(list)
root.Right = deserializeHelper(list)
return root
}
/*
func deserializeHelper(list []string) *TreeNode {
val, err := strconv.Atoi(list[0])
list = list[1:]
if err != nil {
return nil
}
root := &TreeNode{Val: val}
root.Left = deserializeHelper(list)
root.Right = deserializeHelper(list)
return root
}
*/
非常值得注意的是:deserializeHelper 的参数是切片指针!如果不用,则会出错(如注释掉的同名方法那样)。
原因:因为递归涉及多层函数调用,若采用切片指针,则下一层的 *list = (*list)[1:] 的作用是全局的。而如果不用切片指针,list = list[1:] 的作用仅仅是当前层。
BFS 方法
序列化思路
用一个队列实现层次遍历,结果保存在 ser 中,初始根节点入队且添加到 ser:
- 从队列中取出一个节点,若该节点非 nil,添加到 ser,同时将其左右节点入队(nil 也要入队)。
- 若该节点为 nil,则添加 # 到 ser 中。
反序列化思路
用 ser 按层次遍历构树即可。
代码实现
// Serializes a tree to a single string.
func (this *Codec) serialize(root *TreeNode) string {
if root == nil {
return ""
}
ser := []string{}
queue := list.New()
queue.PushBack(root)
for queue.Len() > 0 {
node := queue.Front().Value.(*TreeNode)
queue.Remove(queue.Front())
if node != nil {
ser = append(ser, strconv.Itoa(node.Val))
queue.PushBack(node.Left)
queue.PushBack(node.Right)
} else {
ser = append(ser, "#")
}
}
return strings.Join(ser, ",")
}
// Deserializes your encoded data to tree.
func (this *Codec) deserialize(data string) *TreeNode {
if len(data) <= 0 { return nil }
temp := strings.Split(data, ",")
val, _ := strconv.Atoi(temp[0])
root := &TreeNode{Val: val, Left: nil, Right: nil}
queue := list.New()
queue.PushBack(root)
i := 1
for i < len(temp) {
node := queue.Front().Value.(*TreeNode)
queue.Remove(queue.Front())
leftVal := temp[i]
rightVal := temp[i+1]
if leftVal != "#" {
v, _ := strconv.Atoi(leftVal)
leftNode := &TreeNode{Val: v, Left: nil, Right: nil}
node.Left = leftNode
queue.PushBack(leftNode)
}
if rightVal != "#" {
v, _ := strconv.Atoi(rightVal)
rightNode := &TreeNode{Val: v, Left: nil, Right: nil}
node.Right = rightNode
queue.PushBack(rightNode)
}
i += 2
}
return root
}
