How to Serialize Binary Tree
Asked Answered
E

11

33

I went to an interview today where I was asked to serialize a binary tree. I implemented an array-based approach where the children of node i (numbering in level-order traversal) were at the 2*i index for the left child and 2*i + 1 for the right child. The interviewer seemed more or less pleased, but I'm wondering what serialize means exactly? Does it specifically pertain to flattening the tree for writing to disk, or would serializing a tree also include just turning the tree into a linked list, say. Also, how would we go about flattening the tree into a (doubly) linked list, and then reconstructing it? Can you recreate the exact structure of the tree from the linked list?

Experimentalize answered 6/1, 2011 at 3:48 Comment(3)
Related: #2676256Inorganic
Most of the time interviewers will ask this question to see if you will us a recursive approach. Basically, you write serialize for leaf nodes, and then for parent nodes, you call serialize(left), output current node, serialize(right). It's an elegant solution and you let interviewers know that you have taken a decent algorithms class.Cruz
thanks everyone for the helpful info.Experimentalize
D
13

All those articles talk mostly about the serialization part. The deserialization part is slightly tricky to do in one pass.

I have implemented an efficient solution for deserialization too.

Problem: Serialize and Deserialize a binary tree containing positive numbers.

Serialization part:

  1. Use 0 to represent null.
  2. Serialize to list of integers using preorder traversal.

Deserialization part:

  1. Takes in list of integers and uses recursive helper method for deserialization.
  2. Recursive deserializer returns a pair (BTNode node, int nextIndexToRead) where node is tree node constructed so far, and nextIndexToRead is position of next number to be read in the serialized list of numbers.

Below is the code in 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;
    }
}
Db answered 24/8, 2013 at 20:28 Comment(0)
I
9

Using pre order traversal, serialize Binary tree. Use the same pre order traversal to deserialize tree. Be careful about the edge cases. Here null nodes are represented by "#"

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;
    } 
Inna answered 18/1, 2016 at 17:56 Comment(0)
B
7

Approach 1: Do both Inorder and Preorder traversal to searialize the tree data. On de-serialization use Pre-order and do BST on Inorder to properly form the tree.

You need both because A -> B -> C can be represented as pre-order even though the structure can be different.

Approach 2: Use # as a sentinel whereever the left or right child is null.....

Bona answered 23/2, 2013 at 19:49 Comment(0)
T
1

I have been trying to get the gist of it. So here is my Java implementation. As mentioned, this is a binary tree not a BST. For serializing, a preorder traversal seems to be working easier (to a string with "NULL" for null nodes). Please check the code below with a complete example of recursion calls. For deserializing, the string is converted to a LinkedList where remove(0) gets the top element in an O(1) running time. Please also see a complete example in the comments of the code for deserializing. Hope that will help someone struggle less than I did :) The overall running time for each method (serialize and deserialize) is the same running time for binary tree traversal, i.e., O(n) where n is the number of nodes (entries) in the tree

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();


    }
}
Than answered 21/12, 2017 at 16:43 Comment(0)
S
0

How about performing an in-order traversal and putting the root key and all node keys into a std::list or other container of your choice which flattens the tree. Then, simply serialize the std::list or container of your choice using the boost library.

The reverse is simple and then rebuild the tree using standard insertion to a binary tree. This may not be entirely efficient for a very large tree but runtime to convert the tree into a std::list is O(n) at most and to rebuild the tree is O(log n) at most.

I am about to do this to serialize a tree I just coded up in c++ as I am converting my database from Java to C++.

Starobin answered 12/3, 2013 at 17:59 Comment(2)
A binary tree is not necessarily a binary search tree so it may not be sorted. (Think binary space partitioning.)Minority
@Minority But the tree doesn't have to be sorted. An in-order traversal preserves order and flattens the tree for serialization. Insertion into a binary tree from the serialized flattened list returns a new binary tree with the same order. Sort has nothing to do with it.Starobin
S
0

The best way is to use a special char (like # as previous comment mentioned) as sentinel. It's better than constructing an inorder traversal array and a preorder/postorder traversal array, both in space complexity wise and time complexity wise. it's also way easier to implement.

Linked list is not a good fit here since in order to reconstruct the tree, you better have const element access time

Skew answered 19/3, 2014 at 22:7 Comment(1)
What if tree node value can be '#'?Fie
S
0

Here is a late answer in Python. It uses (depth-first) preorder serialization and returns a list of strings. Deserialization returns the tree.

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

To test:

tree1 = Node('root', Node('left'), Node('right'))
t = deserialize(serialize(tree1))
print(str(t.right.val))
Sedgewick answered 28/2, 2019 at 2:19 Comment(0)
O
0

I am not using pre-order but I am using BFS. This is a question from leetcode

Majority of people implementation are incorrect when using pre-order: the expected result should be

"[1,2,3,null,null,4,5]", but instead majority people print the output as "[1,2,3,null,null,4,5,null,null]" since they are not counting the levels.

Here is my implementation with the correct result.

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)
Overseas answered 8/10, 2019 at 23:2 Comment(0)
S
0

convert it to array with size of (2n + 1) each left son and right son will be in place of (2 * node number) and ((2 * node number) + 1 accordingly.

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

Subsequence answered 5/11, 2020 at 12:37 Comment(0)
C
0

Serialize deserialize binary tree:

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();
    }

}

Serialize deserialize N-ary tree: With this code, you can serialize, deserialize any n-ary tree. Basically, a binary tree is a 2-ary tree. N shows the maximum number of children of all the nodes in the whole tree.

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
    }

}
Cephalalgia answered 3/3, 2021 at 2:38 Comment(0)
A
-2

Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment.

Deserialization is the process of converting the string back to the original tree structure.

Concept of serialization and deserialization is very similar to what a compiler does to code. There are multiple phases in the entire compilation process but we will try to keep it abstract.

Given a piece of code, compiler breaks different well-defined components into tokens (for example, int is a token, double is another token, { is one token, } is another token, etc). [Link to a demonstration of the abstract level of compilation][1].

Serialization: We use preorder traversal logic for serializing tree to a String. We will add "X" to denote a null pointer/node in a tree. In addition, to keep our deserialization logic in mind, we need to add "," after every serialized node value so that the deserialization process can access each node value split with ",".

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

Explanation by Back To Back SWE Youtube channel: 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));
Arbuckle answered 16/7, 2019 at 19:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.