C++ Implementation of Binary Tree Explanation and Common Operations

Keywords: C++ less

Binary Tree Padding - Tree

In the previous articles, the linear table, stack, queue, string, and so on, which we have mainly introduced, are one-to-one linear structures. Today we are explaining "tree" is a typical non-linear structure. The feature of the non-linear structure is that the direct precursor of any node, if it exists, must be unique, if it exists, then there can be more than one.It can also be interpreted as a one-to-many relationship, so let's first get to know trees

The concept of trees

The following picture shows the trees we see in our daily life. From the main tree trunk, we can see that many branches are derived upwards, and from each branch, we can see some branches. This makes up the structure of the trees we can see on the ground, but for each branch, it is derived from the main tree trunk layer by layer.

We often need to solve such practical problems in our computers as:

  • Used to store and process tree-like data, such as genealogy, organization charts
  • Lookup and some large-scale data indexing aspects
  • Efficient sorting of data

Without mentioning some complex functions, such as modeling some data with tree hierarchy to solve practical problems, we can use the structure of "tree" to represent it. In order to better suit our habits, we usually look at "tree" upside down, and we can summarize it as the following structure, which is also in our data structure"Tree"

Common terms in trees

  • Node: A branch that contains both data items and points to other nodes, such as Circle A in the figure above, which contains both data items A and B and C.
  • In particular, because A has no precursor and only one, it is called the root node.
  • Subtree: A subtree of a tree is a subtree derived from the root node and all descendants of the root node.

    • For example, the two figures below are subtrees of the tree above

  • Node Degree: The number of subtrees a node has, simply by looking directly at how many branches it has, such as degree A above is 2 and degree B above is 1
  • Leaf node: Also known as terminal node, i.e. node without a successor, e.g. E F G H I
  • Branch node: Also known as non-terminal node, this can be called except leaf node
  • Child Node: Also known as Son Node, which is the immediate successor of a node, such as B and C are children nodes of A
  • Parent Node: Also known as parent node, a direct precursor of a node, e.g. A is the parent node of B and C
  • Brothers node: Children nodes of the same parent are called brothers to each other, e.g. B and C are brothers to each other
  • Cousins: Nodes where parents are brothers, e.g. D and E are cousins to each other
  • Ancestor node: All nodes on the path from the root node to a node, A B D node is the ancestor node of H node
  • Descendant node: Any node in a subtree whose root is a node is called a descendant node of that node, for example, the descendant node of C has an E F I
  • Hierarchy of nodes: Set the root node hierarchy to 1, and the remaining nodes to add 1 to their parent node hierarchy, for example, Level A to 1, Level B to 2
  • The height of a tree: Also known as the depth of a tree, the maximum level of nodes in a tree
  • Ordered/Unordered Tree: Whether node subtrees in a tree are ordered from left to right, ordered is ordered tree, and disordered is disordered tree

As you may also see, in the example I mentioned above, the branches are all within two, which is a kind of tree we focus on today - "binary tree"

Binary Tree

In computer science, a Binary tree is a tree structure where each node has at most two branches (that is, there is no node with a branch greater than 2).Branches are often referred to as "left subtrees" or "right subtrees".The branches of a Binary tree have left-right order and cannot be reversed at will.- Wikipedia

Specifically emphasized by definition:

  • Each node has at most two branches, not two branches, but the maximum, none or only one is possible
  • The left subtree and the right subtree must have a clear order, even if there is only one subtree, whether it is a left subtree or a right subtree

Several Special Binary Trees

(1) Full Binary Tree

Usually, the trees we see are high, low and uneven. If the number of nodes in any level of a binary tree reaches the maximum, such a tree is called a full binary tree, and a binary tree with a height of k has 2k - 1 nodes

(2) Complete Binary Tree

Complete binary trees are highly efficient data structures. Complete binary trees are derived from full binary trees.A binary tree with n nodes at depth K is called a complete binary tree if and only if each node corresponds to one-to-one numbers from 1 to N in a full binary tree at depth K

How to quickly determine if a complete binary tree is true:

  • A binary tree can be a complete binary tree if only the lowest two levels of nodes are less than 2 and the lowest level of nodes are concentrated in the leftmost contiguous position of that level.
  • Looking at the diagram of the tree, I silently numbered the tree hierarchically according to the structure of the full binary tree. If there is a gap in the numbering, it is not a complete binary tree.

(3) Regular binary trees

Regular binary trees are also called strictly binary trees. If any node of a binary tree is either a leaf node or exactly two non-empty subtrees, that is, all branch nodes have a degree of 2 except for leaf nodes with degree 0.

