Sword finger Offer reply flow series - rebuild binary tree

Keywords: Java Android JDK

Interview question 6: reconstruction of binary tree

Title Description:

Enter the results of the preorder traversal and inorder traversal of a binary tree, and rebuild the binary tree. It is assumed that the results of the input preorder traversal and preorder traversal do not contain duplicate numbers. For example, input the sequence {1,2,4,7,3,5,6,8} and the sequence {4,7,2,1,5,3,8,6}, then reconstruct the binary tree shown in Figure 2.6 and output its head node. The definition of binary tree node is as follows:

class Node{
    int e;
    Node left;
    Node right;
    Node(int x) { e = x; }
}

This question mainly tests your understanding of the properties of binary trees, which is very representative. But before that, let's go through the basic properties of binary trees.

Basic properties of binary trees

(1) About trees

Tree is a kind of data structure, which consists of n (n > = 1) finite nodes to form a set with hierarchical relationship.

The basic terms of trees are:

  • Each node has zero or more child nodes
  • A node without a parent is called a root node
  • Each non root node has only one parent node
  • In addition to the root node, each sub node can be divided into multiple disjoint sub trees.
  • If a node has a subtree, the node is called the "parent" of the subtree root, and the root of the subtree is called the "child" of the node. The nodes with the same parents are brothers to each other.
  • Any node on all subtrees of a node is the descendant of that node. All nodes in the path from the root node to a node are the ancestors of that node.
  • Degree of node: the number of subtrees owned by the node
  • Leaf node: node with degree 0
  • Branch node: node with degree not 0
  • Degree of tree: the maximum degree of nodes in the tree
  • Level: the level of root node is 1, and the level of other nodes is equal to the level of parent node plus 1
  • Height of tree: the maximum level of nodes in the tree
  • Forest: 0 or more disjoint trees. Add a root to the forest and the forest will become a tree; delete the root and the tree will become a forest.

"Tree" is a very important part of computer science. It has many deformations and applications. For example, Android engineers like to use dictionary tree to access data when making address book. For Java backend, sometimes we need to query interval when we process data. For example, when did your blog pay attention to you last year, when did your people grow fastest? When did your blog read the most in a day? Line tree can be used To achieve. In the structure of HashMap in JDK 1.8, when there are more than 8 elements, they will turn into red black trees and so on.

However, for Java development, binary tree is a kind of structure with more foundation and contact.

(2) On binary trees

A binary tree is a tree structure in which each node has at most two subtrees. It has five basic forms:

1) empty trees;

2) A tree with only roots, that is, a single node;

3) Having roots and a left subtree;

4) Having roots and a right subtree;

5) It has roots and a left subtree and a right subtree.

The properties of binary trees are as follows:

  • The maximum number of nodes on layer I of binary tree is 2i-1 (I > = 1)
  • A binary tree with a depth of K has at most 2k-1 nodes (k > = 1)
  • The height of a binary tree with n nodes is at least (log2n)+1
  • In any binary tree, if the number of leaf nodes is n0 and the number of nodes with degree 2 is n2, then n0=n2+1

    The fourth proof: because the degree of all nodes in the binary tree is not greater than 2, let n0 represent the number of nodes with degree 0, n1 represent the number of nodes with degree 1, n2 represent the number of nodes with degree 2.
    Three types of nodes add up to the number of summary points, so we can get: n=n0+n1+n2 (formula 1)
    From the relationship between degrees, we can get the second equation: n=n0*0+n1*1+n2*2+1, that is, n=n1+2n2+1 (formula 2)
    When (formula 1) (formula 2) is combined, n0=n2+1 can be obtained

It is almost enough to understand the nature of the fourth article. If you want to learn more about the properties of binary trees, you can go to discrete mathematics?

Understanding of traversal of binary tree

Take this tree for example:

