Algorithm design and analysis binary tree

sketch

Recursive order (recursive access)

  • Recursion: access order

Recursive order: 1, 2, 4, 4, 2, 5, 5, 5, 2, 1, 3, 6, 6, 3, 7, 7, 7, 3, 1
First order: print the first time in the recursive order, and no operation during the second and third access: 1, 2, 4, 5, 3, 6 and 7
Middle order: print the second time in the recursive order, and no operation during the first and third accesses: 4, 2, 5, 1, 6, 3, 7
Post order: print when the recursive order appears for the third time. There is no operation during the first and second access: 4, 5, 2, 6, 7, 3, 1

public static void preOrderRecur(Node head){
        if (head == null){
            return;
        }
        System.out.println(head.value + " ");
        preOrderRecur(head.left);
        preOrderRecur(head.right);
    }

    public static void inOrderRecur(Node head){
        if (head == null){
            return;
        }
        inOrderRecur(head.left);
        System.out.println(head.value + " ");
        inOrderRecur(head.right);
    }

    public static void posOrderRecur(Node head){
        if (head == null){
            return;
        }
        posOrderRecur(head.left);
        posOrderRecur(head.right);
        System.out.println(head.value + " ");
    }

Non recursive access

  • Non recursive: implemented by handwriting stack
    1. Priority:
    (1) Pop up a node from the stack
    (2) Print (process) the node
    (3) Press right then left (if any) into the stack (ensure that the stack is left and right)
    (4) Repeat until the stack is empty
public static void preOrderRecur(Node head){
        if (head != null){
            Stack<Node> stack = new Stack<>();
            stack.push(head);
            while (!stack.isEmpty()){
                Node p = stack.pop();
                System.out.println(p.value);
                if (p.right != null){
                    //Press in the right subtree first
                    stack.push(p.right);
                }
                if (p.left != null){
                    stack.push(p.left);
                }
            }
        }
    }

Sequence 2:
(1) The left boundary of each subtree is stacked
(2) Print (process) in the process of pop-up in turn, and pop up the right subtree (and its left boundary) of the node into the stack
(3) Repeat 1, 2

public static void inOrderRecur(Node head){
        if (head != null){
            Stack<Node> stack = new Stack<Node>();
            Node p = head;
            while (!stack.isEmpty() || p != null){
                if (p != null){
                    stack.push(p);
                    p = p.left;
                }else {
                    p = stack.pop();
                    System.out.println(p.value + " ");
                    p = p.right;
                }
            }
        }
    }

3. Post order: a collection stack is required
(1) Pop up a node from the stack
(2) Put the node into the collection stack (the parent node is at the bottom of the collection stack)
(3) Press left to right (if any) into the stack (ensure that the collection stack is right and left)
(4) Repeat until the stack is empty
(5) Print (process) collection stack (stack out is left, right, parent)

public static void preOrderRecur(Node head){
        if (head != null){
            Stack<Node> stack = new Stack<>();
            Stack<Node> temp = new Stack<>(); //Collection stack
            stack.push(head);
            while (!stack.isEmpty()){
                Node p = stack.pop();
                temp.push(p); //Push into collection stack
                if (p.left != null){
                    //Press in the left subtree first
                    stack.push(p.left);
                }
                if (p.right != null){
                    stack.push(p.right);
                }
            }
            while (!temp.isEmpty()){
                Node p = temp.pop();
                System.out.println(p.value + " ");
            }
        }
    }

Visual print tree

public static class Node{
        int value;
        Node left;
        Node right;

