【问题标题】:How to Serialize Binary Tree如何序列化二叉树
【发布时间】:2011-06-04 10:28:58
【问题描述】:

我今天去参加一个面试,我被要求序列化一棵二叉树。我实现了一种基于数组的方法,其中节点 i 的子节点(按级别顺序遍历编号)位于左孩子的 2*i 索引和右孩子的 2*i + 1 处。面试官似乎或多或少很高兴,但我想知道序列化到底是什么意思?它是否特别涉及扁平化树以写入磁盘,或者序列化树是否还包括将树转换为链表,例如。另外,我们如何将树扁平化为(双)链表,然后重建它?你能从链表中重新创建树的确切结构吗?

【问题讨论】:

  • 大多数时候面试官会问这个问题,看看你是否会采用递归方法。基本上,你为叶节点编写序列化,然后为父节点调用序列化(左),输出当前节点,序列化(右)。这是一个优雅的解决方案,你可以让面试官知道你上了一门不错的算法课。
  • 感谢大家提供的有用信息。

标签: serialization tree binary-tree flatten binary-search-tree


【解决方案1】:

序列化反序列化二叉树:

public class BTSerialization {

    public static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;

        TreeNode(int x) {
            val = x;
        }
    }

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        StringBuilder sb = new StringBuilder();
        serialize(root, sb);
        return sb.toString();
    }

    private void serialize(TreeNode root, StringBuilder sb) {
        if (root == null) {
            sb.append("()");
            return;
        }
        sb.append('(').append(root.val);
        serialize(root.left, sb);
        serialize(root.right, sb);
        sb.append(')');
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if ("()".equals(data)) return null;
        data = data.substring(1, data.length() - 1); // Unwrap the two parenthesis (root(left)(right))
        int i = 0;
        while (data.charAt(i) != '(') i++;
        TreeNode root = new TreeNode(Integer.parseInt(data.substring(0, i)));
        data = data.substring(i);
        i = -1;
        int open = 0;
        while (true) { // Find left child- starts with parenthesis
            i++;
            if (data.charAt(i) == '(') open++;
            else if (data.charAt(i) == ')') {
                open--;
                if (open == 0) break;
            }
        }
        root.left = deserialize(data.substring(0, i + 1));
        data = data.substring(i + 1); // The rest is right child- starts with parenthesis
        root.right = deserialize(data);
        return root;
    }

    public static void main(String[] args) {
        BTSerialization b = new BTSerialization();
        TreeNode node1 = new TreeNode(1);
        TreeNode node2 = new TreeNode(2);
        TreeNode node3 = new TreeNode(3);
        TreeNode node4 = new TreeNode(4);
        TreeNode node5 = new TreeNode(5);
        node1.left = node2;
        node1.right = node3;
        node3.left = node4;
        node3.right = node5;
        TreeNode root = b.deserialize(b.serialize(node1));
        System.out.println();
    }

}

序列化反序列化 N 叉树: 使用此代码,您可以序列化、反序列化任何 n 叉树。基本上,二叉树是二叉树。 N表示整棵树中所有节点的最大子节点数。

public class CodecNry {

    class Node {
        public int val;
        public List<Node> children;
        public Node(int _val, List<Node> _children) {
            val = _val;
            children = _children;
        }
    }

    public String serialize(Node root) {
        if (root == null) return ""; // Serialization base case
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(root.val).append(",").append(root.children.size()); // Add root val+,+num of children
        for (Node child : root.children)
            stringBuilder.append(",").append(serialize(child)); // Add children recursively, one by one
        return stringBuilder.toString(); // Return result
    }

    int i;

    public Node deserialize(String data) {
        if (data.isBlank()) return null; // Base case root is null
        i = 0; // The index on the tokens
        return recursiveDeserialize(data.split(",")); // Recursively build the tree from the tokenized string
    }

    private Node recursiveDeserialize(String[] nums) {
        if (i == nums.length) return null; // Base case- no more child
        Node root = new Node(Integer.parseInt(nums[i++]), new ArrayList<>()); // Build the root
        int childrenCount = Integer.parseInt(nums[i++]); // Get number of children
        for (int j = 0; j < childrenCount; j++) { // Add children recursively one by one
            Node child = recursiveDeserialize(nums);
            if (child != null) root.children.add(child); // If child is not null, add it to the children of root
        }
        return root; // Return the root of the tree
    }

}

