Binary Tree Learning notes-implementation

Source: Internet
Author: User

In the previous article, it was a preliminary understanding of how the data structure of the two-fork tree is a preliminary impression. Next, use your own code to implement a binary search tree (the following is called the binary tree) class, to provide common interface, such as INSERT, erase, size, find and so on. Like a house, if the binary tree is a building, then the node is a block of bricks. To implement the two-fork tree class, you must first implement the node class, assuming we are named TreeNode. In the STL standard library, like the general data structure is the template class, here for the sake of convenience, it is assumed that the data stored in this class of binary tree is all int type.

In this node class, you need to include some of the following members: The value of the node, the pointer to the left Dial hand node left, a pointer to the right child node, and a pointer to the parent node. Implemented in code as follows:

Class Treenode{public:int Value;treenode *left;treenode *right;treenode *parent;treenode () {value = 0;left = NULL;right = Null;parent = NULL;}};
For the sake of convenience, we initialize the value to 0, and the pointer is all initialized to null.

With this class, there is a basic ingredient for building a house. Now also need to implement two-fork tree class, the purpose is to follow certain rules, a node is added to form a binary tree. At the same time, it is necessary to provide a friendly interface to external users without having to worry about internal details. For example, I now want to insert a node with a value of 12 into a binary tree, only need to call insert (12), instead of the instance of a node, the assignment is 12, then adjust the pointer, insert the binary tree, but also to ensure the structure of the binary tree characteristics. These implementation details need to be encapsulated with the Insert function.

In this class of binary tree, you need to include the following members, insert (), Erase (), find (), size () member function, treeSize, root node pointer root. There is, of course, a constructor function. Implemented in code 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 an instance of a two-fork tree to a tree that contains a root node with value of the root node.

The Insert function first generates a node instance with the value of the node as value and then inserts the node into the tree. Insert successfully returns 0, insert failure returns non 0, insert failed when the tree contains the same node, that is, a node with value values already exists.

The Erase function deletes a node with a value in the tree, delete successfully returns 0, delete failure returns non 0, and delete fails when the tree does not contain a node that has value values.

The Find function looks for a node in the tree that has value values and returns a pointer to that node. If the lookup succeeds in returning a pointer to the node, the lookup fails to return NULL, and if the tree does not contain a node with value values, the lookup fails.

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

The private member variable treesize saves the number of nodes in the current tree, and the pointer root holds the address of the node.

Following the implementation of the constructor, only one instance of the root node needs to be generated in the constructor, then the value of the root node is set to value and the TreeSize value is 1, indicating that there is one node in the current tree. The code is as follows:

Searchtree::searchtree (const int &value) {root = new TreeNode (); root->value = Value;treesize = 1;}
Next, implement the size function, because the size function is the simplest, directly returning the number of nodes in the current tree. The code is as follows:

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

Following the implementation of the Find function, the Find function looks in the tree for a node with value values and returns a pointer to that node. The idea of implementation is to compare the root node value and the size relationship of value from the root node, and if the value of the root node is greater than value, it will descend along the left child node, otherwise it will fall along the right child node. Following this descent rule, there are two possibilities, the first of which is to find the node with the node value equal to value and return the node pointer. The second is to drop to an empty node, which is null, at which time Null is returned, representing the lookup failure. The code is as follows:

treenode* searchtree::find (const int &value) {TreeNode *curr;curr = root;//looks up from the root node from top to bottom while (curr!=null)// One of the termination conditions is to drop the null node {if ((Curr->value) > Value)//value less than the value of the current node, descending along the left child node {curr = Curr->left;} else if ((curr->value) <value)//value is greater than the value of the current node, down the right child node {curr = Curr->right;} The else//value equals the value of the current node, finds the end, returns {return curr;}} Return null;//has dropped to an empty node or the node is not found, representing the node is not in the tree, lookup failed, return NULL}

Following the implementation of the Insert function, the Insert function inserts a node with the value of node in the current tree. The implementation of the function is similar to the Find function, but also from the root node to compare the value of the root node and the size of value relationship, if the root node value is greater than value, along the left child node down, or along the right child node down. This decline will eventually occur in two possible ways, the first is to find a node value and the value of the same size, which represents the tree already has a value of the node, return 1, representing the insertion failed. The second is to drop to an empty node, which is null, at which point the correct insertion position is found and the new node is assigned value, which is inserted into that position. The problem to be aware of is that the parent node of the current node needs to be recorded during the descent process, so that when the final insert succeeds, the newly generated node knows who it is on the parent node. Also record whether the last drop is down along the left or right child nodes, so that eventually the inserted node knows whether it is the left child or right child node of its parent node. The code is as follows:

int Searchtree::insert (const int &value) {int direct = -1;//down from top to bottom, you need to record the last descent along the left child node or along the right child node down TreeNode *curr,*par;// The current node and the parent node of the current node Curr = root;//from top to bottom from the root node the process of finding the insertion position from top to bottom requires saving the parent node of the current node, although each node holds its own parent node, but the inserted location is an empty node, that is, null. An empty node is a parent node without its own */par = Root;while (curr!=null)//Termination condition One is to find an empty node as the insertion position {par = curr;//save Parent if ((Curr->value) > value) If the value is less than the value of the current node, decrease along the left child node {curr = Curr->left;direct = 0;} else if ((curr->value) <value)//If the value is greater than the value of the current node, decrease along the right child node {curr = Curr->right;direct = 1;} Else{return 1;//If the value equals the value of the current node, the insertion fails, representing the node already in the tree, returning 1}}curr = new TreeNode ();//dynamically generating a node assigned to Valuecurr->value = Value;if ( DIRECT!=-1)//If the value is-1, which represents the root node is null, an error {if (direct = = 0)//indicates that the subsequent descent is down along the left child node, then the newly inserted node is the parent node's left child node {par->left = Curr;} else//indicates that the last drop is down along the right child node, then the newly inserted node is the right child node of the parent node {par->right = Curr;} curr->parent = par;//Update the parent node of the insertion node the number of treesize++;//nodes plus a return of 0;} Return 1;//root node is null, error, 1}

The next step is to implement the erase function, which is the last one because this is the hardest function. Our first reaction is to delete is not very simple, find this node, release the memory is not good enough. If you simply delete a node, it's simple. The hard part is to keep the structure of the binary tree after the node is deleted. That is, after the node is deleted, the remaining nodes are also composed of a complete tree, which has a layer of intrinsic meaning is that each node in the tree also satisfies the left child node value is less than the parent node, the right child node value is greater than the parent node such a relationship.

Let's assume there's a tree like this:


Suppose we delete 8 this node, if according to the above thought, just pure release 8 node of memory, then the root node 12 has no left dial hand node, the left child node points to a section of unallocated memory, so that when the access error. If you cannot decline along the left child node, then the remaining 6, 11, and 10 of these three nodes cannot be accessed. Another problem is that nodes 6 and 11 do not have their own parent node, so when traversing a tree, it cannot be returned along the parent node. Obviously, this method of deletion is unscientific. However, if you delete the 10 node, if you also follow the original idea, release 10 node of memory, the 11 node left child node pointer to NULL, it seems reasonable.

The difference between node 8 and node 10 is that 8 of the left and right child nodes are not empty, and the left and right child nodes of the 10 node are empty. In this way, we will divide the nodes into four cases according to the principle of whether the left and right child nodes are empty:

1, the left and right child nodes are empty.

2, the left Dial hand node is empty, the right child node is not empty.

3, the right child node is empty, the left child node is not empty.

4, the left and right child nodes are not empty.

For the above four cases, we need to find out the different deletion rules, the purpose is the same. That is to find an alternative node, instead of the currently deleted node, the role of the replacement node is to replace the deleted node, still can ensure that the remaining node is still a complete two-fork tree.

In the first case, the substitution node is null.

In the second case, the 13 node satisfies this situation, and the replacement node found is its right child node and 16 node.

In the third case, the 11 node in this case satisfies this situation, and the replacement node found is its left child node and 10 node.

In the fourth case, the 8 node in this case satisfies this situation, and the method of finding an alternative node is to descend to the right child node of the node, that is, the 11 node. , if the node has no left child node, then that node is the replacement node, which is the 11 node in the diagram. If there is a left dial hand node, then the left child node of the node is dropped down to the end. The replacement node found at this point is the 10 node in the diagram.

Alternative nodes are not unique, as long as the replacement node is found to ensure that the remaining nodes are still a complete two-fork tree. In the process of searching, keep a fixed rule unchanged, and try to be as simple and fast as possible. In the process of searching above, our rule is to classify the nodes first, then set four seed rules for four different cases. The code is as follows:

int searchtree::erase (const int &value) {TreeNode *curr, *par, *replace, *replacepar;//is the current node, the parent node of the current node, the replacement node, The parent node of the alternate node int direct = -1;curr = root;//finds the point to be deleted from the root node, the lookup process and the Find function are consistent par = Root;while (curr!=null) {if (Curr->value) > Value) {par = Curr;curr = Curr->left;direct = 0;} else if ((curr->value) <value) {par = Curr;curr = Curr->right;direct = 1;} else//found the point to be deleted {if (curr->left==null&curr->right==null)//corresponds to Case 1, the left and right child nodes are NULL, that is, the node is the leaf node {if (direct = = 0)// The left child node or right child node of the parent node is set to Null{par->left = NULL based on the last Descent direction;} else if (direct==1) {par->right = NULL;} Delete curr;//frees memory, the number of nodes is reduced by one Treesize--;return 0;} else if (curr->left==null&curr->right!=null)//corresponds to case 2, left child node is empty, right child node is not empty {replace = curr->right;// The replacement node is the right child of the current node/* Based on the last descent direction, the left child node or right child node of the parent node is placed as an alternative node, and the node parent node is 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 represents the root node of the deletion, sets the root node as an alternative node, and the parent node of the replacement node is set to Null{root = Replace;replAce->parent = NULL;} Delete curr;//frees the memory of the current node, reducing the number of nodes by one Treesize--;return 0;} else if (curr->left! = Null&curr->right = = NULL)//corresponds to condition 3, and case 2 resembles {replace = curr->left;if (direct = = 0) {PAR-&G T;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//the left and right nodes are not empty {replace = curr->right;//first the alternate node is tentatively the current node, if (replace->left==null)//If the left child node of the right child node is NULL, Then the right child node is the replacement node {replace->left = curr->left;//update the left child node of the replacement node is the left child node of the current node if (curr->left!=null) {/* If the left child node of the current node exists, Updates the parent node of the left child node. At this point, if the left Dial hand node is null,null, there is no parent node */curr->left->parent = Replace;}} else//the right child node left child node is not NULL, at this point the left dial hand node must be dropped down to the end {Replacepar = replace;//down the process of saving the parent node of the alternate node while (replace->left! = NULL)// Down to the end {Replacepar = Replace;replace = Replace->left;} /* When dropped to the end, there are two scenarios for the replacement node. The first is that the right child node of the substitution node is not NULL, then it is necessary to use its right child node as the left child node of the parent node of the alternative node, that is, the right subtree of the alternative node, relink to the tree */replacepar->left = replace->right;// In either case, replace the right child node of the node as theThe left child node of the parent node of the generation node if (replace->right!=null)//If the right child node is not NULL, update the parent node of the right child node {replace->right->parent = Replacepar;} Replace->left = curr->left;//Update the left child node pointer of the alternate node curr->left->parent = Replace;replace->right = curr-> right;//Update the right child node pointer of the replacement node curr->right->parent = Replace;} if (direct = = 0)//based on the last descent direction, then update the parent node's pointer {par->left = Replace;replace->parent = par;} else if (direct = = 1) {par->right = Replace;replace->parent = par;} Else{root = Replace;replace->parent = NULL;} Delete curr;//frees the memory of the current node, and the number of nodes in the tree is reduced by one Treesize--;return 0;}}} return 1;}
Deleting a node is difficult in the last case, according to the description of how to delete a left and right child nodes are not empty



Now assume that node 36 and node 11 are deleted.

Delete Node 35: First find the right child node 35, node 64, found that 64 of the left child node is null, then 64 is an alternative node. Removing the 35 node at this point requires updating the three relationship pointers on the 35 node, the relationship pointer to the left child node, the relationship pointer to the right child node, and the relationship pointer to the parent node. First, the left child node of node 64 is set to the left child node of 35, which is the 31 node, and the parent node pointer of the 31 node is set to Node 64. Left Dial hand node relationship pointer update is complete. Similarly, update the parent node's relationship pointer. Because the replacement node is the right child node of the deleted node, the relationship pointer to the right child node does not need to be updated.

Delete Node 11, first of all, the substitution node is tentatively the right child node of the deletion node, found that the right child node left child node is not NULL, along the left dial hand node down to the end, that is, node 12, as an alternative node. At this time to update the left child node, the right child node, the parent node of the relationship pointer (the simple understanding is that the node 12 is populated to the original 11 position, update node 12 and the surrounding relationship). After the update, it is found that all nodes below node 15 are out of the original tree, so you also need to update the relationship between node 15 and node 19.

This is tantamount to saying that delete node 11 is done in two steps, the first step is to find the replacement node, and remove from the tree, because the replacement node is the left child node down to the end, then its left child node must be null, then the operation to delete the replacement node is equal to the situation 1 or 2. The second step then fills the replacement node with the location of the deleted node, updates the relation pointer to the perimeter, and releases the memory of the deleted node.

To this, we also need to get the root node of a tree for the sake of testing, if all the nodes in the output tree are traversed in the middle order, the output will be sorted in order from small to large. There are two ways to get this root node, the first of which is to set the root node in the class to public, or we need to add a method RootNode to the class to return the current root node. Given the encapsulation, the latter is better. For security reasons, the returned pointer should be set to constant, for convenience.

treenode* Searchtree::rootnode () {return root;}
To test, we also needed a function that would output nodes in the tree in the middle order, in order to be concise in code, in a recursive way. The code is as follows:

void output (TreeNode *node) {if (node!=null) {output (node->left); cout << node->value << "-"; Output ( node->right);}}
So far, the preparation is all over, write a simple test function, test our own implementation of the two-fork tree. The code is as follows:

int main () {TreeNode *root,*curr;searchtree tree, for (size_t i = 0; i < i++) {Tree.insert (rand ()% 200);} cout << tree.size () << Endl;curr = Tree.find (in), if (curr!=null) {cout << "Find the Node" << Endl;} Else{cout << "The node is not in the tree" << Endl;} Tree.erase (in); root = Tree.rootnode (); output (root);}

First, an instance of a tree is declared, the root node is 100, and a randomly generated 20 0-199 number is inserted into the tree. Then look for a node with a value of 41, and finally delete the node. Finally, all nodes in the tree are printed, if the nodes are printed from small to large, it means that this is a complete two-fork tree. The output results are as follows:



The first reason that size is 19 is that it is possible to generate duplicate points randomly, so that the insertion fails. We see nodes as well as small to large sequential prints. And then upload an image of the tree's memory in the VS environment:

We can see clearly that the root node is 100, and the left and right child nodes are 64 and 134, respectively. The left and right child nodes of node 64 are 27 and 67, respectively, and they can clearly see their distribution in memory.



Binary Tree Learning notes-implementation

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.