Detailed explanation of binary tree (you deserve it)


Erha: woof, woof, woof, woof - > first of all, I wish all program apes a happy holiday and present you with the knowledge related to binary tree

Binary tree

Tree type

What is a tree

Tree is a nonlinear data structure. It is a set with hierarchical relationship composed of n (n > = 0) finite nodes. When n is equal to 0, it is an empty tree. It is called a tree because it looks like an upside down tree, which means that its roots face up and its leaves face down. Therefore, it also has the following characteristics:
1. There is a special node called the root node. The root node has no precursor node.
2. Except for the root node, the other nodes are divided into m (M > 0) disjoint sets T1, T2,..., Tm, in which each set Ti (1 < = I < = m) is a subtree similar to the tree. The root node of each subtree has only one precursor, and can have 0 or more successors
3. The tree is recursively defined.

Concept of tree

1. Node degree: the number of subtrees contained in a node is called the degree of the node; As shown in the figure above: A is 2.
2. Tree degree: the degree of the largest node in a tree is called the degree of the tree; As shown in the figure above: the degree of the tree is 2.
Leaf node or terminal node: a node with a degree of 0 is called a leaf node; As shown in the figure above, node D E F G is a leaf node.
3. Parent node or parent node: if A node contains child nodes, this node is called the parent node of its child nodes; As shown in the figure above, A is the parent node of B, and B is the node of D and E.
4. Child node or child node: the root node of the subtree contained in A node is called the child node of the node; As shown in the figure above, B is the child node of A, and F and G are the child nodes of C.
5. Root node: nodes without parent nodes in A tree are called root nodes, as shown in Figure: A.
Node hierarchy: defined from the root, the root is the first layer, and the child node of the root is the second layer, and so on;
6. Height or depth of tree: the maximum level of nodes in the tree; As shown in the figure above: the height of the tree is 3.
Note: the concepts mentioned above are the most basic and important. We must remember them. The following concepts only need to be understood:
Non terminal node or branch node: node whose degree is not 0.
Sibling node: nodes with the same parent node are called sibling nodes.
Cousin node: nodes with parents on the same layer are cousins to each other.
Ancestor of a node: all nodes from the root to the branch through which the node passes.
Descendant: any node in the subtree with a node as the root is called the descendant of the node.
Forest: a collection of m (m > = 0) disjoint trees is called forest

Tree expression

Generally speaking, the structure type of tree is much more complex than linear structure, and its storage difficulty is also more complex. In practice, there are many kinds of tree storage, such as parent representation, child representation, etc. I will talk about the most commonly used child brother representation here

class TreeNode{
int val;
TreeNode firstChild;
TreeNode nextBrother;
}


Definition of binary tree

What is a binary tree

A binary tree is a finite set of nodes, which is either empty or composed of a root node and two disjoint binary trees called left subtree and right subtree respectively.
Characteristics of binary tree:

  1. Each node has at most two subtrees, that is, the binary tree does not have nodes with a degree greater than 2.
  2. The subtree of a binary tree can be divided into left and right, and the order of its subtrees can not be reversed. Therefore, a binary tree is an ordered tree.

Two special binary trees

1. Full binary tree: a binary tree. If the number of nodes in each layer reaches the maximum, the binary tree is a full binary tree. In other words, if the number of layers of a binary tree is K and the total number of nodes is, it is a full binary tree.
2. Complete binary tree: complete binary tree is a highly efficient data structure. Complete binary tree is derived from full binary tree. A binary tree with depth K and n nodes is called a complete binary tree if and only if each node corresponds to the nodes numbered from 1 to n in the full binary tree with depth K. It should be noted that full binary tree is a special complete binary tree.

Binary tree property

1. If the specified number of layers of root node is 1, there are at most (i > 0) nodes on layer i of a non empty binary tree.
2. If the depth of the binary tree with only the root node is specified as 1, the maximum number of nodes of the binary tree with depth K is (k > = 0).
3. For any binary tree, if the number of leaf nodes is n0 and the number of non leaf nodes with degree 2 is n2, then n0 = n2+1.
4. The depth k of a complete binary tree with n nodes is rounded up.
5. For a complete binary tree with n nodes, if all nodes are numbered from 0 from top to bottom and from left to right, the nodes with sequence number i are:
If I > 0, parental serial number: (i-1)/2; i=0, I is the root node number, no parent node
If 2i+1 < n, left child serial number: 2i+1, otherwise there is no left child
If 2i+2 < n, right child serial number: 2i+2, otherwise there is no right child

