7. Binary Tree Serialization-二叉树的序列化和反序列化(BFS)

本文介绍了一种二叉树序列化和反序列化的算法实现,使用BFS遍历方式,通过字符串形式存储和恢复二叉树结构。文章提供了具体的代码示例,帮助读者理解和应用。

Description

设计一个算法,并编写代码来序列化和反序列化二叉树。将树写入一个文件被称为“序列化”,读取文件后重建同样的二叉树被称为“反序列化”。

如何反序列化或序列化二叉树是没有限制的,你只需要确保可以将二叉树序列化为一个字符串,并且可以将字符串反序列化为原来的树结构。

 注意事项

There is no limit of how you deserialize or serialize a binary tree, LintCode will take your output of serialize as the input of deserialize, it won't check the result of serialize.

样例

给出一个测试数据样例, 二叉树{3,9,20,#,#,15,7},表示如下的树结构:

  3
 / \
9  20
  /  \
 15   7

我们的数据是进行BFS遍历得到的。当你测试结果wrong answer时,你可以作为输入调试你的代码。

你可以采用其他的方法进行序列化和反序列化。

Solution

算法思路:

    1. 序列化:

    (1)BFS法访问二叉树左右节点,如果节点为空则序列化为#,如果不为空就将他的值加入StringBuilder中,且每个字符后面用逗号隔开。

    (2)最后移除末尾多余的“#”和“,”

    2. 反序列化:

    (1)先将序列化的字符串,取出中间部分,并用“,”分割为字符数组

    (2)取出第一个字符转换为int,并放入新生成的根节点中,创建队列,并加入root

    (3)循环字符串数组,如果不是#,就转换后左右依次添加为队首的左右儿子,并加入队列等待添加他的儿子节点

    (4)走到右节点之后,说明当前节点的左右节点信息已添加完毕,将待添加的TreeNode从队列中移除,准备添加下一节点

/**
 * Definition of TreeNode:
 * public class TreeNode {
 *     public int val;
 *     public TreeNode left, right;
 *     public TreeNode(int val) {
 *         this.val = val;
 *         this.left = this.right = null;
 *     }
 * }
 */


public class Solution {
    /**
     * This method will be invoked first, you should design your own algorithm 
     * to serialize a binary tree which denote by a root node to a string which
     * can be easily deserialized by your own "deserialize" method later.
     */
    public String serialize(TreeNode root) {
        // write your code here
        //序列化,BFS访问二叉树左右节点,如果节点为空则序列化为#
        //如果不为空就将他的值加入StringBuilder中,每个字符后面用逗号隔开
        //移除末尾多余的“#”和“,”
        if (root == null) {
            return "{}";
        }
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            if (node == null) {
                sb.append("#");
            } else {
                sb.append(node.val);
                queue.offer(node.left);
                queue.offer(node.right);
            }
            sb.append(",");
        }
        
         // remove tailing sharp and comma
         for (int i = sb.length() - 1; i >= 0; i--) {
             if (sb.charAt(i) == '#' || sb.charAt(i) == ',') {
                 sb.deleteCharAt(i);
             } else {
                 break;
             }
         }
         sb.append("}");
         return sb.toString();
    }

    /**
     * This method will be invoked second, the argument data is what exactly
     * you serialized at method "serialize", that means the data is not given by
     * system, it's given by your own serialize method. So the format of data is
     * designed by yourself, and deserialize it here as you serialize it in 
     * "serialize" method.
     */
    public TreeNode deserialize(String data) {
        // write your code here
        //反序列化
        //先将序列化的字符串,取出中间部分,并用“,”分割为字符数组
        //取出第一个字符转换为int,并放入新生成的根节点中
        if (data.equals("{}")) {
            return null;
        }
        //substring(a,b);左闭右开区间[a,b)
        String[] values = data.substring(1, data.length() - 1).split(",");   
        TreeNode root = new TreeNode(Integer.parseInt(values[0]));
        
        //创建队列,并加入root
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        
        //循环字符串数组,如果不是#就转换后,左右依次添加到队首的左右有儿子
        //并加入队列等待添加孙子节点
        boolean isLeftChild = true;
        for (int i = 1; i < values.length; i++) {
            if (!"#".equals(values[i])) {
                TreeNode child = new TreeNode(Integer.parseInt(values[i]));
                if (isLeftChild) {
                    queue.peek().left = child;
                } else {
                    queue.peek().right = child;
                }
                queue.offer(child);
            }
            //走到右节点之后,说明当前节点的左右节点信息已添加完毕
            //将待添加的TreeNode从队列中移除,准备添加下一节点
            if (!isLeftChild) {
                queue.poll();
            }
            //左右节点信息取反
            isLeftChild = !isLeftChild;
        }
        return root;
    }
}