        public Node(int date){
            //Constructor
            this.value = date;
        }
    }

    public static void printTree(Node head){
        System.out.println("Binary Tree");
        printInOrder(head, 0, "H", 12);
        System.out.println();
    }

    public static void printInOrder(Node head, int height, String to, int len){
        //head node
        //height current depth
        //to represents the relationship, H: head node; ↖:  Indicates that the parent node is on its upper left; ↙:  Indicates that the parent node is at its lower left
        //The display length of len tree can be adjusted by itself
        if (head == null){
            return;
        }
        printInOrder(head.right, height + 1, "↙", len);
        String val = to + head.value + to;
        int lenM = val.length();
        int lenL = (len - lenM) / 2;
        int lenR = len - lenM - lenL;
        val = getSpace(lenL) + val + getSpace(lenR);
        System.out.println(getSpace(height * len) + val);
        printInOrder(head.left, height + 1, "↖" , len);
    }

    public static String getSpace(int num){
        String space = " ";
        StringBuilder buf = new StringBuilder(" ");
        for (int i = 0; i < num; i++){
            buf.append(space);
        }
        return buf.toString();
    }

    public static void main(String[] args) {
        Node head = new Node(1);
        head.left = new Node(2);
        head.right = new Node(3);
        head.left.left = new Node(4);
        head.left.right = new Node(5);
        head.right.left = new Node(6);
        head.right.right = new Node(7);
        head.left.right.right = new Node(8);
        head.right.right.left = new Node(9);
        printTree(head);
    }

Ideal diagram:

Rendering: H: head node; ↖: Indicates that the parent node is on its upper left; ↙: Indicates that the parent node is at its lower left

Topic 1: find the maximum width of the tree (breadth first)

level traversal

public static void levelOrder(Node head){
        if (head == null){
            return;
        }
        Queue<Node> queue = new LinkedList<Node>();
        Node p = head;
        queue.add(p);
        while (!queue.isEmpty()){
            p = queue.poll();
            if (p.left != null){
                queue.add(p.left);
            }
            if (p.right != null){
                queue.add(p.right);
            }
        }
    }
  • Written test code:
    (1) Idea: three values, the layer where the current node is located, the number of nodes in each layer, and the layer with the most nodes in the tree
    (2) Implementation method: combined with hierarchical traversal, HashMap is used to bind each node to the corresponding layer. When leaving the queue, add one node to the number of corresponding layers, and finally find the layer with the most nodes
public static int levelMax(Node head){
        if (head == null){
            return 0;
        }
        Queue<Node> queue = new LinkedList<Node>();
        int level = 1; //Number of layers
        int levelNum = 0; //Number of nodes in this layer
        int nodeMax = 0; //Maximum number of nodes
        HashMap<Node, Integer> map = new HashMap<>();
        Node p = head;
        map.put(p, 1);
        queue.add(p);
        while (!queue.isEmpty()){
            p = queue.poll();
            if (map.get(p) == level){
                //The current node layer is the same as the layer to be recorded
                levelNum++;
            }else {
                //The current node layer is different from the layer to be recorded. Enter the next layer to update the data
                nodeMax = Math.max(nodeMax, levelNum); //Maximum update
                level++; //Layer number update
                levelNum = 1; //Reset the node number of the corresponding layer
            }
            if (p.left != null){
                map.put(p.left, level + 1);
                queue.add(p.left);
            }
            if (p.right != null){
                map.put(p.right, level + 1);
                queue.add(p.right);
            }
        }
        return  nodeMax;
    }
  • Interview ideas:
    (1) Idea: four values, the end node of the current layer, the next end node of the current layer, the number of nodes in each layer, and the layer with the most nodes in the tree
    (2) Implementation method: in combination with hierarchical traversal, the end node of the first layer is head. In the second layer, the end node of the next layer recorded in the first layer will be used as the end node of the current layer. The next end node will be updated while going out and in, and the number of nodes in each layer will be recorded at the same time.
public static int levelMax(Node head){
        if (head == null){
            return 0;
        }
        Queue<Node> queue = new LinkedList<Node>();
        Node levelEnd = head; //End node of this layer
        Node levelNextEnd = null; //Next end node
        int levelNum = 0; //Number of nodes in this layer
        int nodeMax = 0; //Maximum number of nodes
        Node p = head;
        queue.add(p);
        while (!queue.isEmpty()){
            p = queue.poll();
            if (p.left != null){
                //Update levelNextEnd at any time
                levelNextEnd = p.left;
                queue.add(p.left);
                levelNum++;
            }
            if (p.right != null){
                levelNextEnd =p.right;
                queue.add(p.right);
                levelNum++;
            }
            if (p == levelEnd){
                //Traverse to the last node of the layer and update the data
                levelEnd = levelNextEnd;
                nodeMax = Math.max(levelNum, nodeMax);
                levelNum = 0;
                levelNextEnd = null;
            }
        }
        return  nodeMax;
    }

