Binary tree

Definition of Binary Tree

A binary tree is a finite set of n (n>=0) nodes. It is either an empty tree (n=0), or consists of a root node and two disjoint trees called the left subtree and the right subtree. The binary tree composition.

A binary tree is an ordered tree with at most two subtrees per node. The subtrees of a binary tree are usually called "left subtree" and "right subtree". The order of the left and right subtrees cannot be interchanged.

Various forms of binary trees

Binary trees have different forms. According to the principle of classification of general and special cases of problem handling, we can summarize and classify the five basic forms and two special forms of binary trees, which is convenient for the discussion of binary trees.

(1) The basic form of a binary tree. A binary tree can be an empty set: a left subtree or a right subtree with an empty root; or both left and right subtrees are empty.

(2) Special form of binary tree:

① Full Binary Tree

A full binary tree is a binary tree in which every node except the leaf nodes has left and right subtrees and the leaf nodes are at the bottom. Or, if a binary tree with a depth of k, there are 2 k-th power-1 nodes called a full binary tree.

②Complete Binary Tree (Complete Binary Tree)

If a tree has the maximum number of nodes in each layer except for the lowest layer, and the lowest layer is either full or lacks a number of consecutive nodes on the right side (required to start from the rightmost), then the binary tree is Complete binary tree. As shown below:

It can be said that a full binary tree is a special case of a complete binary tree.

Basic operation

According to the definition of the logical structure of the binary tree, the binary tree has the following basic operations:

  • Construction: build a binary tree
  • Search: Find root node, parent node, child node, leaf node, etc.
  • Insert: Insert a node at the specified position
  • Delete: delete the node at the specified location
  • Traversal: Follow a certain search route and visit each node in the binary tree in turn and only once.
  • Find the depth: Calculate the depth of the binary tree

Chain structure and its basic realization

Binary Linked List

Each node of the binary tree contains two pointer fields to point to the corresponding branch respectively, which we generally call a binary linked list.

Corresponding data structure description:

public class BinaryTreeNode {
    private int data;
    private BinaryTreeNode leftTreeNode;
    private BinaryTreeNode rightTreeNode;
}

In this form of binary tree, after we get a node, it is easy to get its child nodes, and it is quite difficult to get its father node. If we want to get the node, it is easy to get it. Can its father node also get its child nodes?

Trigeminal linked list

This is the trigeminal linked list. When the node stores the child node, it stores the position of the father node, as shown in the following figure:

Basic nature

  • The i-th level of the binary tree has at most 2 i-1 5 nodes (i >=1)

    Prove by induction: i = 1 level, there is only one root node, 2 1-1 = 2 0 = 1.

    The inductive hypothesis holds true for all n, n >= 1.

    Inductive proof: each node in the binary tree has at most two subtrees, then the number of nodes in the n+ layer is 2 161c82f238880e n-1 *2 = 2 n . The proposition holds

  • A binary tree with depth h has at most 2 h -1 nodes (h >= 1)

    Proof: Based on the previous property, the number of nodes on a binary tree of depth k is at most:

    20+ 21+.....+2k-1=2k - 1.

  • For any binary tree, if it contains n0 leaf nodes and n2 nodes with degree 2, there must be a relation: n0 = n2 + 1.

    Let n1 be the number of nodes with degree 1. The degree of the leaf node is 0, and the degree of a binary tree can only be 0,1,2

​ Suppose the total number of nodes on the binary tree: n = n0 + n1 + n2.

​ The total number of branches of the binary tree is: b = n1 + 2n2.

​ Each node in the tree will have a branch, but there is only one exception-the root node. So b = n-1 = n0 + n1 + n2-1. Thus, n0 = n2 + 1.

  • The depth of a complete binary tree with n nodes is [log2 n ] + 1 (square brackets indicate rounding)