【讨论】:

    【解决方案2】:

    将其转换为大小为 (2n + 1) 的数组 每个左儿子和右儿子将相应地代替 (2 * 节点号) 和 ((2 * 节点号) + 1。

    使用 https://www-inst.eecs.berkeley.edu//~cs61bl/r/cur/trees/array-repr.html?topic=lab20.topic&step=1&course=

    【讨论】:

      【解决方案3】:

      这是 Python 中的一个较晚的答案。它使用(深度优先)预序列化并返回strings 的列表。反序列化返回树。

      class Node:
          def __init__(self, val, left=None, right=None):
              self.val = val
              self.left = left
              self.right = right
      
      
      # This method serializes the tree into a string
      def serialize(root):
          vals = []
          def encode(node):
              vals.append(str(node.val))
              if node.left is not None:
                  encode(node.left)
              else:
                  vals.append("L")
              if node.right is not None:
                  encode(node.right)
              else:
                  vals.append("R")
          encode(root)
          print(vals)
          return vals
      
      
      # This method deserializes the string back into the tree
      def deserialize(string_list):
          def create_a_tree(sub_list):
              if sub_list[0] == 'L' or sub_list[0] == 'R':
                  del sub_list[0]
                  return None
              parent = Node(sub_list[0])
              del sub_list[0]
              parent.left = create_a_tree(sub_list)
              parent.right = create_a_tree(sub_list)
              return parent
          if len(string_list) != 0:
              root_node = create_a_tree(string_list)
          else:
              print("ERROR - empty string!")
              return 0
          return root_node
      

      测试:

      tree1 = Node('root', Node('left'), Node('right'))
      t = deserialize(serialize(tree1))
      print(str(t.right.val))
      

      【讨论】:

        【解决方案4】:

        我一直试图了解它的要点。所以这是我的Java实现。如前所述,这是一棵二叉树而不是 BST。对于序列化,前序遍历似乎更容易(对于空节点的“NULL”字符串)。请使用递归调用的完整示例检查下面的代码。对于反序列化,字符串被转换为 LinkedList,其中 remove(0) 获取 O(1) 运行时间内的顶部元素。另请参阅反序列化代码的 cmets 中的完整示例。希望这会帮助有人比我更努力:) 每种方法(序列化和反序列化)的总运行时间与二叉树遍历的运行时间相同,即 O(n),其中 n 是树中节点(条目)的数量

        import java.util.LinkedList;
        import java.util.List;
        
        public class SerDesBinTree<T> {
        
            public static class TreeEntry<T>{
                T element;
                TreeEntry<T> left;
                TreeEntry<T> right;
                public TreeEntry(T x){
                    element = x;
                    left = null;
                    right = null;
                }
            }
        
            TreeEntry<T> root;
            int size;
            StringBuilder serSB = new StringBuilder();
            List<String> desList = new LinkedList<>();
        
            public SerDesBinTree(){
                root = null;
                size = 0;   
            }
        
            public void traverseInOrder(){
                traverseInOrder(this.root);
            }
        
            public void traverseInOrder(TreeEntry<T> node){
                if (node != null){
                    traverseInOrder(node.left);
                    System.out.println(node.element);
                    traverseInOrder(node.right);
                }
            }
        
            public void serialize(){
                serialize(this.root);
            }
        
        
            /*
             *          1
             *         / \
             *        2   3
             *           /
             *          4 
             *        
             *        ser(1)                              
             *            serSB.append(1)                     serSB: 1
             *            ser(1.left)
             *            ser(1.right)
             *            |
             *            |
             *            ser(1.left=2)
             *                serSB.append(2)                 serSB: 1, 2
             *                ser(2.left)
             *                ser(2.right)
             *                |
             *                |
             *                ser(2.left=null)
             *                    serSB.append(NULL)          serSB: 1, 2, NULL
             *                    return
             *                |    
             *                ser(2.right=null)
             *                    serSB.append(NULL)          serSB: 1, 2, NULL, NULL
             *                    return
             *                    
             *             |
             *             ser(1.right=3)
             *                serSB.append(3)                 serSB: 1, 2, NULL, NULL, 3
             *                ser(3.left)
             *                ser(3.right)
             *                
             *                |
             *                ser(3.left=4)
             *                    serSB.append(4)             serSB: 1, 2, NULL, NULL, 3, 4
             *                    ser(4.left)
             *                    ser(4.right)
             *                    
             *                    |
             *                    ser(4.left=null)
             *                        serSB.append(NULL)      serSB: 1, 2, NULL, NULL, 3, 4, NULL
             *                        return
             *                        
             *                    ser(4.right=null)
             *                        serSB.append(NULL)      serSB: 1, 2, NULL, NULL, 3, 4, NULL, NULL
             *                        return
             *                        
             *                ser(3.right=null)
             *                    serSB.append(NULL)          serSB: 1, 2, NULL, NULL, 3, 4, NULL, NULL, NULL
             *                    return
             *        
             */
            public void serialize(TreeEntry<T> node){
                // preorder traversal to build the string
                // in addition: NULL will be added (to make deserialize easy)
                // using StringBuilder to append O(1) as opposed to
                // String which is immutable O(n)
                if (node == null){
                    serSB.append("NULL,");
                    return;
                }
        
                serSB.append(node.element + ",");
                serialize(node.left);
                serialize(node.right);
            }
        
            public TreeEntry<T> deserialize(TreeEntry<T> newRoot){
                // convert the StringBuilder into a list
                // so we can do list.remove() for the first element in O(1) time
        
                String[] desArr = serSB.toString().split(",");
        
                for (String s : desArr){
                    desList.add(s);
                }
        
        
                return deserialize(newRoot, desList);
            }
        
        
            /*
             *          1
             *         / \
             *        2   3
             *           /
             *          4 
             * 
             *        deser(root, list)                              list: 1, 2, NULL, NULL, 3, 4, NULL, NULL, NULL
             *            root = new TreeEntry(1)                    list: 2, NULL, NULL, 3, 4, NULL, NULL, NULL
             *            root.left = deser(root.left, list)  // **
             *            root.right = deser(root.right, list) // *-*
             *            return root // ^*^
             *            
             *            
             *      so far subtree
             *          1
             *         / \
             *      null  null
             *            
             *            deser(root.left, list)
             *                 root.left = new TreeEntry(2)          list: NULL, NULL, 3, 4, NULL, NULL, NULL
             *                 root.left.left = deser(root.left.left, list) // ***
             *                 root.left.right = deser(root.left.right, list)  // ****
             *                 return root.left // eventually return new TreeEntry(2) to ** above after the two calls are done
             *                 
             *           so far subtree
             *                 2
             *                / \
             *            null   null 
             *                 
             *                 deser(root.left.left, list)      
             *                     // won't go further down as the next in list is NULL
             *                      return null    // to ***                    list: NULL, 3, 4, NULL, NULL, NULL
             *                      
             *           so far subtree (same, just replacing null)
             *                 2
             *                / \
             *            null   null 
             *            
             *                 deser(root.left.right, list)
             *                     // won't go further down as the next in list is NULL
             *                      return null    // to ****                 list: 3, 4, NULL, NULL, NULL
             *                      
             *           so far subtree (same, just replacing null)
             *                 2
             *                / \
             *            null   null 
             *            
             *      
             *      so far subtree // as node 2 completely returns to ** above
             *          1
             *         / \
             *        2  null
             *       / \
             *   null   null
             *      
             *      
             *            deser(root.right, list)
             *                 root.right = new TreeEntry(3)                list: 4, NULL, NULL, NULL
             *                 root.right.left = deser(root.right.left, list) // *&*
             *                 root.right.right = deser(root.right.right, list)  // *---*
             *                 return root.right // eventually return to *-* above after the previous two calls are done
             *                 
             *           so far subtree
             *                 3
             *                / \
             *            null   null 
             *            
             *            
             *                 deser(root.right.left, list)
             *                      root.right.left = new TreeEntry(4)       list: NULL, NULL, NULL
             *                      root.right.left.left = deser(root.right.left.left, list) // *(*
             *                      root.right.left.right = deser(root.right.left.right, list) // *)*
             *                      return root.right.left // to *&*
             *                      
             *                  so far subtree
             *                       4
             *                      / \
             *                  null   null 
             *                    
             *                       deser(root.right.left.left, list)
             *                             // won't go further down as the next in list is NULL
             *                             return null // to *(*         list: NULL, NULL
             *                             
             *                  so far subtree (same, just replacing null)
             *                       4
             *                      / \
             *                  null   null 
             *                  
             *                       deser(root.right.left.right, list)
             *                             // won't go further down as the next in list is NULL
             *                             return null // to *)*         list: NULL
             *                             
             *                             
             *                  so far subtree (same, just replacing null)
             *                       4
             *                      / \
             *                  null   null 
             *                  
             *                  
             *           so far subtree
             *                 3
             *                / \
             *               4   null   
             *              / \
             *           null  null
             *                
             *                
             *                deser(root.right.right, list)
             *                        // won't go further down as the next in list is NULL
             *                       return null // to *---*    list: empty
             *                       
             *           so far subtree (same, just replacing null of the 3 right)
             *                 3
             *                / \
             *               4   null   
             *              / \
             *           null  null   
             *           
             *           
             *           now returning the subtree rooted at 3 to root.right in *-*
             *           
             *          1
             *         / \
             *        /   \
             *       /     \
             *      2       3
             *     / \     / \
             * null  null /   null
             *           /
             *          4
             *         / \
             *      null  null 
             *      
             *      
             *      finally, return root (the tree rooted at 1) // see ^*^ above
             *    
             */
            public TreeEntry<T> deserialize(TreeEntry<T> node, List<String> desList){
        
                if (desList.size() == 0){
                    return null;
                }
        
                String s = desList.remove(0); // efficient operation O(1)
                if (s.equals("NULL")){
                    return null;
                }
        
                Integer sInt = Integer.parseInt(s);
                node = new TreeEntry<T>((T)sInt);
        
                node.left = deserialize(node.left, desList);
                node.right = deserialize(node.right, desList);
        
                return node;
            }
        
        
            public static void main(String[] args) {
                /*
                 *          1
                 *         / \
                 *        2   3
                 *           /
                 *          4 
                 *        
                 */
                SerDesBinTree<Integer> tree = new SerDesBinTree<>();
                tree.root = new TreeEntry<Integer>(1);
                tree.root.left = new TreeEntry<Integer>(2);
                tree.root.right = new TreeEntry<Integer>(3);
                tree.root.right.left = new TreeEntry<Integer>(4);
                //tree.traverseInOrder();
        
                tree.serialize();
                //System.out.println(tree.serSB);
        
                tree.root = null;
                //tree.traverseInOrder();
        
                tree.root = tree.deserialize(tree.root);
                //tree.traverseInOrder();
        
                // deserialize into a new tree
                SerDesBinTree<Integer> newTree = new SerDesBinTree<>();
                newTree.root = tree.deserialize(newTree.root);
                newTree.traverseInOrder();
        
        
            }
        }
        

        【讨论】:

          【解决方案5】:

          我没有使用预购,但我使用的是 BFS。这是一个问题 leetcode

          大多数人在使用预购时执行不正确: 预期的结果应该是

          "[1,2,3,null,null,4,5]",但大多数人将输出打印为 “[1,2,3,null,null,4,5,null,null]” 因为他们没有计算级别。

          这是我的实现,结果正确。

          class Node(object):
              def __init__(self,data):
                  self.left = None
                  self.right = None
                  self.data = data
          
          def serialize(root):
                  queue = [(root,0)]
                  result = []
                  max_level_with_value = 0
                  while queue:
                      (node,l) = queue.pop(0)
                      if node:
                          result.append((node.data,l))
                          queue.extend([(node.left,l+1),
                                        (node.right,l+1)
                                        ])
                          max_level_with_value = max(max_level_with_value,l)
                      else:
                          result.append(('null',l))
                  filter_redundant(result,max_level_with_value)
          
          
          def filter_redundant(result,max_level_with_value):
              for v,l in result:
                  if l<= max_level_with_value:
                      print(v)
          
          
          
          
          root = Node(1)
          root.left = Node(2)
          root.right = Node(3)
          root.right.left = Node(4)
          root.right.right = Node(5)
          serialize(root)
          

          【讨论】:

            【解决方案6】:

            序列化是将数据结构或对象转换为位序列的过程,以便将其存储在文件或内存缓冲区中,或通过网络连接链路传输,以便稍后在相同或另一个计算机环境中重建.

            反序列化是将字符串转换回原始树结构的过程。

            序列化和反序列化的概念与编译器对代码所做的非常相似。整个编译过程有多个阶段,但我们会尽量保持抽象。

            给定一段代码,编译器将不同的定义良好的组件分解为标记(例如,int 是一个标记,double 是另一个标记,{ 是一个标记,} 是另一个标记,等等)。 [链接到编译的抽象级别的演示][1]。

            序列化:我们使用前序遍历逻辑将树序列化为字符串。 我们将添加“X”来表示树中的空指针/节点。 另外,为了牢记我们的反序列化逻辑,我们需要在每个序列化的节点值之后添加“,”,以便反序列化过程可以访问每个用“,”分割的节点值。

            Leetcode 链接:https://leetcode.com/problems/serialize-and-deserialize-binary-tree/

            Back To Back SWE Youtube 频道的解释https://www.youtube.com/watch?v=suj1ro8TIVY

            For example:
            
            You may serialize the following tree:
            
                1
               / \
              2   3
                 / \
                4   5
            
            as "[1,2,null,null,3,4,null,null,5,null,null,]"
            
             /**
             * Definition for a binary tree node.
             * public class TreeNode {
             *     int val;
             *     TreeNode left;
             *     TreeNode right;
             *     TreeNode(int x) { val = x; }
             * }
             */
            public class Codec {
            
                // Encodes a tree to a single string.
                public String serialize(TreeNode root) {
            
                    if(root == null)
                        return "X,";
            
                    String leftSerialized = serialize(root.left);
                    String rightSerialized = serialize(root.right);
            
                    return root.val + "," + leftSerialized + rightSerialized;
                }
            
                private TreeNode deserializeHelper(Queue<String> queue)
                {
                    String nodeValue = queue.poll();
            
                    if(nodeValue.equals("X"))
                        return null;
            
                    TreeNode newNode = new TreeNode(Integer.valueOf(nodeValue));
            
                    newNode.left = deserializeHelper(queue);
                    newNode.right = deserializeHelper(queue);
            
                    return newNode;
                }
            
                // Decodes your encoded data to tree.
                public TreeNode deserialize(String data) {
            
                    Queue<String> queue = new LinkedList<>();
                    queue.addAll(Arrays.asList(data.split(",")));
            
                    return deserializeHelper(queue);
                }
            }
            
            //Codec object will be instantiated and called as such:
            //Codec codec = new Codec();
            //codec.deserialize(codec.serialize(root));
            

            【讨论】:

              【解决方案7】:

              所有这些文章都主要讨论序列化部分。一次性完成反序列化部分有点棘手。

              我也为反序列化实现了一个有效的解决方案。

              问题:序列化和反序列化包含正数的二叉树。

              序列化部分:

              1. 使用 0 表示 null。
              2. 使用前序遍历序列化为整数列表。

              反序列化部分:

              1. 接受整数列表并使用递归辅助方法进行反序列化。
              2. 递归反序列化器返回一对 (BTNode node, int nextIndexToRead),其中 node 是目前构建的树节点,nextIndexToRead 是序列化数字列表中要读取的下一个数字的位置。

              以下是 Java 代码:

              public final class BinaryTreeSerializer
              {
                  public static List<Integer> Serialize(BTNode root)
                  {
                      List<Integer> serializedNums = new ArrayList<Integer>();
              
                      SerializeRecursively(root, serializedNums);
              
                      return serializedNums;
                  }
              
                  private static void SerializeRecursively(BTNode node, List<Integer> nums)
                  {
                      if (node == null)
                      {
                          nums.add(0);
                          return;
                      }
              
                      nums.add(node.data);
                      SerializeRecursively(node.left, nums);
                      SerializeRecursively(node.right, nums);
                  }
              
                  public static BTNode Deserialize(List<Integer> serializedNums)
                  {
                      Pair pair = DeserializeRecursively(serializedNums, 0);
              
                      return pair.node;
                  }
              
                  private static Pair DeserializeRecursively(List<Integer> serializedNums, int start)
                  {        
                      int num = serializedNums.get(start);
              
                      if (num == 0)
                      {
                          return new Pair(null, start + 1);
                      }
              
                      BTNode node = new BTNode(num);
              
                      Pair p1 = DeserializeRecursively(serializedNums, start + 1);
                      node.left = p1.node;
              
                      Pair p2 = DeserializeRecursively(serializedNums, p1.startIndex);
                      node.right = p2.node;
              
                      return new Pair(node, p2.startIndex);
                  }
              
                  private static final class Pair
                  {
                      BTNode node;
                      int startIndex;
              
                      private Pair(BTNode node, int index)
                      {
                          this.node = node;
                          this.startIndex = index;
                      }
                  }
              }
              
              public class BTNode 
              {
                  public int data;
                  public BTNode left;
                  public BTNode right;
              
                  public BTNode(int data)
                  {
                      this.data = data;
                  }
              }
              

              【讨论】:

                【解决方案8】:

                使用前序遍历,序列化二叉树。 使用相同的前序遍历来反序列化树。小心边缘情况。这里空节点用“#”表示

                public static String serialize(TreeNode root){
                            StringBuilder sb = new StringBuilder();
                            serialize(root, sb);
                            return sb.toString();
                        }
                
                    private static void serialize(TreeNode node, StringBuilder sb){
                        if (node == null) {
                            sb.append("# ");
                        } else {
                            sb.append(node.val + " ");
                            serialize(node.left, sb);
                            serialize(node.right, sb);
                        }
                    }
                
                    public static TreeNode deserialize(String s){
                        if (s == null || s.length() == 0) return null;
                        StringTokenizer st = new StringTokenizer(s, " ");
                        return deserialize(st);
                    }
                
                    private static TreeNode deserialize(StringTokenizer st){
                        if (!st.hasMoreTokens())
                            return null;
                        String val = st.nextToken();
                        if (val.equals("#"))
                            return null;
                        TreeNode root = new TreeNode(Integer.parseInt(val));
                        root.left = deserialize(st);
                        root.right = deserialize(st);
                        return root;
                    } 
                

                【讨论】:

                  【解决方案9】:

                  最好的方法是使用一个特殊的字符(比如前面提到的#)作为哨兵。它比构造一个中序遍历数组和一个前序/后序遍历数组要好,无论是在空间复杂度方面还是时间复杂度方面。它也更容易实现。

                  链表在这里不太适合,因为要重建树,最好有 const 元素访问时间

                  【讨论】:

                  • 如果树节点值可以是'#'怎么办?
                  【解决方案10】:

                  如何执行按顺序遍历并将根键和所有节点键放入 std::list 或您选择的其他容器,以使树变平。然后,只需使用 boost 库序列化您选择的 std::list 或容器。

                  反过来很简单,然后使用标准插入二叉树来重建树。对于非常大的树,这可能不是完全有效,但是将树转换为 std::list 的运行时间最多为 O(n),而重建树的时间最多为 O(log n)。

                  当我将数据库从 Java 转换为 C++ 时,我将要序列化我刚刚用 C++ 编写的树。

                  【讨论】:

                  • 二叉树不一定是二叉搜索树,因此它可能无法排序。 (想想二进制空间分区。)
                  • @idbrii 但树不必排序。中序遍历保留顺序并展平树以进行序列化。从序列化展平列表插入二叉树会返回具有相同顺序的新二叉树。排序与它无关。
                  【解决方案11】:

                  方法一: 执行 Inorder 和 Preorder 遍历以对树数据进行序列化。 在反序列化时,使用 Pre-order 并执行 BST 以正确形成树。

                  您需要两者,因为即使结构可以不同,A -> B -> C 也可以表示为预购。

                  方法 2: 只要左或右孩子为空,就使用 # 作为哨兵.....

                  【讨论】:

                    猜你喜欢
                    • 2021-11-14
                    • 2020-04-09
                    • 1970-01-01
                    • 1970-01-01
                    • 2019-01-26
                    • 1970-01-01
                    • 2014-02-07
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多