Binary Tree Learning notes-Implementation

Source: Internet
Author: User

Binary Tree Learning notes-Implementation

In the previous article, I had a preliminary understanding of what kind of data structure a binary tree is. Next we will use our own code to implement a binary search tree (called binary tree below) class and provide common external interfaces, such as insert, erase, size, and find. Just like building a building, if a binary tree is a building, the node is a block of bricks. To implement the binary tree class, we must first implement the node class. Suppose we name it treeNode. In the STL standard library, data structures like General Are Template classes. For convenience, we assume that the data stored in the binary tree class is of the int type.

This node class must contain the following members: node value, pointer to left of the left child node, pointer to right of the right child node, and pointer to parent of the parent node. The Code is as follows:

class treeNode{public:int value;treeNode *left;treeNode *right;treeNode *parent;treeNode(){value = 0;left = NULL;right = NULL;parent = NULL;}};
For convenience, we initialize the value to 0 and all pointers to NULL.

 

With this class, it is equivalent to having basic raw materials for House Building. Now we also need to implement binary tree classes to add nodes according to certain rules to form a binary tree. At the same time, it is necessary to provide external users with friendly interfaces without having to worry about internal details. For example, if I want to insert a node with a value of 12 into a binary tree, I just need to call insert (12), instead of a node instance, assign a value of 12 and then adjust the pointer, insert a binary tree and ensure the structure of the binary tree. These implementation details must be encapsulated by the insert function.

The binary tree class must contain the following members: insert (), erase (), find (), size () member function, treeSize, and root node pointer. Of course, there is also a constructor. The Code is as follows:

class searchTree{public:searchTree(const int &value);intinsert(const int &value);interase(const int &value);treeNode* find(const int &value);int size() const;private:int treeSize;treeNode* root;};
In the constructor, We initialize a binary tree instance to contain a tree with a root node value of value.

 

The insert function first generates a node instance with the value of the node and then inserts the node into the tree. If the data is inserted successfully, 0 is returned. If the tree contains the same node, that is, a node with the value already exists, the data insertion fails.

The erase function deletes a node with the value in the tree. If the deletion is successful, 0 is returned. If the deletion fails, non-0 is returned. If the tree does not contain a node with the value, the deletion fails.

The find function searches for the node with the value in the tree and returns the pointer to the node. If the search is successful, the pointer of the node is returned. If the search fails, NULL is returned. If the fruit tree does not contain a node with the value, the search fails.

The size function returns the number of nodes in the current tree.

The private member variable treeSize stores the number of nodes in the current tree, And the pointer root stores the address of the node.

Next, implement the constructor. In the constructor, you only need to generate an instance of the root node, set the value of the root node to value, and set the treeSize value to 1, indicating that there is a node in the current tree. The Code is as follows:

searchTree::searchTree(const int &value){root = new treeNode();root->value = value;treeSize = 1;}
Next we will implement the size function. Because the size function is the simplest, We will directly return the number of nodes in the current tree. The Code is as follows:

 

 

int searchTree::size(){return treeSize;}

Next, implement the find function. The find function searches for the node with the value in the tree and returns the pointer to the node. The implementation idea is to compare the relationship between the value of the root node and the value from the root node. If the value of the root node is greater than the value, it will drop along the left subnode; otherwise, it will drop along the right subnode. There are two possibilities for following this descent rule. The first is to find the node with the same value as the value and return the node pointer. The second type is to drop to an empty node, that is, NULL. If NULL is returned, the query fails. The Code is as follows:

TreeNode * searchTree: find (const int & value) {treeNode * curr; curr = root; // search for while (curr! = NULL) // One of the termination conditions is to drop to an empty node {if (curr-> value) // The value is smaller than the value of the current node, drop down the left subnode {curr = curr-> left;} else if (curr-> value)
 
  
Right;} else // The value is equal to the value of the current node. After the query is complete, {return curr ;}return NULL; // The node has been dropped to an empty node or not found, it indicates that the node is not in the tree. If the node fails to be searched, NULL is returned}
 

 

