序列化二叉树(剑指OFFER 面试题37)

题目描述

请实现两个函数,分别用来序列化和反序列化二叉树。

思路

看到这个题,“反序列化二叉树”,就想起了前面解决的面试题“重建二叉树”,从前序遍历序列和中序遍历序列中构造一个二叉树。受此启发,可以先把一个二叉树序列化成一个前序遍历序列和一个中序遍历序列,然后在反序列化时通过这两个序列就能重构出二叉树了。

但是,从上述思路可以看出,该方法有两个缺陷:
1、该方法要求二叉树不能有数值重复的节点;
2、只有当两个序列中所有数据都读出后才能开始反序列化。
如果两个遍历序列的数据是从一个流里读出来的,那么可能需要等待很长的时间。

那么换个思路:如果二叉树的序列化是从根节点开始的,那么相应的反序列化在根节点的数值读出来的时候就可以开始了。因此,我们可以根据前序遍历的顺序来序列化二叉树,因为前序遍历是从根节点开始的。在遍历二叉树碰到null指针时,这些null指针序列化为一个特殊的字符。另外,节点的数值之间要用一个特殊字符隔开(在这里我用的是‘,’)。
根据上述规则,下图的二叉树可以序列化成字符串“8,6,5,null,null,7,null,null,10,9,null,null,11,null,null”
这里写图片描述

用C#的代码可以实现成如下形式:

/// <summary>
/// 按先序遍历顺序遍历节点并序列化
/// </summary>
/// <param name="root">二叉树的根节点</param>
/// <returns>返回序列化后的字符串</returns>
public string Serialize(TreeNode root)
{
    string str = "";
    //如果节点为空,给序列化字符串加入一个空字符串表示碰到了叶子节点
    if (root == null)
    {
        str = str.Insert(str.Length, "null,");
        return str;
    }

    //如果节点不为空,把节点序列化,遍历左右子节点
    str = str.Insert(str.Length, root.val.ToString() + ",");

    str = str.Insert(str.Length, Serialize(root.left));
    str = str.Insert(str.Length, Serialize(root.right));

    return str;
}

接着,以字符串“8,6,5,null,null,7,null,null,10,9,null,null,11,null,null”为例分析如何反序列化二叉树。第一个读出的数字是8,由于前序遍历是从根节点开始的,所以8是根节点的值。接下来读出来的是数字6,根据前序遍历的规则,这个节点一定是节点8的左子节点的值,同样的,接下来的数字5是值为6的节点的左子节点。接着,从序列化字符串里读出两个字符串“null”,这表明值为5的节点的左右子节点均为空,因此它是一个叶节点。接下来回到值为6的节点,重建它的右子节点,下一个节点值是7,由于接下来两个字符串是“null”,所以表示值为7的节点也是叶节点,返回到节点6,发现左右子树都重构完了,返回到根节点,反序列化根节点的右子树。

下一个序列化字符串中的数字是10,同样的,这个数字一定是根节点的右子节点的值。再读下一个,发现不是空,说明有左子树,所以数字9就是节点10的左子节点的值,接着两个null表示9为一个叶节点,返回上面节点10,重构右子节点,发现序列剩下的字符为“11,null,null”,所以11为10的右子节点且整个二叉树重构完毕。

以上反序列化重构二叉树过程可以用以下C#代码实现:

/// <summary>
/// 反序列重构二叉树,返回重构二叉树的根节点
/// </summary>
/// <param name="str">序列</param>
/// <returns>根节点</returns>
public TreeNode Deserialize(string str)
{
    //把字符串切割成字符数组,保存每个节点的值
    string[] ch = str.Split(',');
    //index表示数组下标
    int index = 0;
    TreeNode pNode = CreateNode(ch, ref index);
    return pNode;
}

/// <summary>
/// 通过切割的序列数组重构二叉树
/// </summary>
/// <param name="ch">序列被切割成的数组</param>
/// <param name="index">字符串数组下标索引</param>
/// <returns>二叉树根节点</returns>
private TreeNode CreateNode(string[] ch, ref int index)
{
    //如果当前节点为空或者下标索引大于数组长度,返回空
    if (ch[index].Equals("null") || index >= ch.Length)
    {
        return null;
    }
    //把数组中第index个值转化为int整数,然后重构节点
    TreeNode pNode = new TreeNode(int.Parse(ch[index]));
    //如果index做对应的下一个值不是null,则添加为左子节点
    if (!ch[++index].Equals("null"))
    {
        pNode.left = CreateNode(ch, ref index);
    }
    //如果index做对应的下一个值不是null,则添加为右子节点
    if (!ch[++index].Equals("null"))
    {
        pNode.right = CreateNode(ch, ref index);
    }
    //返回当前创建的节点
    return pNode;
}

总结

前面的序列化和反序列化过程可以发现,我们把二叉树分成三部分:根节点、左子树、右子树。我们在处理他的根节点之后再分别处理它的左右子树。这是典型的把问题递归分解然后逐个解决的过程。
最近博主也是在看剑指OFFER里解决面试题的思路这章,在解决问题中,就有这么一个思路:分解让复杂问题简单化。我们以后如果遇到问题,可以想想能否把问题概括一下,分为几个步骤,然后再细化这些步骤,逐个击破~加油哦~正在学习的小伙伴们,为了你们期待的薪资和OFFER,拼尽剩余的所有力气吧

猜你喜欢

转载自blog.csdn.net/qq_33575542/article/details/80815812