Storage of binary tree

The storage structure of binary tree is divided into sequential storage and chain storage similar to linked list.
Among them, we only need to focus on the chain storage of binary tree: the chain storage of binary tree is referenced by nodes one by one. The common representations include binary and trigeminal representations, as follows:

// Child representation
class TreeNode {
 int val; // Data domain
 TreeNode left; // The reference of the left child often represents the whole left subtree with the left child as the root
 TreeNode right; // The reference of the right child often represents the whole right subtree with the right child as the root
}
// Child parent representation
class TreeNode {
 int val; // Data domain
 TreeNode left; // The reference of the left child often represents the whole left subtree with the left child as the root
 TreeNode right; // The reference of the right child often represents the whole right subtree with the right child as the root
 TreeNode parent; // The root node of the current node
}

Generally speaking, the most common representation we use is the child representation

Traversal implementation of binary tree

Traversal means that each node in the tree is accessed once and only once along a search route. The operation of the access node depends on the specific application problem (for example, print the node content and add 1 to the node content). Traversal is one of the most important operations on a binary tree
The basis of other operations.
**When traversing a binary tree, if there is no agreement, everyone traverses it in their own way, and the results are chaotic
If the rules are agreed, everyone's traversal result of the same tree must be the same. If N represents the root node, L represents the left subtree of the root node and generation R
For the right subtree of the table root node, there are the following traversal methods according to the order of traversing the root node:

  1. NLR: Preorder Traversal (also known as Preorder Traversal) - access the root node - > the left subtree of the root - > the right subtree of the root.
  2. LNR: inorder traversal - left subtree of root - > root node - > right subtree of root.
  3. LRN: postorder traversal - left subtree of root - > right subtree of root - > root node**

Preorder traversal

Preorder traversal is to access the root node first, then the left child of the root, and finally the right child of the root
As shown in the figure:

Recursive implementation

The core code is as follows:

class TreeNode {
    int val; // Data domain
    TreeNode left; // The reference of the left child often represents the whole left subtree with the left child as the root
    TreeNode right; // The reference of the right child often represents the whole right subtree with the right child as the root
    public TreeNode(int val){
        this.val=val;
    }
}
public class Main {
    //Preorder traversal 
    public void preOrderTraversal(TreeNode root){
        //If the root node is empty, return;
        if (root==null){
            return;
        }
        System.out.println(root.val);//Access root node
        preOrderTraversal(root.left);//Start recursing the left child of the root
        preOrderTraversal(root.right);//Start recursing the right child of the root
    }
}

For example: Preorder traversal of binary tree

Main idea: this question mainly tests our familiarity with the preorder traversal of binary trees. However, we should note that the return type is List, so if we use recursion, we need to receive their return type
The code is as follows:

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> ret=new LinkedList<>();
          if (root==null){
              return ret;
          }
          ret.add(root.val);
          List<Integer> left=new LinkedList<>(preorderTraversal(root.left));
          ret.addAll(left);
          List<Integer> right=new LinkedList<>(preorderTraversal(root.right));
          ret.addAll(right);
          return ret;
    }
}

Non recursive implementation

When we do not use recursion for traversal, we need to use a stack to realize the sequential traversal of the binary tree. The operations are as follows: first put the root node into the stack, take out the nodes in the stack, and then put the right child and left child of the root into the stack in turn for circular traversal
The core code is as follows

public void preOrderTraversal(TreeNode root){
        Stack<TreeNode> stack=new Stack<>();//Create a stack
        if (root==null){
            return;
        }
        TreeNode cur=root;
        stack.push(cur);
        while (!stack.isEmpty()){
            TreeNode node=stack.pop();
            System.out.println(node.val);
            //Put the right child first, and then the left child, because the stack is a first in, first out feature
            if(node.right!=null){
                //If the right child is not empty, put it into the stack
                stack.push(node.right);
            }
            if (node.left!=null){
                //If the left child is not empty, put it into the stack
                stack.push(node.left);
            }
        }
    }

Medium order traversal

Middle order traversal is to access the left child of the root first, then the root, and finally the right child of the root
As shown in the figure:

Recursive implementation

The core code is as follows:

class TreeNode {
    int val; // Data domain
    TreeNode left; // The reference of the left child often represents the whole left subtree with the left child as the root
    TreeNode right; // The reference of the right child often represents the whole right subtree with the right child as the root
    public TreeNode(int val){
        this.val=val;
    }
}
//Medium order traversal
    public void inOrderTraversal(TreeNode root){
        //If the root node is empty, return;
        if (root==null){
            return;
        }
        inOrderTraversal(root.left);//Start recursing the left child of the root
        System.out.println(root.val);//access node 
        inOrderTraversal(root.right);//Start recursing the right child of the root
    }