​ Suppose the depth of the complete binary tree is k, then according to the second property, 2 k-1 <= n <2 k ie k-1 <= log2 n <k. Therefore, k can only be an integer. [log2 n ] + 1.

  • If a complete binary tree with n nodes is numbered from 1 to n from top to bottom and from left to right, then for any node numbered i in the complete binary tree:

    If i = 1, then the node is the root of the binary tree, Wushuang.

    If 2i> n, then the node has no left node, otherwise the node numbered 2i is the left child node.

    If 2i + 1> n, then the node has no right child node, otherwise the node numbered 2i+1 is the right child node.

    It can be proved by induction that the node numbered i, if there is a left node, then the number of the left node is 2i, and if there is a right node, it is 2i+1.

    When i=1, it is true.

    Suppose it holds when i = n, suppose there is a left child node, numbered 2n, and suppose there is a right child node, numbered 2n+1.

    If there is a left child node, it means there is a right child node. Because it is a complete binary tree, the two nodes are adjacent, so the left subtree of the node numbered n+1 is 2i+2, 2i+3.

    The conclusion is established.

Tree traversal

Traversal-Tree traversal (also called tree search) is a type of Graph Traversal, which refers to the process of visiting all nodes of a certain tree structure according to a certain rule without repetition. The specific access operation may be to check the value of the node, update the value of the node, and so on. Different traversal methods have different order of accessing nodes.

Tree traversal is also relatively common in daily life:

Example 1: Simple model of power-on self-check of electromechanical equipment

Any electromechanical equipment is composed of several parts. For example, computer hardware consists of a series of devices such as boards and non-boards. A binary tree composed of computer hardware (in fact a multi-branch tree) can be established according to the composition classification of the computer, as shown in the following figure:

The prerequisite for the normal operation of the equipment is that each device is working normally. As to whether the state of each device and its component parts and even the entire device is normal, it is obviously right to start from the bottom layer.

Equipment power-on self-test model testing steps:

  • Step 1: The self-check of each device (leaf node) is normal
  • Step 2: Perform "post-order traversal" on the binary tree until the root node is gradually detected.
  • Step 3: Report an error if there is an error message, otherwise the display device is normal.
  • Step 4: End.

The specific detection steps are as follows:

  • Left subtree: network card, etc. normal -> PCI normal -> graphics card, etc. normal -> non-PCI normal -> board card is normal.
  • Right subtree: Hard disk, etc. are normal -> External storage is normal -> Keyboard, etc. are normal -> Others are normal -> Non-board cards are normal.
  • The whole tree: the board is normal -> the non-board is normal -> the computer is normal.

    In the above detection process, "post-order traversal" means that the root node of the tree is the last to be visited, whether it is the entire tree or the left and right subtrees.

Example 2: Management of online shopping products

An online food store organizes products by category, color and variety. The classification diagram is as follows:

To automatically read the information in the tree structure through the program and print out all the product names, what should be done to make it clear and without omissions? We can first analyze the law of the data in a list, as shown in the following figure:

What is the order of printing from the tree to each node in the table?

Root node: Food

Left subtree: Meat -> Pork/Beef

Right subtree: Fruit -> (left subtree) Yellow -> Banana

​ Fruit -> (Left Subtree) Red -> Cherry

The order of printing is that the root node of the tree is visited first, whether it is the entire tree or the left and right subtrees. To visit the nodes of the tree with this rule, we can call it a word-first-order traversal.

According to different search strategies, the traversal of the binary tree can be divided into two methods: Breadth-First Search and Depth-First Search.

Tree breadth first traversal

Breadth-first traversal is a traversal strategy for connected graphs, because its idea is to start from a vertex and traverse the immediate neighboring extensive node area radially, hence the name.

The breadth-first traversal of the binary tree is also called traversal by level. It starts from the first layer (root node) of the binary tree and traverses layer by layer from top to bottom. In the same layer, the nodes are visited one by one in the order from left to right.

The breadth-first traversal operation of the tree is to visit from the root node, and then use this node as a clue to sequentially visit the node (child) sequence directly adjacent to it, and then what kind of node should be used for the next breadth traversal Click to be a clue to the beginning.

As shown in the figure below, the first visited node is A. Search the direct neighbor of A on the chain storage structure. There are two nodes B and C, and the sequence of nodes that can be visited is ABC. A new search node starts from B, that is, the first node in the sequence of visited nodes that has not been used as a clue is used as the clue, and the operation is repeated until all the nodes are visited.

The above operation process is a process in which a clue node is continuously moved back in the "visited sequence", and new access nodes are continuously added in the "visited sequence". Therefore, the operation process for the "visited sequence" is just one Queue processing method. First in, first out. The data structure of the binary tree is shown in the following figure:

public class BinaryTreeNode {
    private int data;
    private BinaryTreeNode leftTreeNode;
    private BinaryTreeNode rightTreeNode;
}

The difference between queue add and offer: add throws an exception for you to handle, offer returns false

Breadth-first traversal of the tree:

    /**
     * 广度优先遍历
     * @param root 根结点
     */
    public static void levelOrder(BinaryTreeNode root) {
        // LinkedList 实现了 Queue 我们用LinkedList来实现队列
        LinkedList<BinaryTreeNode>  nodeQueue = new LinkedList<>();
        // 根结点首先入队
        nodeQueue.offer(root);
        while (nodeQueue.size() > 0){
            BinaryTreeNode node = nodeQueue.poll();
            System.out.println(node.getData());
            BinaryTreeNode leftNode = node.getLeftTreeNode();
            if (leftNode != null){
                nodeQueue.offer(leftNode);
            }
            BinaryTreeNode rightNode = node.getRightTreeNode();
            if (rightNode != null){
                nodeQueue.offer(rightNode);
            }
        }
  }

Depth-first traversal of binary tree

According to the definition of binary tree, a binary tree is composed of the root node, the left subtree of the root node, and the right subtree of the root node. Therefore, the traversal of the binary tree can be decomposed into three "subtasks" accordingly.

①: Visit the root node

②: Traverse the left subtree (that is, visit all nodes on the left subtree in turn)

③: Traverse the right subtree (that is, visit all nodes on the right subtree in turn)

Because the left and right subtrees are both binary trees (it can be an empty binary tree). The traversal of them can continue to decompose according to the above precautions, until each subtree is an empty binary tree. This shows the sequence of the above three subtasks. If D, L, and R are used to represent these three subtasks, there are 6 possible sequences:

①: DLR ②: LDR ③: LRD ④: DRL ⑤: RDL ⑥: RLD

Usually our attached restriction is "Left first and then right", that is, subtask two is completed before subtask three, so there are only three sequences left:

  • DLR-first root traversal (preorder traversal)
  • LDR-middle root traversal (middle order traversal)
  • LRD-followed by traversal (or post-order traversal)

The three traversal methods are defined as follows:

Pre-order traversal: first visit the root node D, and then traverse the left and right subtrees of D according to the strategy of pre-order traversal. The root node is visited first, that is, every time a subtree to be traversed is encountered, the root node of the subtree is visited first.

A more popular explanation is: if I reach a node, I print the current node first, and then visit other nodes.

Middle-order traversal: First traverse the left subtree of root D according to the middle-order traversal strategy, then visit the root node D, and finally traverse the right subtree of D according to the middle-order traversal strategy. The root is visited in the middle.

A more popular explanation is: When I reach a node, I first judge whether the left node of the current node is empty, if there is, then I visit it down, if not, I print the current node.

Follow-up traversal: traverse the left and right subtrees of the root sequentially, then visit the root node D, and the root is visited last.

The popular explanation is: When I reach a node, I first judge whether the left node of the current node is empty, and if there is, then I will visit downwards. If the left subtree is empty, I will visit the existing node, if not. Just print the current node.

By definition, this is recursion, and we can write code based on recursion:

// 前序遍历  
public void frontShow(BinaryTreeNode root){
        System.out.println(root.getData());
        if (root.getLeftTreeNode() != null){
            frontShow(root.leftTreeNode);
        }
        if (root.getRightTreeNode() != null){
            frontShow(root.rightTreeNode);
        }
 }
    public void middleShow(BinaryTreeNode root){
        if (root.getLeftTreeNode() != null){
            frontShow(root.leftTreeNode);
        }
        System.out.println(root.getData());
        if (root.getRightTreeNode() != null){
            frontShow(root.rightTreeNode);
        }
    }
 public void rightShow(BinaryTreeNode root){
        if (root.getLeftTreeNode() != null){
            frontShow(root.leftTreeNode);
        }
        if (root.getRightTreeNode() != null){
            frontShow(root.rightTreeNode);
        }
        System.out.println(root.getData());
 }

