[C++ dry goods store] Binary tree (BSTree) that can search

=========================================================================

Click to go directly to personal homepage:Xiaobai is not a program Yuan

C++ series column:C++ dry goods store

Gitee:Gitee

=========================================================================

Table of contents

Foreword:

binary search tree

Binary search tree concept

Binary search tree operations

Binary search tree search

 Binary search tree insertion

Deletion of binary search tree elements

Implementation of binary search tree

BSTree node

BSTree framework

Copy constructor and parameterless constructor

destructor

Assignment overloading (operator=)

Insert()

FindFind()

delete()

 In-order traversal

Application of Binary Search Tree

Performance Analysis of Binary Search Tree


Foreword:

During the data structure of C language, we introduced some concepts ofbinary tree; and implemented its chain structure and the previous Middle and post-order traversal; some OJ questions are difficult to implement using C language. For example, in some places, dynamically opened two-dimensional arrays need to be returned, which is very troublesome. Therefore, this section uses the binary tree search tree to summarize the binary tree part. And the following map and set features need to prepare the binary search tree first, and the binary search tree is also a tree structure; the binary search tree Characteristic understanding helps to better understand the characteristics of map and set.


binary search tree

Binary search tree concept

Binary search tree is also called binary sorting tree. It is either an empty tree,or a binary tree with the following properties:

  • If its left subtree is not empty, the values ​​of all nodes on the left subtree are less than the value of the root node
  • If its right subtree is not empty, the values ​​of all nodes on the right subtree are greater than the value of the root node
  • Its left and right subtrees are also binary search trees respectively.


Binary search tree operations

Binary search treeIn-order traversalAccording to its storage structure, it issorted< /span>.

  • If a number smaller than the root is stored on the left and a number larger than the root is stored on the right, the results of inorder traversal are in ascending order;
  • If an array larger than the root is stored on the left and a number smaller than the root is stored on the right, the result of inorder traversal is in descending order;
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

Note:The binary search tree isnot "modified", because if it is casual a>To modify a piece of data, the entire tree must be re-implemented

Binary search tree search

  • Compare and search starting from the root. If it is larger than the root, search to the right. If it is smaller than the root, search to the left.
  • The height has been searched for at most times, and it has reached empty. It has not been found yet. This value does not exist.​ 

Notice:

Binary search trees have a particularly importantcharacteristic:There are no two identical ones in the tree element.

 Binary search tree insertion

The specific process of insertion is as follows:

  • If the tree is empty, add a node directly and assign it to the root pointer.
  • If the tree is not empty, find the insertion position according to the properties of the binary search tree and insert the new node. 

Deletion of binary search tree elements

First check whether the element is in the binary search tree. If it does not exist, return it. Otherwise, the node to be deleted may be divided into the following four situations
:

  • The node to be deleted has no child nodes
  • The node to be deleted is only the left child node
  • The node to be deleted is only the right child node
  • The node to be deleted has left and right child nodes

It seems that there are 4 situations of nodes to be deleted. The actual situation 1 can be combined with situations 2 or 3, so the real deletion process
is as follows: 

  • Case b: Delete the node and make the parent node of the deleted node point to the left child node of the deleted node - delete directly
  • Case c: Delete the node and make the parent node of the deleted node point to the right child node of the deleted node - delete directly
  • Case d: Find the first node in the middle order (the smallest key code) in its right subtree, fill it with its value into the deleted node, and then deal with the deletion problem of the node - deletion by substitution method


Implementation of binary search tree

BSTree node

The node contains two pointers of this node type, which respectively represent pointing to the left and right children< /span> and the value stored in the node.

template <class K>
struct BSTNode
{
	BSTNode<K>* _left;
	BSTNode<K>* _right;
	K _key;
    //结点的构造函数
	BSTNode(const K& key)
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
	{
	}
};

BSTree framework

Member variables are pointers of node types.

template<class K>
class BST
{
	typedef BSTNode<K> Node;

private:
	Node* _root=nullptr;
};

Copy constructor and parameterless constructor

Because we wrote the copy constructor ourselves, the compiler will not generate a parameterless constructor by default. In C++11 you can make the default constructor equal to default, Let the compiler automatically generate the default constructor again.