Topic 2: judgment of related concepts of binary tree

1. How to determine whether it is a search binary tree (sort binary tree)

Idea: the sequence obtained by traversing the binary tree in medium order. If it is an ascending sequence, it is a search binary tree

Recursive middle order:
 public static void inOrderRecur(Node head){
        if (head == null){
            return;
        }
        inOrderRecur(head.left);
        System.out.println(head.value + " ");
        inOrderRecur(head.right);
    }
Code modification:
public static int preValue = Integer.MIN_VALUE;
    public static boolean checkBST(Node head){
        if (head == null){
            return true;
        }
        if (!checkBST(head.left)){
            //Recursive left subtree
            return false;
        }
        if (head.value <= preValue){
            //Right < = left, not meeting BST
            return false;
        }else {
            preValue = head.value;
        }
        return checkBST(head.right);
    }
Non recursive middle order:
public static void inOrderRecur(Node head){
        if (head != null){
            Stack<Node> stack = new Stack<Node>();
            Node p = head;
            while (!stack.isEmpty() || p != null){
                if (p != null){
                    stack.push(p);
                    p = p.left;
                }else {
                    p = stack.pop();
                    System.out.println(p.value + " ");
                    p = p.right;
                }
            }
        }
    }
Code modification:
public static boolean checkBST(Node head){
        if (head != null){
            Stack<Node> stack = new Stack<Node>();
            Node p = head;
            int preValue = Integer.MIN_VALUE;
            while (!stack.isEmpty() || p != null){
                if (p != null){
                    stack.push(p);
                    p = p.left;
                }else {
                    p = stack.pop();
                    if (p.value <= preValue){
                        return false;
                    }else {
                        preValue = p.value;
                    }
                    p = p.right;
                }
            }
        }
        return true;
    }

Summary: both are dynamic checks. You can also store the sequence of binary tree into the set of list class to judge whether the sequence is orderly

2. How to judge whether it is a complete binary tree

Idea: breadth first (level traversal), judgment 1: return false as long as there is a node with a right child and no left child; Judgment 2: start from the incomplete node of the first subtree, and the subsequent nodes must be leaf nodes

public static boolean isCBT(Node head){
        if (head == null){
            return true;
        }
        Queue<Node> queue = new LinkedList<Node>();
        boolean flag = false; //Indicates whether a node with incomplete subtree is encountered
        Node p = head;
        queue.add(p);
        while (!queue.isEmpty()){
            p = queue.poll();
            if ((p.left == null && p.right != null) || flag && (p.left != null && p.right != null)){
                //Condition of not full binary tree
                return false;
            }
            if (p.left != null){
                queue.add(p.left);
            }
            if (p.right != null){
                queue.add(p.right);
            }
            if (p.left == null || p.right == null){
                //Subtree incomplete
                flag = true;
            }
        }
        return true;
    }

3. How to judge whether it is a full binary tree

Idea: count the depth l of the binary tree, and the number of nodes is N. if N = 2 ^ L - 1, it is a full binary tree
Methods: call recursively to find L and N of the left subtree; Find L, n of right subtree; Find the L, n of the tree

public static boolean isFull(Node head){
        ReturnType returnType = process(head);
        //1 < < x, x power of 2
        return returnType.num == (1 << returnType.height - 1);
    }

    public static class ReturnType{
        //Custom type
        public int height; //Tree height
        public int num; //Number of nodes

        public ReturnType(int height, int num){
            //Constructor
            this.height = height;
            this.num = num;
        }
    }

    public static ReturnType process(Node head){
        if (head == null){
            return new ReturnType(0, 0);
        }
        ReturnType leftTree = process(head.left); //Recursive call
        ReturnType rightTree= process(head.right);
        int height = Math.max(leftTree.height, rightTree.height) + 1; //height
        int num = leftTree.num + rightTree.num + 1; //Number of nodes
        return new ReturnType(height, num);
    }

4. How to judge whether it is a balanced binary tree

