[Data structure] Basic explanation of binary tree (C++)

Trees are an important data structure.

Maybe when you first come into contact with trees, you may think that trees are difficult to understand and difficult to operate, but in fact, many foods in the real world use the data structure of trees.

For example, there are many provinces or cities under a country, there are counties under the provinces, and there are divisions under the counties, etc. There is a hierarchical relationship between them.

For example, hard disk management, there are some files under the C drive, and there are some subdirectories under these files, which is also a hierarchical relationship.

The three most typical operations involved in data management:insertdeletelook up

tree definition

Tree: A finite set of n (n>=0) nodes.

When n=0, there are no nodes in the tree, which is called an empty tree.

For any non-empty tree, it has the following properties:

  • There is a root node in the tree
  • The remaining nodes can be divided into m disjoint finite sets, each set itself is also a tree, called the subtree of the original tree.
  • Subtrees cannot intersect.
  • Except for the root node, each node has one and only one parent node.
  • A tree with n nodes has n-1 edges. Because every node has an edge pointing to it, except the root node.

Basic Terms for Trees

  • Node : An independent unit in a tree.
  • Degree of a node : the number of subtrees of a node.
  • Degree of the tree : the maximum value of the degree of each node in the tree.
  • Leaf : nodes with degree 0. Also called a leaf node or a terminal node.
  • Non-terminal node : A node whose degree is not 0 is called a non-terminal node or a branch node. All but the lowest leaf nodes are called non-terminal nodes.
  • Internal nodes : In addition to root nodes, non-terminal nodes are also called internal nodes. That is, the nodes between the root node and the leaf nodes.
  • Parent node : A node with a subtree is the parent node of the root node of its subtree.
  • Child node : If A is the parent node of B, then B is the child node of A, and the child node is also called the child node.
  • Brother node : Children of the same parent node can be called brothers to each other.
  • Cousins : Nodes whose parents are on the same layer are cousins.
  • Ancestors : All nodes on the branch from the root node to the node.
  • Descendants : Any node of the subtree with a node as the root node is called the descendant of the given node.
  • Hierarchy : The hierarchy of the tree is defined from the root, the root is the first level, and the number of successively lower levels increases.
  • Depth of the tree : The maximum level of nodes in the tree is called the depth or height of the tree. Refers to the maximum number of layers.

Graphical representation of the terms:
insert image description here

tree representation

For normal trees we can useson-brother notation

Definition of binary tree

Binary tree: a collection of n (n>=0) nodes.

When n=0, it is an empty tree.
When n is not 0, it has the following properties:

  • There is one and only one node called the root.
  • Nodes other than the root node are divided into two mutually disjoint subsets T1 and T2, which are called the left subtree and right subtree of T, respectively, and T1 and T2 themselves are also binary trees.

There are 5 basic forms of binary tree

insert image description here
Notice:The subtrees of the binary tree are divided into left and right.
insert image description here
For binary trees, these 2 are different binary trees. Although they both have only one subtree, one is the left subtree and the other is the right subtree, which belong to different binary trees.

Special form of binary tree

  1. Oblique binary tree (every node has only left child or only right child).
    insert image description here

  2. Full Binary Tree (Perfect Binary Tree): A binary tree of depth k and containing 2 k -1 nodes.
    Every level of a full binary tree is full, and there must be 2 i-1 nodes on the i-th level.
    The depth of a full binary tree with n nodes is log 2 (n+1) .
    insert image description here

  3. Complete binary tree: A binary tree with a depth of k and n nodes is a complete binary tree if and only if each node in it has a one-to-one correspondence with nodes from 1 to n in a full binary tree with a depth of k .
    insert image description here
    The right example is not a complete binary tree, because there should be a sibling node to the right of the 8-node, instead of going directly to the next left child called node.

Important properties of binary trees

  1. There are at most 2 i-1 nodes on level i of a binary tree . (n>=1)
  2. A binary tree of depth k has a maximum number of nodes of 2 k -1 nodes. (k>=1)
  3. For any binary tree, if the number of terminal nodes is n0, and the number of nodes with degree 2 is n2, then n0=n2+1.
  4. with n nodescomplete binary treeThe depth of is log 2 n (rounded down) +1.