Preorder traversal: root node - > left subtree - > right subtree (first traverse root node, then left and right)

    //Preorder traversal takes node as root
    public void preOrder(Node node) {
        //Termination condition
        if(node == null) {
            return;
        }
        System.out.println(node.e);
        preOrder(node.left);
        preOrder(node.right);
    }

The previous traversal of this tree is: FCADBEHGM

Middle order traversal: left subtree - > root node - > right subtree (traversing root node in the middle)

    //With node as root
    public void inOrder(Node node) {
        //Termination condition
        if(node == null) {
            return;
        }
        inOrder(node.left);
        System.out.println(node.e);
        inOrder(node.right);
    }

The middle order traversal of this tree is: ACBDFHEMG

Post order traversal: left subtree - > right subtree - > root node (last traversal root node)

        //Middle order takes node as root
        public void postOrder(Node node) {
            //Termination condition
            if(node == null) {
                return;
            }
    
            postOrder(node.left);
            postOrder(node.right);
            System.out.println(node.e);
        }

The subsequent traversal of this tree is: ABDCHMGEF

Sequence traversal:

    //level traversal
    public void levelOrder() {
        Queue<Node> q = new LinkedList<>();
        q.add(root);
        while( !q.isEmpty() ) {
            Node cur = q.remove();
            System.out.println(cur.e);
            
            if(cur.left != null)
                q.add(cur.left);
            if(cur.right != null)
                q.add(cur.right);  //Later exit
        }
    }

The sequence traversal of this tree is FCEADHGBM

The so-called pre order, middle order and post order are for the root node. The left and right traversal order is the same. The pre order is that the root node traverses first, then left and right; the middle order is that the root node is placed in the middle; and the post order is that the root node is placed in the last traversal.

Middle order traversal can help us to determine the location of the root node, which is a bit scary. In the actual interview, not only will you give the results of pre order traversal and middle order traversal to rebuild the binary tree, but also give the results of post order traversal and middle order traversal or the results of sequence traversal and middle order traversal to rebuild the binary tree

No other traversal combination can restore the shape of A binary tree, because its left and right children cannot be confirmed. For example, if the front order is AB and the back order is AB, it cannot be confirmed whether node B is the left child or the right child of node A, so it cannot be restored.

Title Solution

Idea: get the position of the root node by traversing the pre order, divide the middle order sequence into left and right sub trees by using the root node, and then divide them recursively.

There is an explanation in the code.

  private Node buildTree(int[] pre, int preBegin, int preEnd, int[] mid, int midBegin, int midEnd) {
        // Preorder traversal ensures that the first element is the root node
        Node root = new Node(pre[preBegin]);
        // Used to mark the position of the root node in the middle order traversal result
        int midRootLocation= 0;
        for (int i = midBegin; i <= midEnd; i++) {
            if (mid[i] == pre[preBegin]) {  
                midRootLocation= i;
                break;
            }
        }

        if ( midRootLocation - midBegin >= 1 ) {
            // Recursive left subtree
            
            // Middle order traversal: left subtree - > root node - > right subtree (traversing root node in the middle)
            // midRootLocation marks the location of the root node in the middle order traversal result. The two ends of this location correspond to the left and right subtrees of the root node
            // midRootLocation- midBegin indicates the number of nodes to the left of the root node
            
            // Preorder traversal: root node - > left subtree - > right subtree (first traverse root node, then left and right)
            // preBegin marks the location of the root node in the traversal result
            // preBegin + 1 indicates the starting position of the left subtree of the root node
            // preBegin + (midRootLocation- midBegin) indicates the end position of the left subtree of the root node
            Node left = buildTree(pre, preBegin + 1, preBegin + (midRootLocation- midBegin),
                    mid, midBegin, midRootLocation - 1);
            root.left = left;
        }


        if ( midEnd - midRootLocation >= 1 ) {
            // Recursive right subtree
            // Same principle as above
            Node right = buildTree(pre, preEnd - (midEnd - midRootLocation) + 1, preEnd,
                    mid, midRootLocation+ 1, midEnd);
            root.right = right;
        }

        return root;
    }

