web-dev-qa-db-fra.com

Comment sérialiser un arbre binaire

Je suis allé à une interview aujourd'hui où on m'a demandé de sérialiser un arbre binaire. J'ai implémenté une approche basée sur les tableaux où les enfants du nœud i (numérotation dans l'ordre de passage du niveau) étaient à l'indice 2 * i pour l'enfant gauche et 2 * i + 1 pour l'enfant droit. L'intervieweur semblait plus ou moins satisfait, mais je me demande ce que signifie sérialiser exactement? Cela concerne-t-il spécifiquement l’aplatissement de l’arbre pour l’écriture sur le disque ou la sérialisation d’un arbre implique-t-elle simplement de transformer l’arbre en une liste chaînée, par exemple? Aussi, comment pourrions-nous mettre à plat l'arbre dans une liste (doublement) chaînée, puis le reconstruire? Pouvez-vous recréer la structure exacte de l'arbre à partir de la liste liée?

27
worker1138

Tous ces articles parlent principalement de la sérialisation. La désérialisation est un peu délicate à faire en un seul passage.

J'ai également mis en place une solution efficace de désérialisation. 

Problème: Sérialiser et désérialiser un arbre binaire contenant des nombres positifs.

Partie de sérialisation:

  1. Utilisez 0 pour représenter null.
  2. Sérialiser à la liste des entiers en utilisant la traversée de pré-ordre.

Partie désérialisation:

  1. Prend la liste des entiers et utilise la méthode d'assistance récursive pour la désérialisation.
  2. Le désérialiseur récursif retourne une paire (noeud BTNode, int nextIndexToRead) où noeud est un noeud d'arborescence construit jusqu'à présent et nextIndexToRead est la position du numéro suivant à lire dans la liste de numéros sérialisée.

Ci-dessous le code en 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;
    }
}
13
AbhishekPrateek

Méthode 1: Faites la traversée en ordre et en pré-commande pour équilibrer les données de l’arborescence Lors de la désérialisation, utilisez Pre-order et utilisez BST sur Inorder pour former correctement l’arbre.

Vous avez besoin des deux, car A -> B -> C peut être représenté sous forme de précommande même si la structure peut être différente. 

Approche 2: Utilisez # comme sentinelle partout où l’enfant gauche ou droit est nul .....

7
Ketan Thakkar

À l'aide de la traversée de précommande, sérialisez l'arborescence binaire . Utilisez la même traversée de précommande pour désérialiser l'arborescence. Faites attention aux cas Edge. Ici, les nœuds nuls sont représentés par "#" 

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;
    } 
5
sreeprasad

Voici une réponse tardive en Python. Il utilise (en premier lieu la profondeur) la sérialisation en pré-commande et renvoie une liste de strings. La désérialisation renvoie l'arbre.

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

        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

Tester:

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

Le meilleur moyen consiste à utiliser un caractère spécial (comme # comme le commentaire précédent mentionné) comme sentinelle. C'est mieux que de construire un tableau de traversée en ordre et un tableau de traversée en ordre, ou en ordre, en termes de complexité spatiale et de complexité temporelle. c'est aussi beaucoup plus facile à mettre en œuvre.

La liste chaînée ne convient pas ici car, pour reconstruire l’arbre, il vaut mieux avoir le temps d’accès à l’élément const.

0
Haitao

J'ai essayé de comprendre l'essentiel. Voici donc mon implémentation Java. Comme mentionné, il s’agit d’un arbre binaire et non d’une BST. Pour la sérialisation, une traversée de précommande semble fonctionner plus facilement (en une chaîne avec "NULL" pour les nœuds nuls). Veuillez vérifier le code ci-dessous avec un exemple complet d'appels récursifs. Pour la désérialisation, la chaîne est convertie en une liste LinkedList où remove (0) obtient le premier élément dans une durée d'exécution O(1). Veuillez également consulter un exemple complet dans les commentaires du code de désérialisation. J'espère que cela aidera quelqu'un à lutter moins que moi:) Le temps d'exécution total pour chaque méthode (sérialiser et désérialiser) est le même temps d'exécution pour la traversée d'arbres binaires, c'est-à-dire, O(n) le nombre de nœuds (entrées) dans l'arbre

import Java.util.LinkedList; import Java.util.List;

classe publique SerDesBinTree {

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


}

}

0
Khaled Alnaami

Pourquoi ne pas effectuer une traversée dans l’ordre et placer la clé racine et toutes les clés de nœud dans un std :: list ou un autre conteneur de votre choix qui aplatit l’arborescence. Ensuite, sérialisez simplement le conteneur std :: list ou le conteneur de votre choix en utilisant la bibliothèque boost.

L'inverse est simple, puis reconstruisez l'arborescence à l'aide d'une insertion standard en une arborescence binaire. Cela peut ne pas être tout à fait efficace pour un très grand arbre, mais l’exécution pour convertir l’arbre en std :: list est O(n) au plus et reconstruire l’arbre est au maximum pour O (log n).

Je suis sur le point de le faire pour sérialiser un arbre que je viens de coder en c ++ alors que je convertis ma base de données de Java en C++.

0
user633658