Serialize and Deserialize N-ary Tree

Updated on 24 June, 2025
Serialize and Deserialize N-ary Tree header image

Problem Statement

Serialization in computing involves converting a data structure or object state into a format that can be stored or transmitted and later reconstructed. This challenge requires designing an algorithm capable of both serializing and deserializing an N-ary tree, where the tree consists of nodes that each have up to N children. The pivotal requirement is that the serialized N-ary tree, represented as a string, can be transformed back into the original tree structure with appropriate consistency. The solution should adhere strictly to being stateless, not relying on any external or static storage to maintain the state during encoding or decoding.

Examples

Example 1

Input:

root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]

Output:

[1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]

Example 2

Input:

root = [1,null,3,2,4,null,5,6]

Output:

[1,null,3,2,4,null,5,6]

Example 3

Input:

root = []

Output:

[]

Constraints

  • The number of nodes in the tree is in the range [0, 104].
  • 0 <= Node.val <= 104
  • The height of the n-ary tree is less than or equal to 1000
  • Do not use class member/global/static variables to store states. Your encode and decode algorithms should be stateless.

Approach and Intuition

When tackling the serialization and deserialization of an N-ary tree, the main consideration is how to record the relationship between nodes and their children efficiently and clearly. Let's break down the approach:

  1. Serialization Approach:

    • For each node, record the node's value followed by a special marker (e.g., null) that indicates the end of children for that node.
    • Proceed recursively or iteratively with each child of the current node, appending their values to the serialization string.
    • Ensure each node's children are denoted clearly such that when deserializing, the exact tree structure can be reconstructed.
  2. Deserialization Approach:

    • Parse the serialized string from left to right.
    • Use a stack or queue to help rebuild the tree structure. Each time a node value is read, create a new tree node.
    • Upon encountering the special marker, determine the closure of a node's children list and attach the popped nodes to their parent from the last node in the stack.

The examples provided utilize a specific serialized pattern:

  • Example 1:

    • Series of node values interspersed with null to denote the transition to a new set of children or moving up the tree hierarchy.
    • From the given serialization pattern, reconstruct the tree by assigning children appropriately based on the null markers.
  • Example 2 and Example 3:

    • Similar handling where null separates nodes or indicates the absence of further nodes.

The constraints of the problem ensure that the solution needs to be efficient in both space and execution time, where maximum node and depth levels are stipulated. This fosters an approach that must be both lightweight concerning memory usage and optimized for speed to handle potentially large trees effectively.

Solutions

  • Java
java
class TreeSerializer {

    // Transforms a tree to a unified string.
    public String encode(Node root) {
        if (root == null) {
            return "";
        }

        StringBuilder result = new StringBuilder();
        constructSerialization(root, result);
        return result.toString();
    }

    private void constructSerialization(Node root, StringBuilder result) {

        Queue<Node> queue = new LinkedList<>();
        Node levelTerminator = new Node();
        Node childrenSeparator = new Node();
        queue.add(root);
        queue.add(levelTerminator);

        while (!queue.isEmpty()) {

            Node currentNode = queue.poll();

            if (currentNode == levelTerminator) {
                result.append('#');
                if (!queue.isEmpty()) {
                    queue.add(levelTerminator);
                }
            } else if (currentNode == childrenSeparator) {
                result.append('`');
            } else {
                result.append((char) (currentNode.val + '0'));
                for (Node child : currentNode.children) {
                    queue.add(child);
                }
                if (queue.peek() != levelTerminator) {
                    queue.add(childrenSeparator);
                }
            }
        }
    }

    // Rebuilds the tree from the encoded data.
    public Node decode(String data) {
        if (data.isEmpty()) {
            return null;
        }

        Node rootNode = new Node(data.charAt(0) - '0', new ArrayList<>());
        rebuildTree(data, rootNode);
        return rootNode;
    }

    private void rebuildTree(String data, Node root) {

        LinkedList<Node> thisLevel = new LinkedList<>();
        LinkedList<Node> previousLevel = new LinkedList<>();
        thisLevel.add(root);
        Node currentParent = root;

        for (int i = 1; i < data.length(); i++) {
            char charData = data.charAt(i);
            if (charData == '#') {
                previousLevel = thisLevel;
                thisLevel = new LinkedList<>();
                currentParent = previousLevel.poll();
            } else {
                if (charData == '`') {
                    currentParent = previousLevel.poll();
                } else {
                    Node childNode = new Node(charData - '0', new ArrayList<>());
                    thisLevel.add(childNode);
                    currentParent.children.add(childNode);
                }
            }
        }
    }
}

The provided Java code defines a class TreeSerializer that enables the serialization and deserialization of an N-ary tree. Below is an explanation of how each part of the code works:

Serialize a Tree

  • Initiate serialization with the encode method. If the tree root is null, return an empty string; otherwise, use a StringBuilder for constructing the serialization string.
  • In constructSerialization, use the breadth-first search (BFS) logic leveraging a queue. A level terminator and a children separator nodes ensure appropriate delineation during serialization:
    • Extract and process nodes from the queue.
    • Append a unique character to the result for the level terminator (#) and children separator (').
    • Convert the node value to a character and append it to the resulting string.
    • Enqueue child nodes of the current node and distinguish sibling nodes with a separator unless the next node is a level separator.

Deserialize a String to Tree

  • Initiate tree reassembly from a string data using the decode method. If the input data string is empty, return null.
  • The rebuildTree assists in constructing the actual tree by:
    • Setting up two levels of list structure (previousLevel and thisLevel) to manage nodes at different depths of the tree.
    • Iteratively read through the string data:
      • Use a hash (#) to demarcate transitions between levels, adjusting reference to the current set of parent nodes.
      • Use a grave accent (') to shift parent node reference within the current level.
      • Incoming characters represent new nodes which are created, added to thisLevel, and linked to the current parent node in parentLevel.

By carefully managing node relationships and levels with queues and lists, the process effectively maintains the tree structure across serialization and deserialization procedures, ensuring data integrity and correctness of the tree's shape.

Comments

No comments yet.