Binary tree operations

4 traversal methods and codes (for your convenience, you can run them directly)

  1. preorder traversal (root, left, right) LeetCode:144. Preorder traversal of a binary tree
void preorderTraversal(TreeNode* root) {
    
    
    if(!root)
        return;
    cout<<root->val<<" ";
    preorderTraversal(root->left);
    preorderTraversal(root->right);
}
  1. Inorder traversal (left,root, right) LeetCode:94. Inorder traversal of a binary tree
void inorderTraversal(TreeNode* root) {
    
    
    if(!root)
        return;
    inorderTraversal(root->left);
    cout<<root->val<<" ";
    inorderTraversal(root->right);
}
  1. postorder traversal (left, right,rootLeetCode:145. Postorder traversal of binary tree
void postorderTraversal(TreeNode* root) {
    
    
    if(!root)
        return;
    postorderTraversal(root->left);
    postorderTraversal(root->right);
    cout<<root->val<<" ";
}

Notice: When the traversal sequence of the binary tree is given, when 3 traversal sequences are given and 2 are given, if the in-order traversal sequence is included, the only corresponding binary tree can be obtained, but if only the pre-order and post-order traversal are given, then A unique binary tree cannot be obtained.

  1. Hierarchical traversal (traversal according to each layer, from top to bottom, from left to right) LeetCode: 102. The hierarchical traversal of the binary tree
    uses the queue to complete the hierarchical operation of the binary tree: build a queue for storing node pointers, when the queue is not When it is empty, it means that there are still binary tree nodes that have not been traversed, then continue to loop, and traverse all the elements in the queue each time, each time the queue stores elements of one row, set a temporary pointer to the queue For the beginning node, when the node is not empty, enqueue its left and right children, because the left child is enqueued first every time, so the traversal of each line is from left to right. After the child enters the team, Then output the value of the current node, then exit the first element, and continue to traverse the next element in this row.
void levelOrder(TreeNode *root){
    
    
    queue<TreeNode*> q;
    q.push(root);
    while(!q.empty()){
    
    
    	int n=q.size();
        for(int i=0;i<n;++i){
    
    
            TreeNode *temp=q.front();
            if(temp){
    
    
                q.push(temp->left);
                q.push(temp->right);
                cout<<temp->val<<" ";
            }
            q.pop();
         }
    }
}

Test Data:

-1
2 5 -1 7 56 -1 -1 34 -1 -1 55 -1 22 -1 66 -1 -1

Code: (press ctrl+Z to end the input, and the program outputs the result)

#include<iostream>
#include<vector>
#include<string>
#include<sstream>
#include<queue>
#include<stack>
#include<algorithm>
#include<stdio.h>
#include<map>
#include<unordered_map>
using namespace std;
int em,shu;

//二叉树的结点
struct TreeNode{
    
    
    int val;//树中结点的值
    TreeNode *left,*right;//结点的左右指针
    TreeNode():left(NULL),right(NULL){
    
    } //构造函数
    TreeNode(int value):val(value),left(NULL),right(NULL){
    
    }
    TreeNode(int value,TreeNode *l,TreeNode *r){
    
    //带参数的构造函数
        val=value;  left=l;   right=r;
    }
    int Getval(){
    
    return val;}//返回结点的值
    TreeNode* Getleft(TreeNode *root){
    
    return root->left;}//返回root结点的左孩子
    TreeNode* Getright(TreeNode *root){
    
    return root->right;}//返回root结点的右孩子
};

//二叉树
class BinaryTree{
    
    
    private:
        TreeNode *root=NULL;//根结点指针
        int sum=0;//结点总数
    public:
        BinaryTree():root(NULL),sum(0){
    
    }//无参数的构造函数
        //带参数的构造函数

        //返回根结点
        TreeNode* Getroot() const {
    
    return root;}
        //返回二叉树结点的个数
        int GetBinaryTreeSize() const {
    
    return sum;}
        //判断二叉树是否为空
        bool isBinaryTreeNULL() const {
    
    return root==NULL;}
        //返回根结点的值
        int GetRootval() const {
    
    return root->val;}
        void SetRoot(TreeNode *r){
    
    root=r;}
        //构建二叉树的内部函数(前序遍历)
        TreeNode* CreatBinaryTree(vector<int> &v,int &e,int &i){
    
    
            if(i+1>=v.size())
                return NULL;
            int x=v[i];
            ++i;
            if(x==em)
                return NULL;
            else{
    
    
                ++sum;
                TreeNode *node=new TreeNode(x);
                node->val=x;
                node->left=CreatBinaryTree(v,e,i);
                node->right=CreatBinaryTree(v,e,i);
                return node;
            }
        }
        //对二叉树前序遍历
        void preorderTraversal(TreeNode* root) {
    
    
            if(!root)
                return;
            cout<<root->val<<" ";
            preorderTraversal(root->left);
            preorderTraversal(root->right);
        }
        //对二叉树中序遍历
        void inorderTraversal(TreeNode* root) {
    
    
            if(!root)
                return;
            inorderTraversal(root->left);
            cout<<root->val<<" ";
            inorderTraversal(root->right);
        }
        //对二叉树后序遍历
        void postorderTraversal(TreeNode* root) {
    
    
            if(!root)
                return;
            postorderTraversal(root->left);
            postorderTraversal(root->right);
            cout<<root->val<<" ";
        }
        //对二叉树层次遍历
        void levelOrder(TreeNode *root){
    
    
            queue<TreeNode*> q;
            q.push(root);
            while(!q.empty()){
    
    
                int n=q.size();
                for(int i=0;i<n;++i){
    
    
                    TreeNode *temp=q.front();
                    if(temp){
    
    
                        q.push(temp->left);
                        q.push(temp->right);
                        cout<<temp->val<<" ";
                    }
                    q.pop();
                }
            }
        }
};

//构建二叉树
void CreatTree(BinaryTree &T,int e){
    
    
    int x;
    vector<int> v;
    while(scanf("%d",&x)!=EOF)
        v.push_back(x);
    TreeNode *root=NULL;
    int num=0;
    root=T.CreatBinaryTree(v,em,num);
    T.SetRoot(root);
}

int main(){
    
    
    cin>>em;
    getchar();
    BinaryTree T;
    CreatTree(T,em);
    TreeNode *root=T.Getroot();//获得二叉树的根结点
    T.preorderTraversal(root);//先序遍历
    cout<<endl;
    T.inorderTraversal(root);//中序遍历
    cout<<endl;
    T.postorderTraversal(root);//后序遍历
    cout<<endl;
    T.levelOrder(root);//层次遍历
    cout<<endl;
    return 0;
}

Results of the:

insert image description here



The classic topic of binary tree

1. Symmetric binary tree

Title: 101. Symmetric Binary Tree

Code Analysis: (C++)

class Solution {
    
    
public:
    bool cmp(TreeNode *lchild,TreeNode *rchild){
    
    
        if(lchild==NULL&&rchild==NULL)//当一个结点的左右孩子都为空时对称
            return true;
        if(lchild&&!rchild||!lchild&&rchild||lchild->val!=rchild->val)
            return false;//当其中有一棵为空,另一棵不为空时不对称,或者当左右子树的值不同时也不对称
        return cmp(lchild->left,rchild->right)&&cmp(lchild->right,rchild->left);
    }
    bool isSymmetric(TreeNode* root) {
    
    
        if(!root)//如果是空树直接返回true
            return true;
        return cmp(root->left,root->right);//递归判断左右子树是否对称
    }
};

To quote a highly praised comment:

The difficulty of recursion lies in: Finding a point that can be recursed why many people think that recursion will be easy at first glance, and it will be discarded as soon as it is written. In other words, if you can't write it yourself, the key is whether you have a deep understanding of recursion.

For this question: How to find the recursive point? From the first time I got the question, the idea is as follows:
1. How to judge whether a tree is a symmetrical binary tree? Answer: If the given root node is empty, then it is symmetric. If it is not empty, when his left subtree is symmetrical with the right subtree, he is symmetrical


2. So how do you know that the left subtree and the right subtree are not symmetrical? Here I directly call it the left tree and the right tree answer: If the left child of the left tree is symmetrical to the right child of the right tree, and the right child of the left tree is symmetrical to the left child of the right tree, then the left tree and the right tree are symmetrical.
Read this sentence carefully, is it a bit confusing? How do I feel that there is a function A that I want to implement, but when I implement A, I need to use the function after A is implemented?

When you think about this, the recursive point has already appeared: Recursive point: When I tried to judge the condition of symmetry between the left tree and the right tree, I found that it has something to do with the symmetry of the children of the two trees.

Thinking of this, you don’t have to have too many doubts, just write the code according to the idea. The function of function A (left tree, right tree) is to return whether it is symmetrical. def function
A (left tree, right tree): the value of the left tree node is equal to the right tree node value and function A (the left subtree of the left tree, the right subtree of the right tree), function A (the right subtree of the left tree, the left subtree of the right tree) are both true to return true

Achieved. . .

Write and write. . . You will find that you have written it. . . . . .

2. The same tree

Topic: 100. The same tree

Code Analysis: (Java)

class Solution {
    
    
    public boolean cmp(TreeNode lchild,TreeNode rchild){
    
    
        if(lchild==null&&rchild==null)//当相同位置的结点都为空时相等
            return true;
        //相同位置的结点只有一个为空,或者2个结点的值不同时不相等
        if(lchild==null&&rchild!=null||lchild!=null&&rchild==null||lchild.val!=rchild.val)
            return false;
        //每次比较的都是相同位置的结点是否相等
        return cmp(lchild.left,rchild.left)&&cmp(lchild.right,rchild.right);
    }
    public boolean isSameTree(TreeNode p, TreeNode q) {
    
    
        return cmp(p,q);
    }
}

insert image description here

3. The maximum depth of the binary tree

Topic: 104. Maximum depth of a binary tree

在这里插入代码片

tree advantage

look up

Find: Given a certain keyword K, find the records with the same keyword as K in the set R.

Find is divided intostatic lookupanddynamic lookup

  1. Static Lookup: The records in the collection are fixed. There are no insertions and deletions, only lookups.

The general idea of ​​static search is to put the data in an array. The simplest search method is to search sequentially, that is, to search one by one from the beginning to the end.

(C++ sequential search code)

int Finder(int x,int a[],int n){
    
    
    for(int i=0;i<n;++i)
        if(a[i]==x)
            return i;  //如果找到了则返回下标i
    return -1; //没找到就返回下标-1
}

It can be seen that the time complexity of this search method is O(n), and the search efficiency is not high.

Of course, there is a faster way to search, which is binary search.

Conditions for binary search: n elements are ordered and stored consecutively (such as in an array), and binary search can be performed. If it is not ordered, dichotomy cannot be done.

(C++ binary search code)

int Finder(int x,int a[],int n){
    
    
    int left=0,right=n-1;
    while(left<=right){
    
    
        int mid=(left+(right-left)/2);//这里其实等效与(left+right)/2 ,但是这样写如果数字比较大时可能会越界,超出范围
        if(x>a[mid])
            left=mid+1;
        else if(x<a[mid])
            right=mid-1;
        else
            return left;
    }
    return -1;
}

Binary search diagram:
insert image description here
Each search of binary search is half of the search range, so the time complexity is O(log n ).

In this way, we can form a binary search decision tree:

  • The number of searches required for each node on the decision tree is equal to the number of layers the node is in
  • When the search is successful, the number of searches will not exceed the depth of the decision tree
  • The depth of a decision tree with n nodes is [log 2 n ]+1
  • The average number of searches = the sum of (the number of nodes in each row * the number of corresponding rows) divided by the total number of nodes. The average search length is called ASL
    insert image description here
    ASL of 3 means that the average search length of each number is 3.
  1. Dynamic lookups: In addition to lookups, insertions and deletions may also occur.
    Using a search tree can solve the problem of dynamic search very well.


Guess you like

Origin blog.csdn.net/xiatutut/article/details/127449141