Properties of Binary Trees

Property 1: A non-empty binary tree has at most 2 i-1 (i $\geq$0) nodes on its layer I

Property 5: If a complete binary tree with n nodes is numbered hierarchically from top to bottom (left to right for each layer) from 1 to n, then any node i (i $\leq$I $\leq$n)

  • If i = 1, node I is the root and there are no parents. If I > 1, the number of parents is [i/2]
  • If 2I $\leq$n, the number of the left child of I is 2i, otherwise there is no left child
  • If 2i + 1 $\leq$n, the number of the right child of i is 2i + 1, otherwise there is no right child

Sequential storage structure for binary trees

(1) in a complete binary tree

For a one-to-many relationship like trees, using a sequential storage structure is not reasonable, but it is also possible.

For a complete binary tree, the nodes numbered i on the tree are stored in the components labeled i in a one-dimensional array, as shown in the following figure

(2) in ordinary binary trees

If you have an ordinary binary tree, you need to now add some empty nodes to make it a full binary tree. The new empty nodes are set to ^as shown in the following figure

(3) In more extreme circumstances

For example, in a right-oblique tree with a depth of k, there are only K nodes in this case, but according to the previous nature, we can know that it is necessary to allocate 2k-1 storage units, which is obviously a huge waste of storage space, so it seems that sequential storage is more practical when only complete binary trees exist.

Binary Tree Chain Storage Structure (Key)

Sequential structure is obviously not very suitable for use, so in practice, we will choose a chain storage structure, chain storage structure, in addition to the need to store its own elements, you also need to set pointers to reflect the logical relationship between nodes, in a binary tree, each node has at most two children, so we set up two pointer fields to point to the left and right children of the node.Children, this structure is called a binary list node (focus on this one)

A common operation in a binary tree is to find the parents of the nodes, and each node can also have an additional pointer field pointing to the parents, a structure called a triple-linked list node

Binary list nodes can be used to form a binary list, as shown in the following figure:

Code representation of tree and binary list

(1) Abstract data types of trees

#ifndef _BINARYTREE_H_
#define _BINARYTREE_H_ 
#include<iostream>
using namespace std;

template<class T>
class binaryTree {
public:
    // empty 
    virtual void clear()=0;
    // Null, empty returns true, non-empty returns false                    
    virtual bool empty()const=0;
    //Height of Binary Tree 
    virtual int height() const=0;
    //Total number of nodes in a binary tree 
    virtual int size()const=0;
    //Pre-order traversal 
    virtual void preOrderTraverse() const=0;
    //Intermediate traversal 
    virtual void inOrderTraverse() const=0;
    //Post-order traversal 
    virtual void postOrderTraverse() const=0;
    //level traversal 
    virtual void levelOrderTraverse() const=0;
    virtual ~binaryTree(){};
};

#endif

(2) Representation of a binary chain table

Note: We set the Node type and its pointer as private members because it facilitates data encapsulation and hiding, which is not much explained in this article, but rather focuses on algorithm implementation.

#ifndef _BINARYLINKLIST_H_
#define _BINARYLINKLIST_H_
#include "binaryTree.h"
#include<iostream>
using namespace std;

//elemType is the type of element stored for the sequential table
template <class elemType>                    
class BinaryLinkList: public binaryTree<elemType>
{ 
private:
    //Binary Chain List Node 
    struct Node {
        // Pointer to left and right children 
        Node *left, *right;
        // Node Data Domain 
        elemType data;
        //non-parameter constructor 
        Node() : left(NULL), right(NULL) {}
        Node(elemType value, Node *l = NULL, Node *r = NULL) {
            data = value;
            left = 1;
            right = r;
        }
        ~Node() {}
    };
    
    //Root node pointing to binary tree 
    Node *root;
    //empty
    void clear(Node *t) const;
    //Total number of nodes in a binary tree
    int size(Node *t) const;
    //Height of Binary Tree
    int height(Node *t) const;
    //Number of leaf nodes of a binary tree
    int leafNum(Node *t) const;
    //Recursive Pre-traversal
    void preOrder(Node *t) const;
    //Recursive Ordered Traversal
    void inOrder(Node *t) const;
    //Recursive Successive Traverse
    void postOrder(Node *t) const;
    
public:
    //Construct an empty binary tree 
    BinaryLinkList() : root(NULL) {}
    ~BinaryLinkList() {clear();}
    //Send blank 
    bool empty() const{ return root == NULL; }
    //empty 
    void clear() {
        if (root)
            clear(root);
        root = NULL;
    }
    //Find the total number of nodes
    int size() const { return size(root); }
    //Finding the Height of a Binary Tree
    int height() const { return heigth(root); }
    //Number of Binary Leaf Nodes
    int leafNum() const { return leafNum(root); }
    //Pre-order traversal
    void preOrderTraverse() const { if(root) preOrder(root); }
    //Intermediate traversal
    void inOrderTraverse() const { if(root) inOrder(root); }
    //Post-order traversal
    void postOrderTraverse() const {if(root) postOrder(root); }
    //level traversal
    void levelOrderTeaverse() const;
    //Non-recursive Pre-traversal
    void preOrderWithStack() const;
    //Non-recursive middle traversal
    void inOrderWithStack() const;
    //Non-recursive postorder traversal
    void postOrderWithStack() const;
};