<think>我们要求使用System.Runtime.Serialization.Formatters.Binary对数组进行序列化反序列化。 根据引用[2]引用[3]中的示例,我们可以总结出如下步骤: 1. 创建BinaryFormatter对象 2. 使用文件流或内存流 3. 调用Serialize方法将数组序列化到流中 4. 调用Deserialize方法从流中反序列化得到数组 注意:数组类型本身是可序列化的,因为它是System.Array的派生类型,而System.Array标记了[Serializable]特性。 下面我们将分别展示两种方式: 方式一:序列化到文件(适合持久化存储) 方式二:序列化到内存流(适合网络传输或进程间通信) 同时,我们也会展示如何处理基本数据类型数组自定义对象数组。 另外,根据引用[5],我们也可以看到如何将序列化的结果转换为byte[],这对于内存中处理非常有用。 注意:.NET 5.0及以上版本中,BinaryFormatter已被标记为过时,因此在使用时要注意环境。 示例代码将包括: 1. 整型数组的序列化反序列化(文件方式) 2. 字符串数组的序列化反序列化(内存流方式,并转换为字节数组) 3. 自定义对象数组的序列化反序列化(文件方式) 自定义对象需要标记[Serializable]特性。 我们首先定义自定义对象类(如Person)用于示例。 </think>以下是使用 `System.Runtime.Serialization.Formatters.Binary` 进行数组序列化反序列化的完整方法及示例代码: ### 数组序列化核心步骤 ```mermaid graph TD A[创建数组对象] --> B[创建文件流或内存流] B --> C[实例化BinaryFormatter] C --> D[调用Serialize写入流] D --> E[保存为文件或字节数组] ``` ### 基本类型数组示例(整型数组) ```csharp using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; class ArraySerializationDemo { static void Main() { int[] numbers = { 10, 20, 30, 40, 50 }; // 序列化到文件 BinarySerializeToFile("numbers.dat", numbers); // 从文件反序列化 int[] deserializedNumbers = BinaryDeserializeFromFile<int[]>("numbers.dat"); Console.WriteLine("反序列化结果:"); foreach (int num in deserializedNumbers) { Console.Write(num + " "); } } // 序列化到文件 static void BinarySerializeToFile<T>(string filePath, T obj) { using (FileStream fs = new FileStream(filePath, FileMode.Create)) { new BinaryFormatter().Serialize(fs, obj); } } // 从文件反序列化 static T BinaryDeserializeFromFile<T>(string filePath) { using (FileStream fs = new FileStream(filePath, FileMode.Open)) { return (T)new BinaryFormatter().Deserialize(fs); } } } ``` ### 内存流序列化(字节数组转换) ```csharp // 序列化数组到字节数组 static byte[] SerializeArrayToBytes<T>(T[] array) { using (MemoryStream ms = new MemoryStream()) { new BinaryFormatter().Serialize(ms, array); return ms.ToArray(); // 返回二进制数据[^3] } } // 从字节数组反序列化 static T[] DeserializeArrayFromBytes<T>(byte[] data) { using (MemoryStream ms = new MemoryStream(data)) { return (T[])new BinaryFormatter().Deserialize(ms); } } // 使用示例: string[] fruits = { "苹果", "香蕉", "橙子" }; // 序列化为字节数组 byte[] fruitData = SerializeArrayToBytes(fruits); // 反序列化还原数组 string[] restoredFruits = DeserializeArrayFromBytes<string>(fruitData); ``` ### 自定义对象数组示例 ```csharp [Serializable] public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } } // 创建并序列化对象数组 Product[] products = { new Product { Id = 1, Name = "键盘", Price = 299.99m }, new Product { Id = 2, Name = "鼠标", Price = 149.99m } }; BinarySerializeToFile("products.dat", products); // 反序列化对象数组 Product[] loadedProducts = BinaryDeserializeFromFile<Product[]>("products.dat"); ``` ### 关键注意事项 1. **类型兼容性要求** - 基本类型数组(`int[]`, `string[]` 等)可直接序列化 - 自定义对象数组要求元素类型标记 `[Serializable]` 特性[^1] - 数组维度支持:一维/多维数组均可序列化 2. **性能优化** ```csharp // 预分配内存流大小提升性能(适用于大型数组) byte[] SerializeLargeArray<T>(T[] array) { // 预估初始大小 = 数组长度 * 预估元素大小 + 头部信息 int estimatedSize = array.Length * 128 + 1024; using (MemoryStream ms = new MemoryStream(estimatedSize)) { new BinaryFormatter().Serialize(ms, array); return ms.ToArray(); } } ``` 3. **异常处理** ```csharp try { var data = DeserializeArrayFromBytes<int>(byteArray); } catch (SerializationException ex) { Console.WriteLine($"反序列化失败: {ex.Message}"); } catch (InvalidCastException ex) { Console.WriteLine($"类型转换错误: {ex.Message}"); } ``` ### 应用场景建议 1. **游戏开发**:保存游戏关卡中的对象位置数组 2. **科学计算**:传输大型数值计算数据集 3. **配置存储**:保存用户自定义的对象集合 4. **网络通信**:高效传输结构化数据包(相比JSON体积减少约40%)[^3]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值