Reconstruction of Binary Trees from Paths - Variable Scope (2023)

Between work, free time and other side projects I've worked withbinary trees🇧🇷 They are a very useful data structure in many areas, but most relevant in this case, they lend themselves to all sorts of tweaks and tweaks. Among the many possible questions is the seemingly simple "How do you serialize a tree?" for interprocess communication, a web-based systemAPIor simply for the joy of it. And with such a serialization, how do you reconstruct the original tree from it?

One way is to express each node as a list with three elements: the node's value, its left child, and its right child. Each of these child elements is its own three-element list (or some kind offilevalue) and so on. The end of this serialization will have a special onethe business:]]]]]]]🇧🇷 something weI've seen it before.

I don't mind square brackets per se, but as some have said, "flat is better than nested".[1]And there are wonderfully flat ways to describe binary trees. This article describes how to plot the how and then build a binary tree from some lists without requiring any additional structure information.

depth-first search

But before we get into the actual construction of trees, we need to briefly explain how to serialize a tree. A favorite among pollsters, depth-first search is elegant as a recursive algorithm. It's the easiest way to traverse all the nodes in a tree, extracting their values ​​along the way.

Reconstruction of Binary Trees from Paths - Variable Scope (1)

A simple binary search tree

The traversal starts at the root node (

4

) and recursively visits the left children before the right children. Small differences in the order of getting the values ​​of the current node and the child node produce significantly different results.

(Video) Binary Tree Algorithms for Technical Interviews - Full Course

Starting at the root of the tree, the algorithm visits each node by first visiting the left child and proceeding recursively there. Upon completion, the correct child is visited and returned to. This corecursion creates a path descending along a left edge, methodically jumping to the next unexplored right branch, and repeating this process until all nodes are covered.

The algorithm avoids costly comparisons because it only requires simple equality checks to determine that there is a child node to descend to and that memory usage is linear with the height of the tree. In the worst case, this height is equal to the number of nodes in the tree (each child is a left or right child), but as the tree grows largerbalanced, the height is approachingThe (record n), as well as the memory requirements.

Traversing this path returns the visited nodes (or their values, depending on the requirement). For a left-to-right scrolling algorithm, there are three different options here:

  1. an order: creates the node itself from the left child and then from the right child; for a binary search tree, the nodes are returned in ascending order;
  2. make a request: creates the node itself, the left child, then the right child; that describes theorder of visitsKnot, also known asTopological Classification;
  3. release order: Returns the left child, the right child, and then the node itself. For expression trees, this gives a reverse-polished notation that can be easily evaluated with a small stack.[2]

For simplicity, we keep the traversals in order and pre-sorted, and run them through a simple binary search tree as follows:

from ... to __Future__ import Remarksfrom ... to Datenklassen import Datenklassefrom ... to typewriting import none, Optionalclass @dataClass Es: valeria: none there went: Optional[Es] = none Right: Optional[Es] = nonefinally an order(es): e es es Not none: acting an order(es.there went) Production es.valeria acting an order(es.Right)finally make a request(es): e es es Not none: Production es.valeria acting make a request(es.there went) acting make a request(es.Right)

It should be clear from the code that the time complexity of these functions is linear with the number of nodes in the tree. Each recursion either creates a single node or does nothing as it goes through the leaf node. There are exactly n+1 instances of the last type, which asymptotically brings the total number of operations to 2nAn).

the result ofan orderis a generator that outputs the tree values ​​from left to right. Caught in a list it seems[1, 2, 3, 4, 5, 7]🇧🇷 On the other hand,make a requestreturns the nodes in the order visited, resulting in a sequence like this:[4, 2, 1, 3, 5, 7].

Construction only on pre-order

No before-after order sequencing unambiguously describes the underlying tree. In a tree with different elements, both preorder and postorder paired with inorder are sufficient to uniquely describe the tree.

—Wikipedia,Kerachse

This seems like a pretty bold claim if we look at the pre-order sequence we generated for the binary search tree example. It is quite possible to create an algorithm for reconstructing the tree, provided of course that it only has distinct elements (if this is not the case, an unambiguous reconstruction without structure information is impossible).

Remember that a pre-order sequencing has the nodes in the order visited, so we can build an algorithm to append each next node to the tree as we build it. We need to backtrack a little (after the behavior of the depth-first search algorithm) for which we remain stacked:

  • context: Create an empty stack and set the root of the tree
    1. Take the first value from the pre-order sequencing, this is theThose
    2. Create a node from it and place it on the stack
    3. save asThosejReales
  • Exit: Take the next value and compare it to the current node value
    • kleiner: descent along the left bank
      1. Create a new node from this value and push it onto the stack
      2. Set it as the left child of the current node and make it the current node
    • greater: Go back to the correct branch point and go right
      1. Look at the stack, if the top node is smaller, remove it and make it current. repeat until the stack is empty or has a larger value at the top
      2. Create a new node from this value and push it onto the stack
      3. Set it as the right child of the current node and make it the current node
  • Hand back: Once all values ​​in the sequence have been consumed, the is returnedThosees.