#endif

Traversal of Binary Trees

(1) Depth first traversal

Concepts: Traversing the nodes of a binary tree along its depth, accessing the branches of a binary tree as deeply as possible, can be divided into three main types: first-order traversal, middle-order traversal, second-order traversal, and

  • Preorder traversal

    • Access the root node first, then traverse the left subtree in precedence, and then the left subtree in precedence (root-left-right)
  • Intermediate traversal

    • The left subtree is traversed in middle order, then the root node is accessed, and the right subtree (left-root-right) is traversed in middle order.
  • Post-order traversal

    • Traverse left subtree in order, right subtree in order, and then root node (left-right-root)

An example is clear:

As an example, the three traversal modes are executed in the following order:

  • Preorder traversal: A - B - C - E - F
  • Intermediate traversal: B - A - E - C - F
  • Postorder traversal: B - E - F - C - A

Let's take the middle order as an example: what does it mean to traverse the left subtree first, then the root node, and then the right subtree (left-root-right) in the middle order?

Medium-order traversal refers to treating each point as the first node, and then performing the middle-order traversal each time, that is, left-root-right. When the left side is empty, it returns to the parent node that accesses the current node, that is, in the middle, after recording, it accesses the right again.

For example, starting from root node A, visit left child B first, there is no left child B, return to A, visit right side C of A, and then traverse through it in middle order, that is, visit E first, then return to C and then visit F: B - A - E - C - F

First, let's use recursion to implement these three traversals. Recursion gives me the feeling that it's extremely easy to understand and that the code is so concise that it's hardly too fast to implement this algorithm quickly

(1) Pre-traversal-recursion

template <class elemType>
void BinaryLinkList<elemType>:: preOrder(Node *t) const {
    if (t) {
        cout << t -> data << ' ';
        preOrder(t -> left);
        preOrder(t -> right);
    }
}

(2) Intermediate traversal-recursion

template <class elemType>
void BinaryLinkList<elemType>:: inOrder(Node *t) const {
    if (t) {
        preOrder(t -> left);
        cout << t -> data << ' ';
        preOrder(t -> right);
    }
}

(3) Post-order traversal-recursion

template <class elemType>
void BinaryLinkList<elemType>:: postOrder(Node *t) const {
    if (t) {
        preOrder(t -> left);
        preOrder(t -> right);
        cout << t -> data << ' ';        
    }
}

Tip: You may notice that in the previous definition we defined three methods that are public.

//Pre-order traversal
void preOrderTraverse() const { if(root) preOrder(root); }
//Intermediate traversal
void inOrderTraverse() const { if(root) inOrder(root); }
//Post-order traversal
void postOrderTraverse() const {if(root) postOrder(root); }

This is because all three of the previous recursive methods require a pointer of type Node as an argument, and the root node root pointing to the binary tree is private, which makes it impossible for us to call it with objects of the BinaryLinklist class, so we need to write a common interface function, which is our three above

Although the recursive method is easy to understand, it consumes more space and time, so we can design other algorithms to implement the three traversals above, that is, the idea of using stacks

(1) Pre-traversal-stack

template <class elemType>
void BinaryLinkList<elemType>::preOrderWithStack() const {
    //Stack in STL 
    stack<Node* > s;
    //Work Pointer, Initialize to Root Node 
    Node *p = root;
    //Stack is not empty or p is not empty 
    while (!s.empty() || p) {
        if (p) {
            //Access the current node 
            cout << p -> data << ' ';
            //Pointer Push on Stack 
            s.push();
            //Working Pointer Points to Left Subtree 
            p = p -> left;
        } else {
            //Get top stack element 
            p = s.top();
            //Unstack 
            s.pop();
            //Working Pointer Points to Right Subtree 
            p = p -> right; 
        }
    } 
}