The stack is the most commonly used auxiliary structure when implementing recursion. A stack is used to record the nodes yet to be traversed for future visits. We can change the recursive depth-first traversal to a non-recursive algorithm.

(1) Non-recursive preorder traversal

The order of access to each node of the binary tree is to access it all the way along its left chain, and at the same time the node is accessed, it is pushed into the stack until the left chain is empty. Then the node is popped. For each popping node, it means that the node and its left subtree have been visited and the right subtree of the node should be visited.

  • Prepare a temporary variable to point to the root node
  • Print the current node, the current temporary variable points to the left child node and pushes to the stack, repeat (2), until the left child is NULL
  • Unstack successively and point the current pointer to the right child
  • If the stack is not empty or the current pointer is not NULL, execute (2), otherwise end.

When a node is encountered, it visits the node, pushes the node into the stack, and then traverses its left subtree.

Code example 1:

public void frontShowNoRecursion(BinaryTreeNode root) {
        Stack<BinaryTreeNode> stack = new Stack<>();
        do{
            while (root != null){
                System.out.println(root.getData());
                stack.push(root);
                root = root.getLeftTreeNode();
            }
            if (!stack.isEmpty()){
                 root = stack.pop();
                 root = root.getRightTreeNode();
            }
        }while (!stack.isEmpty() || root != null); // 只要栈里面有元素或者root不为null都代表没结束
    }

Code example 2:

public void frontShowNoRecursion2(BinaryTreeNode root) {
        Stack<BinaryTreeNode> stack = new Stack<>();
        if (root != null){
            stack.push(root);
            while (!stack.isEmpty()){
                BinaryTreeNode head = stack.pop();
                System.out.println(head);
                if (head.rightTreeNode != null){
                    stack.push(head.rightTreeNode);
                }
                if (head.leftTreeNode != null){
                    stack.push(head.leftTreeNode);
                }
            }
        }
 }

The idea of ​​code example 2 is: first push the root node, because the stack is first-in-last-out, and the first-order traversal is about the root, so the right node needs to be pushed into the stack first, and then the left node.

(2) Non-recursive in-order traversal

The order of middle order traversal is left-to-right. In order to achieve this feature according to the characteristics of the stack, the later we print, the sooner we enter the stack. The traversal steps are:

  • First push the entire left subtree onto the stack. The left subtree as shown below:

  • When the next node no longer has a left subtree, each node popped out of the stack is regarded as having visited its left child node, and then print the current node, and then use this way to visit the current node's child Tree.
 public void middleShowNoRecursion(BinaryTreeNode head) {
        Stack<BinaryTreeNode> stack = new Stack<>();
        while (!stack.isEmpty() || head != null) {
            if (head != null) {
                stack.push(head);
                head = head.leftTreeNode;
            } else {
                BinaryTreeNode node = stack.pop();
                System.out.println(node.getData());
                head = node.rightTreeNode;
            }
        }
   }

(3) Non-recursive post-order traversal

The post-order traversal is the left and right roots, which is 3 4 2 6 7 5 1

To traverse in the form of head, right and left: it is 1 5 7 6 2 4 3, which happens to be the reverse form of post-order traversal.

The traversal form of head, right and left is to press the left first, and then press the right. One stack collects nodes in the form of the head, right and left, and the other stack collects the pop nodes of the head, right and left, to achieve post-order traversal.

Code example 1:

 public void afterShowNoRecursion(BinaryTreeNode root) {
        if (root != null) {
            Stack<BinaryTreeNode> s1 = new Stack<>();
            Stack<BinaryTreeNode> s2 = new Stack<>();
            s1.push(root);
            while (!s1.isEmpty()) {
                BinaryTreeNode node = s1.pop();
                s2.push(node);
                if (node.leftTreeNode != null){
                    s1.push(node.leftTreeNode);
                }
                if (node.rightTreeNode != null){
                    s1.push(node.rightTreeNode);
                }
            }
            while (!s2.isEmpty()) {
                System.out.println(s2.pop().getData());
            }
        }
    }

Reference

  • "A New Perspective on Data Structure and Algorithm Analysis" Edited by Zhou Xingni, Ren Zhiyuan, Ma Yanzhuo, Fan Kai

北冥有只鱼
147 声望36 粉丝