Copying a binary search tree starts with recursive calls.​ 

    BST() = default;
	BST(const BST<K>& st)
	{
		_root=Copy(st._root);
	}

destructor

Because it is troublesome for us to explicitly call the root node outside the class, we can directly implement it recursively inside the class with the root node as a parameter.

public:

    ~BST()
	{
		Destory(_root);
	}
private:
​
    void  Destory(Node*& root)
	{
		if (root == nullptr)
		{
			return;
		}
		Destory(root->_left);
		Destory(root->_right);
		delete root;
		root = nullptr;
	}

​

Assignment overloading (operator=)

swap function is a function in library std, deep copy;

	BST<K>& operator=(BST<K> t)
	{
		swap(_root, t._root);
		return *this;
	}

Insert()

non-recursive version

bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			parent = cur;
			if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(key);
		if (parent->_key < key)
		{
			parent->_right = cur;
		}
		else if (parent->_key > key)
		{
			parent->_left = cur;
		}
	}
  • First, you still need to determine whether the incoming root node is empty. If it is empty, just open a new node directly;
  • If it is not empty, first create a parent node to facilitate modification during insertion; then create a node starting from the root node and start looking for a suitable insertion position based on the characteristics of the binary search tree. When found, open a new node. node, and then let the root node at the appropriate location point to the new node that has been created;

recursive version

pbulic:

    bool InsertR(const K & key)
	{
		return _InsertR(_root, key);
	}
private:
​
    bool _InsertR(Node*& root,const K& key)
	{
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}
		if (root->_key < key)
		{
			return _InsertR(root->_right, key);
		}
		else if (root->_key > key)
		{
			return _InsertR(root->_left, key);
		}
		else
		{
			return false;
		}
	}

​

The recursion here is also based on the characteristics of the children on the left and right sides of the binary search treeIt is realized by clever use of references. The parameters of each recursion are: The previous root node points to the reference of the left child or the right child, the record parent node is removed

FindFind()

non-recursive version

It is also implemented based on the characteristics of the left and right children of the binary search tree. If the value found is greater than the value of the root node, it is compared with the right child of the root node, and vice versa;

Note:There are no two identical values ​​in the search binary tree.

bool find(const K& key)
	{
		if (_root == nullptr)
		{
			return false;
		}
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else
			{
				return true;
			}
		}
		return false;
	}

recursive version

public :

    bool FindR(const K & key)
	{
		return _FindR(_root, key);
	}
private :

​
    bool _FindR(Node* root, const K& key)
	{
		if (root == nullptr)
		{
			return false;
		}
		if (root->_key < key)
		{
			return _FindR(_root->_right, key);
		}
		else if (root->_key > key)
		{
			return _FindR(root->_left, key);
		}
		else
		{
			return true;
		}
	}

​

delete()

non-recursive version

The situation here is more complicated when deleting. You must first find the node according to the idea of ​​finding the function above;

  • If the left child is empty and the node is the left child of the parent node, let the left child pointed by the parent node be the right branch of the node; delete this node. If the node is the right child of the parent node, let the right child pointed to by the parent node be the right branch of the node; delete this node.
  • If the right child is empty and the node is the left child of the parent node, let the left child pointed by the parent node be the right branch of the node; delete this node. If the node is the right child of the parent node, let the left child pointed by the parent node be the left branch of the node; delete this node.
  • If both the left and right children are empty, you need to take the largest left branch (rightmost node) or the smallest right branch (leftmost node). What is implemented here is to take the smallest right branch; enter the node first. On the right, then use a loop to find the leftmost node; exchange the values ​​of this node and its parent node, and then adjust the child node pointed to by its parent node according to the above left child being empty. Then delete the node.