Put that all in Python and it looks like this:

from ... to collections import for this reasonfinally construct_from_preorder(Values): Values = repeated(Values) Those = es = Es(next(Values)) align = for this reason([Those]) Pro valeria no Values: e valeria < es.valeria: es.there went = es = Es(valeria, Pater=es) align.append left(es) most: Tempo align j valeria > align[0].valeria: es = align.slam to the left() es.Right = es = Es(valeria, Pater=es) align.append left(es) hand back Those
(Video) Simplest Binary Tree Traversal trick for preorder inorder postorder

In terms of complexity, the memory footprint is the same as drilling down, branching deeper, or more general.The (record n)assuming a well balanced balanced tree. In terms of time complexity, there is the outer loop, which is clearly linear (no recursion, all function calls areO(1)🇧🇷 Changing the water is a setbackTempoLoop that can be any length at any point in the process. However, we cannot go further back than we went down the tree (in other words,appendjPopeach node at most once), then this limit must also be linear, for an asymptotic time complexity ofAn).

What you'll notice here is that we can reconstruct the tree based on a property specific to binary search trees: the order of the children. Children with smaller values ​​go to the left, larger ones to the right. The quote at the beginning of this section refers togenericBinary trees where there are no guarantees of descending order.

For a generic binary tree it is impossible to unambiguously reconstruct it from its pre-order sequencing alone, since different trees can be the source of the pre-order sequencing and there is not enough information to clarify:

Reconstruction of Binary Trees from Paths - Variable Scope (2)