Infer other things from one fact

(1) Give the results of post order traversal and middle order traversal to reconstruct binary tree

Idea: get the location of the root node through the post order, then divide the left and right subtrees in the middle order, and then divide them recursively.

The form is the same as the result of previous and middle order traversal

    private Node buildTree(int[] mid, int midBegin, int midEnd, int[] end, int endBegin, int endEnd) {
        
        Node root = new Node(end[endEnd]);
        int midRootLocation = 0;
        for (int i = midEnd; i >= midBegin; i--) {
            if (mid[i] == end[endEnd]) {
                midRootLocation = i;
                break;
            }
        }
        
        //Restore left subtree
        if (midRootLocation - midBegin >= 1 ) {
            Node left = buildTree(mid, midBegin, midRootLocation - 1, end, endBegin, endBegin + (midRootLocation - midBegin) - 1);
            root.left = left;
        }

        //Restore right subtree
        if (midEnd - midRootLocation >=  1 ) {
            Node right = buildTree(mid, midRootLocation + 1, midEnd, end, endEnd - (midEnd - midRootLocation), endEnd - 1);
            root.right = right;
        }
        
        return root;
    }

(2) Give the results of sequence traversal and middle order traversal to reconstruct binary tree

Train of thought:

(1) Getting the position of root node according to sequence traversal

(2) According to (1), the middle order is divided into left subtree and right subtree

(3) According to (2), the left subtree and the right subtree can obtain their corresponding sequence order in sequence traversal

(4) The partition is then called recursively.

  private Node buildTree(int[] mid, int[] level, int midBegin, int midEnd) {

        // The first result of sequence traversal is the root node
        Node root = new Node(level[0]);
        // Root node used to mark middle order traversal
        int midLocation = -1;
        for (int i = midBegin; i <= midEnd; i++) {
            if (mid[i] == level[0]) {
                midLocation = i;
                break;
            }
        }

        if (level.length >= 2) {
            if (isLeft(mid, level[0], level[1])) {
                Node left = buildTree(mid, getLevelArray(mid, midBegin, midLocation - 1, level), midBegin, midLocation - 1);
                root.left = left;
                if (level.length >= 3 && !isLeft(mid, level[0], level[2])) {
                    Node right = buildTree(mid, getLevelArray(mid, midLocation + 1, midEnd, level), midLocation + 1, midEnd);
                    root.right = right;
                }
            } else {
                Node right = buildTree(mid, getLevelArray(mid, midLocation + 1, midEnd, level), midLocation + 1, midEnd);
                root.right = right;
            }
        }
        return root;
    }


    // Function: judge whether the element is the left or right subtree node of the root node
    // Parameter: target is the root node isLeft determines whether children are the left or right subtree of the root node in the middle order traversal result
    // Return value: true if it is the left subtree node; otherwise, false
    private boolean isLeft(int[] array, int target, int children) {
        boolean findC = false;

        for (int temp : array) {
            if (temp == children) {
                findC = true;
            } else if (temp == target) {
                return findC;
            }
        }
        return false;
    }


    // Function: extract the midBegin and midEnd elements in the middle sequence from the level in turn, and keep the order of the elements in the level unchanged
    private int[] getLevelArray(int[] mid, int midBegin, int midEnd, int[] level) {
        int[] result = new int[midEnd - midBegin + 1];
        int curLocation = 0;
        for (int i = 0; i < level.length; i++) {
            if (contains(mid, level[i], midBegin, midEnd)) {
                result[curLocation++] = level[i];
            }
        }
        return result;
    }

    // Returns true if there is a target between the begin and end positions (including begin and end) of array.
    private boolean contains(int[] array, int target, int begin, int end) {
        for (int i = begin; i <= end; i++) {
            if (array[i] == target) {
                return true;
            }
        }
        return false;
    }

Posted by tsabar on Fri, 31 Jan 2020 16:42:01 -0800