Idea: balanced binary tree conditions: (1) left subtree balance, left height; (2) Right subtree balance, right height; (3) | left height - right height | < = 1
Combined with recursive implementation

    public static boolean isBalancedTree(Node head){
        return process(head).isBalanced;
    }

    public static class ReturnType{
        //Custom type
        public boolean isBalanced; //Is the tree balanced
        public int height; //Tree height

        public ReturnType(boolean isBalanced,int height){
            //Constructor
            this.isBalanced = isBalanced;
            this.height = height;
        }
    }

    public static ReturnType process(Node head){
        if (head == null){
            //Empty tree balance, height 0
            return new ReturnType(true, 0);
        }
        ReturnType leftTree = process(head.left); //Recursive call
        ReturnType rightTree= process(head.right);
        int height = Math.max(leftTree.height, rightTree.height) + 1;
        boolean isBalanced = leftTree.isBalanced && rightTree.isBalanced
                && Math.abs(leftTree.height - rightTree.height) < 2;
        return new ReturnType(isBalanced, height);
    }

5. Binary tree recursive routine (tree DP, dynamic programming)

Idea: when judging what kind of binary tree is? Decomposed into whether the left subtree is satisfied, whether the right subtree is satisfied, and whether the left and right subtrees are satisfied after merging (tree type DP)

  1. Binary tree recursive routine: judge whether it is a search binary tree
    (1) Left tree search, left max, min; (2) Right tree search, min, max; (3) Left Max < head Value < right min
	public static boolean isBSTree(Node head){
        return process(head).isBST;
    }

    public static class ReturnType{
        //Custom type
        public boolean isBST; //Is the tree a search tree
        public int min; //Minimum value of tree
        public int max; //Maximum value of the tree

        public ReturnType(boolean isBST,int min, int max){
            //Constructor
            this.isBST = isBST;
            this.min = min;
            this.max = max;
        }
    }

    public static ReturnType process(Node head){
        if (head == null){
            //The min and max of the empty tree are not easy to set, so they are directly set to null
            return null;
        }
        ReturnType leftTree = process(head.left); //Recursive call
        ReturnType rightTree= process(head.right);
        int min = head.value; //Setting of min and max
        int max = head.value;
        if (leftTree != null){
            min = Math.min(min, leftTree.min);
            max = Math.max(max, leftTree.max);
        }
        if (rightTree != null){
            min = Math.min(min, rightTree.min);
            max = Math.max(max, rightTree.max);
        }
        boolean isBST = true; //isBST settings
        if (leftTree != null && (leftTree.isBST || leftTree.max > head.value)){
            //If the left is not empty: the left subtree is not a search binary tree or the left Max > head, value
            //Does not satisfy the search Binary Tree
            isBST = false;
        }
        if (rightTree != null && (rightTree.isBST || rightTree.min < head.value)){
            isBST = false;
        }
        return new ReturnType(isBST, min, max);
    }
  1. This method is used to judge full binary tree and balanced binary tree

Topic 3: the lowest common ancestor of two nodes

  • Written examination ideas:
    (1) Use HashMap to establish the relationship between each node and its parent node
    (2) Use HashSet to store all the ancestor nodes of node1
    (3) Find the ancestor node of node2 in turn, and find whether there are the same in the HashSet of node1. When the first one is the same, it is the lowest common ancestor node
    public static Node lowCommonAncestor(Node head, Node node1, Node node2){
        HashMap<Node, Node> fatherMap = new HashMap<Node,Node>();
        fatherMap.put(head, head); //The parent node of the head node is itself
        process(head, fatherMap);
        HashSet<Node> setNode1 = new HashSet<Node>(); //Establish the ancestor node set of node1
        Node p = node1;
        while (p != fatherMap.get(head)){
            //Traverse the header node from the current node node1 point from bottom to top
            setNode1.add(p);
            p = fatherMap.get(p);
        }
        p = node2;
        while (p != fatherMap.get(head)){
            //Traverse the header node from the current node2 node from bottom to top
            if (setNode1.contains(p)){
                //Having the same is the lowest common ancestor node
                return p;
            }
            p = fatherMap.get(p);
        }
        return null;
    }

    public static void process(Node head, HashMap<Node, Node> fatherMap){
        //Establishment of relationship between node and parent node
        if (head == null){
            return;
        }
        fatherMap.put(head.left, head);
        fatherMap.put(head.right, head);
        process(head.left, fatherMap);
        process(head.right, fatherMap);
    }
  • Interview ideas:
    Situation analysis:
    (1) Node1 is the ancestor node of node2, or node2 is the ancestor node of node1
    (2) node1 and node2 are not ancestral nodes to each other. They need to traverse upward to find them
    Method: traverse the tree node, encounter node1, node2 returns the corresponding node, and the other nodes just return null
    Case 1:

Case 2:

    public static Node lowCommonAncestor(Node head, Node node1, Node node2){
        if (head == null || head == node1 || head == node2){
            //Returns the corresponding value when node1 and node2 are selected
            return head;
        }
        Node left = lowCommonAncestor(head.left, node1, node2);
        Node right = lowCommonAncestor(head.right, node1, node2);
        if (left != null && right != null){
            //In case 2, the two nodes are not common ancestors to each other
            //At the same time, it returns to node1 on the left and right, and node2 is the lowest common ancestor
            return head;
        }
        //When one of node1 and node2 is encountered, it returns a non empty value
        return left != null ? left : right;
    }

Topic 4: subsequent node search of tree (including parent node)


Successor node: in the middle order traversal sequence, the next node of node is called the successor node of node
Situation analysis:
(1) If a node has a right subtree, the successor node is the leftmost node of the right subtree
(2) If the node has no right subtree, the subsequent node is the rightmost node of the left subtree of that node (judge whether the current node is the left subtree of its ancestor node)

    public static Node getSuccessorNode(Node node){
        if (node == null){
            return node;
        }
        if (node.right != null){
            //The current node has a right subtree
            return getLeftNode(node);
        }
        else {
            //No right subtree, find through parent node
            Node parent = node.parent;
            while (parent != null && parent.left != node){
                //Is this node the left subtree of the parent node
                node = parent;
                parent = node.parent;
            }
            return parent;
        }
    }

    public static Node getLeftNode(Node node){
        if (node == null){
            return node;
        }
        while (node.left != null){
            node = node.left;
        }
        return node;
    }

Topic 5: serialization and deserialization of binary tree


Idea: use "#" to represent the representation between separated nodes; use "#" to represent empty nodes

Preorder sequence: serialization
    public static String serialByPre(Node head){
        if (head == null){
            return "#_";
        }
        String str = head.value + "_";
        str += serialByPre(head.left);
        str += serialByPre(head.right);
        return str;
    }
Deserialization
    public static Node reconByPreString(String preStr){
        //Split the string and store it in the queue
        String[] values = preStr.split("_");
        Queue<String> queue = new LinkedList<String>();
        for (int i = 0; i < values.length; i++){
            queue.add(values[i]);
        }
        return reconPreOrder(queue);
    }

    public static Node reconPreOrder(Queue<String> queue){
        //Restore tree through queue
        String value = queue.poll();
        if (value.equals("#")){
            return null;
        }
        Node head = new Node(Integer.valueOf(value));
        head.left = reconPreOrder(queue);
        head.right = reconPreOrder(queue);
        return head;
    }

Topic 6: origami problem


Problem analysis:

Idea: find the law of origami and use the way of tree to express it. Both the parent node and the left subtree are concave, and the right subtree is convex
Methods: given N, determine the height of the tree, and simulate the middle order traversal of the tree to get the crease direction

    public static void printAllFolds(int N){
        printProcess(1, N, true);
    }

    public static void printProcess(int height, int N, Boolean down){
        //Middle order traversal of simulated tree
        //Height current height, N is the tree height
        //Down = true - > concave, down = false - > convex
        if (height > N){
            return;
        }
        printProcess(height + 1, N, true);
        System.out.println(down ? "concave" : "Convex");
        printProcess(height + 1, N, false);
    }

Keywords: Java Algorithm data structure Binary tree

Added by Akira on Tue, 25 Jan 2022 10:16:47 +0200