All these binary trees share a pre-order sequencing ([2, 1, 3]🇧🇷 Only one of them is a conforming binary.Search fortree, which is what we usually refer to when we talk about a binary tree, but they are all valid trees.

Another try

Of course, we cannot rely on the order of individual values ​​from any individual sequencing. The solution to this problem must come from the inherent properties of the two distinct sequences, or more specifically from the differences between them. Let's look at what we know about each sequence, how they differ, and how we can use these properties to our advantage.[3]

1: drop to the leftmake a requestpartiesan order

Reconstruction of Binary Trees from Paths - Variable Scope (3)

left descender

The initial structure of the order

(Video) Balanced Binary Tree - Leetcode 110 - Python

[1,2,3,4]

and pre-order sequence

[4,2,1,3]

🇧🇷 Take the order value 1 and build from the pre-order until you reach this value.

  • Ömake a requestThe sequence starts at the root node
  • Öan orderThe sequence begins at the leftmost node

This means that we can, in principle, read values ​​from themake a requestSequence and expand the tree along a left edge to the current onean ordervalue is reached. The figure at right shows how the initial left descent of this tree is constructed from this rule.

2: Move right whenmake a requestpartiesan order

  • Ömake a requestthe sequence contains nodes along the traced path
  • Öan orderSequence scans the tree horizontally from left to right

While the current values ​​ofmake a requestjan orderthe sequences are identical, the next node is to the right of the current node, further down the tree or higher up the tree. To accommodate the case where the next node is further up the tree, we need to manage a stack of nodes. We're going to expand this stack each time we traverse the path on the left, so we can go back and append a node on the right.

3: Go back and expand to the right

Reconstruction of Binary Trees from Paths - Variable Scope (4)

Extension at the back and right

The next value in order is

2

, which is at the top of the stack, so let's go back. The next value in order is

3

, a new expansion destination. This is also the next value in themake a requestSequence and appended as right child.

Expanding on that last observation: If we follow thatmake a requestSequence to "far left" and the values ​​of both sequences are now identical, the next value inan orderthe order will be...

(Video) Serialize & Deserialize A Binary Tree - Crafting Recursive Solutions To Interview Problems

  • ...the top value on the stack, which means the algorithm must return to that node and keep choosing between thean orderSequence;
  • ... not on the stack and therefore a right descendant (although not necessarily an immediate child) of the current node.

This last situation is similar to that of the root, with one small difference: theFirstvalue ofmake a requestThe sequence is appended to the right of the current node. From there the tree expands along the left edge using values ​​from themake a requestorder so faran ordervalue is reached. This can be the first value used to create the right node.

At the end of itmake a requestjan orderthe string values ​​are equal, which is a covered case. As soon as one of the sequences is completely consumed, the composition is complete.

Implementing the Algorithm

From these ground rules and observations, we can create a Python implementation that builds a binary treemake a requestjan orderIterate

finally construct_from_preorder_inorder(make a request, an order): pre_iter = repeated(make a request) Those = es = Es(next(pre_iter)) align = for this reason([es]) Right = NOT CORRECT Pro I guess no an order: e align j I guess == align[0].valeria: es = align.slam to the left() Right = Real consequences Pro pvalor no pre_iter: e Right: es.Right = es = Es(pvalor) most: es.there went = es = Es(pvalor) e Right := pvalor == I guess: rest align.append left(es) hand back Those

The structure of this function is very similar to our function that builds a binary search tree from just onemake a requestthe defender:

  • An iterator is retrievedmake a requeststring (in supportnextand continuous iteration)
  • ONEThosethe node is created and also assigned asReal
  • A stack is created and used to drive the trace

A novelty in this algorithm is the variableRight, which we use to indicate that the next node will be added as the right child node instead of the default left one. Oan orderThe sequence only repeats in a single infinite loop, so you don't have to create an explicit iterator for it.

The main loop is divided into two branches, similar to the previous example:

  • return: If hean orderthe value is the same as the current value on the stack, we need to return to that node. We also know that the next node to the right will be appended (from this node or one higher in the tree) because thean orderthe sequence searches the tree from left to right.
  • extension: If we don't go backwards, we go downstreaman orderValue. The first of these steps could be a step to the right (if we only went backwards), but the others only go to the left. Once we reach the riveran ordervalue, werestand establishRight = Real.

There is a bit of redundancy in the configuration of theRightVariable during rollback (rather than just at the end of expansion). This covers the case of a tree where the root is also the leftmost node. If hean orderThe sequence is guaranteed to be a list, the initial value forRightcan be configuredes.valeria == an order[0]However.

Finally

Sometimes all it takes is a few lines of "banal untruth" to send you down a rabbit hole that will keep you busy for days. I realized my misunderstanding immediately, but I was hooked by then. Finding the relevant algorithm would have been quicker and easier, and all other questions would have been easily resolved with a few keyword searches. From time to time, however, it can be as educational as it is fun to hunt down the rabbit hole and discover its secrets. This is how we learn best.

After this hunt, I've been looking for other solutions or documents on this topic for a long time, but I haven't found many (many algorithms of varying clarity, but few explanations). What I found was an article by Erkki Mäkinen from 1989.Construction of a binary tree from its paths”, which offers an algorithm similar to those explained above, but with themake a requestsequence in the outer loop. This paper mentions two others that are said to be less efficient at one point (O(n^2)) or spaces (unspecified), but remain locked behind a paywall.

footnotes

[1]SincePython-Zen.
[2]Converting expression trees to serialized form has been a recent topic of interest to me.SQLAlchemy-Hybrid-Dienstprogrammetakes SQLALchemy expressions and converts them to a serialized format that can be evaluated against Python objects instead of being run against the database. This allows for a much simpler (shorter) way of defining a particular class of hybrid properties.
[3]The process here is much more complicated than the distilled results. It's a lot of trial and error: sheets of paper with lots of graphics scrawled and lots of cards cut out to simulate different approaches until something works, until something works.cliques.

Related posts:

  • Building binary trees with relative branch coding
  • Create a forest from a single seed
  • Set zealous standards for SQLAlchemy ORM models
  • Merge ordered lists

Remarks

Feedback powered byDisqus

Videos

1. Binary search tree - Implementation in C/C++
(mycodeschool)
2. Delete a node from Binary Search Tree
(mycodeschool)
3. Breadth First Search grid shortest path | Graph Theory
(WilliamFiset)
4. Breadth First Search Algorithm | Shortest Path | Graph Theory
(WilliamFiset)
5. Binary Tree Maximum Path Sum | (C++, Java, Python) | 30 day Challenge | Day 29 | LeetCode #124
(Knowledge Center)
6. Construct a binary tree from inorder and preorder in Hindi | Simple Shortcut method Data structure
(CS Engineering Gyan)

References

Top Articles
Latest Posts
Article information

Author: Prof. Nancy Dach

Last Updated: 19/07/2023

Views: 6265

Rating: 4.7 / 5 (77 voted)

Reviews: 84% of readers found this page helpful

Author information

Name: Prof. Nancy Dach

Birthday: 1993-08-23

Address: 569 Waelchi Ports, South Blainebury, LA 11589

Phone: +9958996486049

Job: Sales Manager

Hobby: Web surfing, Scuba diving, Mountaineering, Writing, Sailing, Dance, Blacksmithing

Introduction: My name is Prof. Nancy Dach, I am a lively, joyous, courageous, lovely, tender, charming, open person who loves writing and wants to share my knowledge and understanding with you.