Middle order traversal of binary tree

Idea: it examines the middle order traversal of binary tree, and its details are the same as those above

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
         List<Integer> ret=new LinkedList<>();
        if (root==null){
            return ret;
        }
       // 
        List<Integer> left=inorderTraversal(root.left);
        ret.addAll(left);
        ret.add(root.val);
        List<Integer> right=inorderTraversal(root.right);
        ret.addAll(right);
        return ret;
    }
}

Non recursive implementation

When we do not use recursion for traversal, we need to use a stack to realize the sequential traversal of the binary tree. The operation is as follows: first put the child of the root into the stack, take out the node at the top of the stack, and then put the right child and left child of the root into the stack in turn for circular traversal
The core code is as follows

public void inOrderTraversal(TreeNode root){
        Stack<TreeNode> stack=new Stack<>();
        if (root==null){//Determine whether the tree is empty
            return;
        }
        TreeNode cur=root;
        while (cur!=null||!stack.isEmpty()){
            while (cur!=null){//Put the left child in it
                stack.push(cur);
                cur=cur.left;
            }
            cur=stack.pop();
            System.out.println(cur.val);
            cur=cur.right;
        }
    }

Postorder traversal

Post order traversal is to access the left child of the root first, and the right child finally accesses the root node
As shown in the figure:

Recursive traversal

class TreeNode {
    int val; // Data domain
    TreeNode left; // The reference of the left child often represents the whole left subtree with the left child as the root
    TreeNode right; // The reference of the right child often represents the whole right subtree with the right child as the root
    public TreeNode(int val){
        this.val=val;
    }
}
//Postorder traversal
    public void postOrderTraversal(TreeNode root){
        //If the root node is empty, return;
        if (root==null){
            return;
        }
        postOrderTraversal(root.left);//Start recursing the left child of the root
        postOrderTraversal(root.right);//Start recursing the right child of the root
        System.out.println(root.val);//access node 
    }

Binary Tree Postorder Traversal

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ret=new LinkedList<>();
        if (root==null){
            return ret;
        }
        List<Integer> left=postorderTraversal(root.left);
        ret.addAll(left);
        List<Integer> right=postorderTraversal(root.right);
        ret.addAll(right);
        ret.add(root.val);
        return ret;
    }
}

Non recursive traversal

Like the non recursive traversal in the preorder and middle order, a stack needs to be used, and the method is the same. However, during the traversal, a judgment needs to be made to judge that the right child of the node to be traversed is empty or its right child has been traversed. If the above conditions are met, the traversal is carried out.

public void postOrderTraversal(TreeNode root){
        Stack<TreeNode> stack=new Stack<>();
        if (root==null){
            return;
        }
        TreeNode prev=null;
        TreeNode cur=root;
        while (cur!=null||!stack.isEmpty()){
            while (cur!=null){
                stack.push(cur);
                cur=cur.left;
            }
            cur=stack.peek();
            //If the right child of the node is empty or its right child has been traversed
            if (cur.right==null||cur.right==prev){
                System.out.println(cur.val);
                stack.pop();
                prev=cur;
                cur = null;// This cur has been printed and cannot be put on the stack again
            }
            else {
                cur=cur.right;
            }
        }
    }

Hierarchical traversal of binary tree

Let the number of layers of the root node of the binary tree be 1. Sequence traversal starts from the root node of the binary tree. First, access the root node of the first layer, then access the nodes on the second layer from left to right, then the nodes on the third layer, and so on. The process of accessing the nodes of the tree layer by layer from top to bottom and from left to right is as follows
Sequence traversal.
As shown in the figure:

public void levelOrderTraversal(TreeNode root) {
        if(root == null) return;//Determine whether the tree is empty
        Queue<TreeNode> queue = new LinkedList<>();//Create a queue
        queue.offer(root);//Put in root node
        while (!queue.isEmpty()) {
            TreeNode top = queue.poll();
            System.out.print(top.val+" ");
            if(top.left != null) {//Determine whether the left child is empty
                queue.offer(top.left);
            }
            if(top.right!=null) {//Determine whether the right child is empty
                queue.offer(top.right);
            }
        }
    }

Tags: Java data structure

Posted on Sun, 24 Oct 2021 11:22:23 -0400 by Deany