Next, implement the insert function. The insert function inserts a node with the node value in the current tree. The implementation idea of the function is similar to that of the find function. It also compares the relationship between the value of the root node and the value from the root node. If the value of the root node is greater than the value, it drops along the left subnode, otherwise, it drops along the right subnode. In this case, there are two possibilities. The first is to find a node with the same value as the value. At this time, the node with the value already exists in the tree, and 1 is returned, which indicates that the insertion fails. The second is to drop to an empty node, that is, NULL. At this time, the correct insertion position is found. A new node is generated, assigned a value, and inserted to this position. Note that the parent node of the current node needs to be recorded during the descent process, so that when the insertion is successful, the newly generated node will know who its parent node is. It also records whether the last drop is down along the left subnode or the right subnode, so that the final inserted node will know whether it is the left subnode of its parent node or the right subnode. The Code is as follows:

Int searchTree: insert (const int & value) {int direct =-1; // when dropping from top to bottom, it is required to record whether to drop treeNode * curr, * par along the left subnode or right subnode at the last descent; // the current node and its parent node curr = root; // search from top to bottom from the root node/* Find the insert position from top to bottom. You need to save the parent node of the current node, although each node saves its parent node, however, the insert position is an empty node, that is, NULL. The empty node does not have its own parent node */par = root; while (curr! = NULL) // One of the termination conditions is to find an empty node as the Insert Location {par = curr; // Save the parent node if (curr-> value) // if the value is smaller than the value of the current node, it drops down along the left subnode {curr = curr-> left; direct = 0;} else if (curr-> value)
 
  
Right; direct = 1;} else {return 1; // if the value is equal to the value of the current node, insertion fails, indicating that the node already exists in the tree, return 1} curr = new treeNode (); // a node is dynamically generated and assigned valuecurr-> value = value; if (direct! =-1) // if the value is-1, it indicates that the root node is NULL and an error occurs {if (direct = 0) // indicates that the next descent is down along the left child node, and the newly inserted node is the left child node of the parent node {par-> left = curr ;} else // indicates that the last descent is down along the right sub-node, so the newly inserted node is the right sub-node of the parent node {par-> right = curr ;} curr-> parent = par; // update the parent node treeSize ++ of the inserted node; // The number of nodes plus return 0;} return 1; // The root node is NULL, an error occurred. 1 is returned}
 

 

Next, implement the erase function. This function is placed at the end because it is the most difficult function. Our first reaction was to delete the node. Isn't it easy to find the node and release the memory. Deleting a node is simple. The difficulty lies in that after the node is deleted, the structure of the binary tree must be maintained. That is to say, after a node is deleted, the remaining nodes need to form a complete tree. In addition, each node in the tree must have a value smaller than the parent node, the value of the right child node is greater than that of the parent node.

Let's assume there is a tree like this:

Suppose we delete the node 8. If we just release the memory of the eight nodes according to the above idea, then the root node 12 will have no left subnode, the left sub-node points to an unallocated memory segment, so that an error occurs during access. If the left subnode cannot be dropped, the remaining six, 11, and 10 nodes cannot be accessed. Another problem is that nodes 6 and 11 do not have their own parent nodes. Therefore, when traversing the tree, they cannot return along the parent node. Obviously, this method of deletion is unscientific. However, if the 10 nodes are deleted and the memory of the 10 nodes is released based on the original idea, it seems reasonable to point the pointer to NULL on the left subnode of the 11 nodes.

The difference between node 8 and node 10 is that the Left and Right subnodes of node 8 are not empty, while the left and right subnodes of node 10 are empty. In this way, we can divide the Left and Right nodes into four types based on the principle of whether the Left and Right nodes are empty:

1. Left and Right subnodes are empty.

2. The left subnode is empty, and the right subnode is not empty.

3. The right subnode is empty, and the left subnode is not empty.

4. Left and Right subnodes are not empty.

In the above four cases, we need to find different deletion rules for the same purpose. That is to find an alternative node to replace the currently deleted node. The role of this alternative node is to replace the deleted node, the remaining nodes are still a Complete Binary Tree.

In the first case, the alternative node is NULL.

In the second case, 13 nodes meet this situation. The alternative node found here is its right child node and 16 nodes.

In the third case, if the 11 node meets this situation, the alternative node is its left subnode and 10 node.

In the fourth case, if the eight nodes meet this situation, the method for finding an alternative node is to first drop to the right subnode of the node, that is, 11 nodes ., If the node does not have a left subnode, the node replaces the node, that is, the 11 node in the figure. If there is a left subnode, it will continue to fall along the left subnode of the node to the end. The replacement node is 10 nodes in the figure.

The replacement node is not unique. As long as the replacement node is found, it can ensure that the remaining node is still a Complete Binary Tree. In the search process, a fixed rule should be kept unchanged and should be as simple and fast as possible. In the above search process, we first classify the nodes and then set four seed rules for four different situations. The Code is as follows:

Int searchTree: erase (const int & value) {treeNode * curr, * par, * replace, * replacePar; // The parent node of the current node, respectively, replace the node. Replace the parent node int direct =-1; curr = root; // start from the root node to find the point to be deleted. The search process is consistent with the find function. par = root; while (curr! = NULL) {if (curr-> value) {par = curr; curr = curr-> left; direct = 0 ;} else if (curr-> value)
 
  
Right; direct = 1;} else // found the point to be deleted {if (curr-> left = NULL & curr-> right = NULL) // corresponding to Case 1, the left and right subnodes are both NULL, that is, the node is a leaf node {if (direct = 0) // according to the last descent direction, set the left or right child nodes of the parent node to NULL {par-> left = NULL;} else if (direct = 1) {par-> right = NULL ;} delete curr; // release the memory. Reduce the number of nodes by one treeSize --; return 0;} else if (curr-> left = NULL & curr-> right! = NULL) // corresponds to scenario 2. The left subnode is empty, and the right subnode is not empty. {replace = curr-> right; // The replacement node is the right child node of the current node/* sets the Left or Right child nodes of the parent node as the replacement node based on the last descent direction, replace the parent node of a node with the parent node of the current node */if (direct = 0) {par-> left = replace; replace-> parent = par ;} else if (direct = 1) {par-> right = replace; replace-> parent = par;} else // direct =-1 indicates the deleted root node, set the root node to an alternative node. Set the parent node of the alternative node to NULL {root = replace; replace-> parent = NULL;} delete curr; // release the memory of the current node, reduce the number of nodes by one treeSize --; return 0;} else if (curr-> Left! = NULL & curr-> right = NULL) // corresponding to case 3, similar to case 2 {replace = curr-> left; if (direct = 0) {par-> left = replace; replace-> parent = par;} else if (direct = 1) {par-> right = replace; replace-> parent = par ;} else {root = replace; replace-> parent = NULL;} delete curr; treeSize --; return 0 ;} else // left and right nodes are not empty. {replace = curr-> right; // tentatively set the replacement node to the right subnode of the current node if (replace-> left = NULL) // If the left subnode of the right subnode is NULL, the right subnode replaces the node {replace-> left = curr-> Left; // update the left subnode of the substitution node to the left subnode of the current node if (curr-> left! = NULL) {/* If the left child node of the current node exists, update the parent node of the left child node. If the left subnode is NULL, NULL indicates that no parent node exists */curr-> left-> parent = replace ;}} else // The left subnode of the right subnode is not NULL, in this case, the child node on the left will be dropped down to the end {replacePar = replace; // The child node will be saved as the parent node of the replacement node while (replace-> left! = NULL) // always drop to the end {replacePar = replace; replace = replace-> left;}/* When drop to the end, there are two alternative nodes. The first method is to replace the right child node of a node with a non-NULL value. Therefore, you must replace the right child node with the left child node of the parent node, that is, the right child tree of the node, relink to the tree */replacePar-> left = replace-> right; // in either case, replace-> right! = NULL) // if the right child node is not NULL, update the parent node {replace-> right-> parent = replacePar;} of the right child node ;} replace-> left = curr-> left; // update the left sub-node pointer of the substitution node, curr-> left-> parent = replace; replace-> right = curr-> right; // update the right sub-node pointer curr-> right-> parent = replace;} if (direct = 0) // based on the last descent direction, update the pointer of the parent node {par-> left = replace; replace-> parent = par;} else if (direct = 1) {par-> right = replace; replace-> parent = par;} else {root = replace; replace-> parent = NULL;} delete curr; // release the memory of the current node, the number of nodes in the tree minus one treeSize --; return 0 ;}} return 1 ;}
 
It is difficult to delete a node in the last case. Follow the instructions to delete a node that is not empty.

 


 

Delete node 36 and node 11.

Delete node 35: first find the right child node of node 35, node 64, and find that the left child node of 64 is NULL, then 64 is the replacement node. To delete 35 nodes, you need to update the Three Link pointers related to 35 nodes, that is, the link pointer to the left child node, the link pointer to the right child node, and the relationship pointer to the parent node. First, set the pointer of the left child node of node 64 to the left child node of 35, that is, 31, and set the parent node pointer of node 31 to node 64. The link pointer of the Left subnode is updated. Similarly, update the relational pointer of the parent node. Because the replacement node deletes the right child node of the node, the link pointer of the right child node does not need to be updated.

Delete node 11. First, the replacement node is tentatively set to delete the right child node of the node. It is found that the left child node of the right child node is not NULL and drops down along the left child node until the end, that is, node 12, which is an alternative node. In this case, the link pointer of the left, right, and parent nodes should be updated at the same time (simply put, the node 12 should be filled to the original position of 11, and the relationship between the node 12 and the surrounding areas should be updated ). After the update, it is found that all nodes under Node 15 are out of the original tree. Therefore, the relationship between node 15 and Node 19 needs to be updated.

This means that node 11 is deleted in two steps. The first step is to find the replacement node and delete it from the tree, because the replacement node is dropped to the end along the left subnode, the left subnode must be NULL, so the operation to delete the replacement node is equal to Case 1 or case 2. Step 2 fill in the replacement node to the location of the deleted node, update the link pointer with the surrounding area, and release the memory of the deleted node.

To facilitate the test, we also need to get the root node of a tree. If we traverse all the nodes in the output tree in the middle order, the output will be sorted in ascending order. There are two ways to get this root node. The first is to set the root node in the class to public, or we need to add another method rootNode in the class to return the current root node. Considering encapsulation, the latter is better. Considering the security, we should set the returned pointer to a constant for convenience.

treeNode* searchTree::rootNode(){return root;}
To test the function, we also need a function that outputs nodes in the tree in the middle order. To make the code concise, we use recursion. The Code is as follows:

 

 

void output(treeNode *node){if (node!=NULL){output(node->left);cout << node->value << "-";output(node->right);}}
By now, all preparations have been completed. Write a simple test function and test our self-implemented binary tree. The Code is as follows:

 

int main(){treeNode *root,*curr;searchTree tree(100);for (size_t i = 0; i < 20; i++){tree.insert(rand() % 200);}cout << tree.size() << endl;curr = tree.find(41);if (curr!=NULL){cout << "find the node" << endl;}else{cout << "the node is not in the tree" << endl;}tree.erase(41);root = tree.rootNode();output(root);}

First declare a tree instance. The root node is 100, and then 20 numbers ranging from 0 to are randomly generated and inserted into the tree. Find the node with the value of 41 and delete it. Finally, print all the nodes in the tree. If the nodes are printed in ascending order, it indicates that this is a Complete Binary Tree. The output result is as follows:



The reason why the initial size is 19 is that duplicate points may be generated randomly, so that insertion will fail. We can see that nodes are printed in ascending order. Finally, upload a memory distribution chart of the tree in the vs environment:

We can clearly see that the root node is 100, and the left and right subnodes are 64 and 134 respectively. The left and right subnodes of node 64 are 27 and 67 respectively, and their distribution in memory can also be clearly seen.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.