bool Erase(const K& key)
	{
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				//左为空
				if (cur->_left == nullptr)
				{
					//删除根节点的值
					if (cur == _root)
					{
						_root = cur->_right;
					}
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_right;
						}
						else if (parent->_right == cur)
						{
							parent->_right = cur->_right;
						}
					}
					delete cur;
				}
				//右为空
				else if (cur->_right == nullptr)
				{
					//删除根节点的值
					if (cur == _root)
					{
						_root = cur->_left;
					}
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_left;
						}
						else if (parent->_right == cur)
						{
							parent->_right = cur->_left;
						}
					}
					delete cur;
				}
				else
				{
					//右树的最小值
					Node* subleft = cur->_right;
					Node* parent = cur;
					while (subleft->_left)
					{
						parent = subleft;
						subleft = subleft->_left;
					}
					swap(cur->_key, subleft->_key);
					if (subleft == parent->_left)
					{
						parent->_left = subleft->_right;
					}
					else
					{
						parent->_right = subleft->_right;
					}
					delete subleft;
				}
				return true;
			}
		}
		return false;
	}

recursive version

public:
    bool EraseR(const K&key)
	{
		return _EraseR(_root, key);
	}
private:

    bool _EraseR(Node*& root, const K& key)
	 {
		 if (root == nullptr)
		 {
			 return false;
		 }
		 if (root->_key < key)
		 {
			 return _EraseR(root->_right, key);
		 }
		 else if (root->_key > key)
		 {
			return  _EraseR(root->_left, key);
		 }
		 else
		 {
			 if (root->_left == nullptr)
			 {
				 Node* del = root;
				 root = root->_right;
				 delete del;
				 return true;
			 }
			 else if (root->_right == nullptr)
			 {
				 Node* del = root;
				 root = root->_left;
				 delete del;
				 return true;
			 }
			 else
			 {
				 Node* subleft = root->_right;
				 while (subleft->_left)
				 {
					 subleft = subleft->_left;
				 }
				 swap(root->_key, subleft->_key);
				 return _EraseR(root->_right, key);
			 }
		 }

	 }

 In-order traversal

public:
    void Inorder()
	{
		_Inorder(_root);
		cout << endl;
	}
private:
​
    void _Inorder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_Inorder(root->_left);
		cout << root->_key << " ";
		_Inorder(root->_right);
	}

​

Application of Binary Search Tree

1. K model: The K model only has key as the key code. Only the Key needs to be stored in the structure. The key code is The value to be searched for.
For example, given a word word, determine whether the word is spelled correctly. The specific method is as follows:

  • Construct a binary search tree using each word in the set of all words in the vocabulary as a key
  • Retrieve whether the word exists in the binary search tree. If it exists, it is spelled correctly. If it does not exist, it is spelled incorrectly.

2. KV model: Each key key has a corresponding value, that is, <Key, Value> key-value pairs. This method is very common in real life:

  • For example, the English-Chinese dictionary is the correspondence between English and Chinese. You can quickly find the corresponding Chinese through English. The English word and its corresponding Chinese <word, chinese> form a key-value pair;
  • Another example is counting the number of words. After the counting is successful, the number of occurrences of a given word can be quickly found. The word and its number of occurrences are <word, count>, forming a key-value pair.​ 

Performance Analysis of Binary Search Tree

Both insertion and deletion operations must be searched first, and search efficiency represents the performance of each operation in the binary search tree.

For a binary search tree with n nodes, if the probability of searching for each element is equal, the average search length of the binary search tree is the node in the binary search
A function of the depth of the tree, that is, the deeper the node, the more comparisons it takes. However, for the same key set, if the order of insertion of each key is different, binary search trees with different structures may be obtained: 

  • Under optimal circumstances, the binary search tree is a complete binary tree (or close to a complete binary tree), and its average number of comparisons is: O(logN)
  • In the worst case, the binary search tree degenerates into a single-branch tree (or similar to a single-branch tree), and its average number of comparisons is: O(N); if it degenerates into a single-branch tree, the performance of the binary search tree is lost. . At this time, you need to use the upcoming AVL tree and red-black tree.

Today’s sharing of the introduction, use, and simulation implementation of binary search trees ends here. I hope you will gain a lot after reading it. You can also comment on the content of the article and share your own opinions in the comment area. Your support is the driving force for me to move forward. Thank you all for your support! ! !

Guess you like

Origin blog.csdn.net/qq_55119554/article/details/135036620