(2) Intermediate traversal-stack

template <class elemType>
void BinaryLinkList<elemType>::inOrderWithStack() const {
    //Stack in STL 
    stack<Node* > s;
    //Work Pointer, Initialize to Root Node 
    Node *p = root;
    //Stack is not empty or p is not empty 
    while (!s.empty() || p) {
        if (p) {
            //Pointer Push on Stack 
            s.push();
            //Working Pointer Points to Left Subtree 
            p = p -> left;
        } else {
            //Get top stack element 
            p = s.top();
            //Unstack 
            s.pop();
            //Access the current node 
            cout << p -> data << ' ';
            //Working Pointer Points to Right Subtree 
            p = p -> right; 
        }
    } 
}

(3) Post-order traversal-stack

Post-order traversal is slightly special, with Left and Right flags set to distinguish whether a pop-up node at the top of the stack returns from the left or right subtree of the top node of the stack

template <class elemType>
void BinaryLinkList<elemType>::postOrderWithStack() const {
    //Define Tags 
    enum ChildType {Left,Right};
    //Element type in stack
    struct StackElem {
        Node *pointer;
        ChildType flag;
    }; 
     StackElem elem;
    //Stack in STL 
    stack<StackElem> S;
    //Work Pointer, Initialize to Root Node 
    Node *p = root;
    while (!S.empty() || p) {
        while (p != NULL) {
            elem.pointer = p;
            elem.flag = Left;
            S.push(elem);
            p = p -> left;
        }
        elem = S.top();
        S.pop();
        p = elem.pointer;    
        //The left subtree has been traversed 
        if (elem.flag == Left) {
            elem.flag = Right;
            S.push(elem);
            p = p -> right;
            //The right subtree has been traversed 
        } else { 
            cout << p -> data << ' ';
            p = NULL;
        }
    }
}

There is really no recursive way to understand these three traversals by stack. When learning this part, you can compare a simple diagram to think about it, which can help you better understand the code. You can refer to my example diagram above.

(2) Width first traversal

Width-first traversal, also known as width-first traversal, or hierarchical traversal, has the idea that nodes are accessed one by one from the root node, top to bottom, and in the same layer, in left to right order

We can use the idea of queues to do this kind of traversal

  • Initialize queue, root node enqueue
  • Queue is not empty, loop performs the following three steps, otherwise end
  • Queue a node while accessing it
  • If the node's left subtree is not empty, queue its left subtree
  • If the node's right subtree is not empty, queue its right subtree
template <class elemType>
void BinaryLinkList<elemType>::levelOrderTeaverse() const {
    queue<Node* > que;
    Node *p = root;
    if (p) que.push(p);
    while (!que.empty()) {
        //Take the first element of the queue 
        p = que.front();
        //Queue 
        que.pop();
        //Access the current node 
        cout << p -> data << ' ';
        //Left Subtree Entry
        if (p -> left != NULL)
            que.push(p -> left);
        //Right Subtree Entry 
        if (p -> right != NULL)
            que.push(p -> rigth);
    }
}

Other Common Operations of Binary Trees

(1) Find the total number of nodes

template <class elemType>
int BinaryLinkList<elemType>::size(Node *t) const {
    if (t == NULL)
        return 0;
    return 1 + size(t -> left) + size(t -> right);
} 

(2) Calculating the height of a binary tree

template <class elemType>
int BinaryLinkList<elemType>::height(Node *t) const {
    if (t == NULL) 
        return 0;
    else {
        int lh = height(t -> left);
        int rh = height(t -> right);
        return 1 + ((lh > rh) ? lh : rh);
    }
}

(3) Number of leaf nodes

template <class elemType>
int BinaryLinkList<elemType>::leafNum(Node *t) const {
    if (t == NULL)
        return 0;
    else if ((t -> left == NULL) && (t -> right == NULL))
        return 1;
    else
        return leafNum(t -> left) + leafNum(t -> right);
}

(4) Empty

template <class elemType>
void BinaryLinkList<elemType>::clear(Node *t) {
    if (t -> left)
        clear(t -> left);
    if (t -> right)
        clear(t -> right); 
    delete t;
}

Ending:

If there are any deficiencies or errors in the article, please leave a message to share your thoughts and thank your friends for their support!

If you can help, pay attention to me!If you prefer the way WeChat articles are read, you can focus on my public number

We don't know each other here, but we are working hard for our dreams.

A public number insisting on original development technology articles: Ideal more than 20 days

Posted by beyers on Sat, 09 Nov 2019 20:13:18 -0800