[C++ Grocery Store] A binary tree with search function

Insert image description here

1. Binary search tree concept

Binary search tree is also called binary insertion 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 (greater 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 (less than) the value of the root node.

  • Its left and right subtrees are also binary search trees respectively.

Insert image description here

2. Operation of binary search tree

2.1 Searching in Binary Search Tree

  • 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 maximum number of times the height has been searched, and if it is still not found when it is empty, it means that this value does not exist.

Small Tips : The time complexity of searching the maximum height here is not O (log N) O(logN)O ( l o g N ) , this is based on a relatively ideal situation, that is, this binary tree is a full binary tree or a complete binary tree. In extreme cases, this binary tree has only one path, and the time complexity of searching at most heights isO ( N ) O(N)O ( N )

2.2 Insertion into binary search tree

The specific process of insertion is as follows:

  • If the tree is empty: add a node directly and assign it to the root pointer.

  • The tree is not empty: first find the insertion position according to the properties of the binary search tree, and insert the new node.

Insert image description here

2.3 Deletion of binary search tree

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:

  1. The node to be deleted has no child nodes.

  2. The node to be deleted is only the left child node.

  3. The node to be deleted is only the right child node.

  4. The node to be deleted has left and right child nodes.

Although it seems that there are 4 cases for deleting a node, in fact case 1 can be combined with case 2 or case 3, so the real deletion process is as follows:

  • Situation 1 (the node to be deleted only has the left child) : 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 2 (the node to be deleted only has the right child) : 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 three (the node to be deleted has left and right children) : Find the node with the largest key code in its left subtree, fill it with its value into the deleted node, and then process the deletion of the node Problem----Replacement method to delete.

Insert image description here

3. Implementation of binary search tree

The two-insertion search tree is just a structure. It is essentially linked by nodes. Therefore, we first need to define a node class. This node is used to store data. After having the node class, you need to define a binary search tree class. This class is mainly used to maintain the structure and implement functions such as additions, deletions, and modifications. Because it maintains the structure, the member variables in this class only need one The root node is enough. With this root node, the entire number structure can be maintained and managed.

3.1 BinarySearchTreeNode (node ​​class)

template <class K>
struct BinarySearchTreeNode
{
    
    
	typedef BinarySearchTreeNode<K> TNode;

	BinarySearchTreeNode(const K& val = K())
		:_val(val)
		,_left(nullptr)
		,_right(nullptr)
	{
    
    }

	K _val;
	TNode* _left;
	TNode* _right;
};

3.2 BinarySearchTree (binary search tree class)

3.2.1 Framework

template <class K>
class BinarySearchTree
{
    
    
	typedef BinarySearchTreeNode<K> BSTNode;
	typedef BinarySearchTree<K> Self;
public:
	BinarySearckTree()
		:_root(nullptr)
	{
    
    }
private:
	BSTNode* _root;
};

3.2.2 insert

Non-recursive version :

bool insert(const K& val)
{
    
    
	if (_root == nullptr)
	{
    
    
		_root = new BSTNode(val);
		return true;
	}

	BSTNode* newnode = new BSTNode(val);
	BSTNode* cur = _root;
	BSTNode* parent = nullptr;
	while (cur)
	{
    
    
		if (val < cur->_val)
		{
    
    
			parent = cur;
			cur = cur->_left;
		}
		else if (val > cur->_val)
		{
    
    
			parent = cur;
			cur = cur->_right;
		}
		else
		{
    
    
			return false;//相等就说明树中已经有了,就应该插入失败
		}
	}

	//if (parent->_left == cur)//左右都是空,每次就走上面这个了
	if(val < parent->_val)
	{
    
    
		parent->_left = newnode;
	}
	else
	{
    
    
		parent->_right = newnode;
	}

	return true;
}

Small Tips : You need to consider the case where the root node is empty separately. Use curto find the position where the node should be inserted, and use to parentpoint to the parent node of that position to achieve the link relationship. Finally, it is necessary to determine whether to insert to the left or right side of the parent node. The binary search tree we implemented requires that nodes storing the same value can only appear once in a binary search tree. Therefore, when inserting a value, if it is detected that there is already a valnode in the tree that stores val, then It should return falseindicating that the insertion failed.

Recursive version :

//插入(递归---版本一)
private:
	bool _InsertR(BSTNode*& root, BSTNode* parent, const K& key)
	{
    
    
		if (root == nullptr)//为空说明就是在该位置插入
		{
    
    
			BSTNode* newnode = new BSTNode(key);
			if (parent != nullptr)
			{
    
    
				if (key < parent->_val)
				{
    
    
					parent->_left = newnode;
				}
				else
				{
    
    
					parent->_right = newnode;
				}
			}
			else
			{
    
    
				root = newnode;
			}
	
			return true;
		}
	
		//root不为空说明还没有找到待插入的位置,还得继续找
		if (key < root->_val)
		{
    
    
			return _InsertR(root->_left, root, key);
		}
		else if (key > root->_val)
		{
    
    
			return _InsertR(root->_right, root, key);
		}
		else
		{
    
    
			return false;
		}
	}
public:
	//插入(递归)
	bool InsertR(const K& key)
	{
    
    
		return _InsertR(_root, _root, key);
	}
//插入(递归---版本二)
private:
	bool _InsertR(BSTNode*& root, const K& key)
	{
    
    
		if (root == nullptr)//为空说明就是在该位置插入
		{
    
    
			root = new BSTNode(key);
			return true;
		}

		//root不为空说明还没有找到待插入的位置,还得继续找
		if (key < root->_val)
		{
    
    
			return _InsertR(root->_left, key);
		}
		else if (key > root->_val)
		{
    
    
			return _InsertR(root->_right, key);
		}
		else
		{
    
    
			return false;
		}
	}
public:
	//插入(递归)
	bool InsertR(const K& key)
	{
    
    
		return _InsertR(_root, key);
	}

Small Tips : When inserting into an empty tree, the root node needs to be changed _root, that is, the pointer needs to be modified, so a reference or secondary pointer needs to be used here.

3.2.3 InOrder (in-order traversal)

private:
	void _InOrder(BSTNode* root) const
	{
    
    
		if (root == nullptr)
		{
    
    
			return;
		}

		_InOrder(root->_left);
		cout << root->_val << " ";
		_InOrder(root->_right);
	}
public:
	void InOrder()
	{
    
    
		_InOrder(_root);
		cout << endl;
	}

Tips : The in-order traversal here is implemented recursively, but the recursive function must require a parameter. To traverse the entire binary tree in in-order, the user must pass the root node to this function, but the root _rootnode _rootIt is a private member variable and cannot be accessed by users, so we cannot directly provide in-order traversal functions to users. The correct approach is that although the user cannot access the root node, it can be accessed in the class, so we can implement a sub-function of in-order traversal in the class, and _InOrderimplement the logic of in-order traversal in this sub-function, and then we Then provide the user with a function interface for in-order traversal InOrder, which can be called by it _InOrder. In this way, users can use in-order traversal normally.

3.2.4 find

Non-recursive version :

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

	return false;
}

Recursive version :

private:
	bool _FindR(BSTNode* root, const K& key)
	{
    
    
		if (root == nullptr)
		{
    
    
			return false;
		}

		if (key < root->_val)
		{
    
    
			return _FindR(root->_left, key);
		}
		else if (key > root->_val)
		{
    
    
			return _FindR(root->_right, key);
		}
		else
		{
    
    
			return true;
		}
	}
public:
	bool FindR(const K& key)
	{
    
    
		return _FindR(_root, key);
	}

3.2.5 erase(delete)

Non-recursive version :

bool erase(const K& key)
{
    
    
	BSTNode* cur = _root;
	BSTNode* parent = nullptr;

	//先找需要删除的结点
	while (cur)
	{
    
    
		if (key < cur->_val)
		{
    
    
			parent = cur;
			cur = cur->_left;
		}
		else if (key > cur->_val)
		{
    
    
			parent = cur;
			cur = cur->_right;
		}
		else
		{
    
    
			//到这里说明cur就是待删除的节点
			if (cur->_left == nullptr)//如果cur只有一个孩子(只有右孩子),直接托孤
			{
    
    
				if (parent == nullptr)//说明删除的是根结点
				{
    
    
					_root = _root->_right;
				}
				else if (cur == parent->_left)//判断cur是左孩子还是右孩子
				{
    
    
					parent->_left = cur->_right;
				}
				else if(cur == parent->_right)
				{
    
    
					parent->_right = cur->_right;
				}
			}
			else if(cur->_right == nullptr)//如果cur只有一个孩子(只有左孩子)
			{
    
    
				if (parent == nullptr)//说明删除的是根结点
				{
    
    
					_root = _root->_left;
				}
				else if (cur == parent->_left)//判断cur是左孩子还是右孩子
				{
    
    
					parent->_left = cur->_left;
				}
				else if (cur == parent->_right)
				{
    
    
					parent->_right = cur->_left;
				}
			}
			else//到这里说明cur有两个孩子
			{
    
    
				BSTNode* parent = cur;
				BSTNode* leftmax = cur->_left;//找到左孩子中最大的那个
				while (leftmax->_right)
				{
    
    
					parent = leftmax;
					leftmax = leftmax->_right;
				}

				swap(cur->_val, leftmax->_val);
				cur = leftmax;

				//有一种极端情况就是左边只有一条路径
				if (leftmax == parent->_left)
				{
    
    
					parent->_left = leftmax->_left;
				}
				else
				{
    
    
					parent->_right = leftmax->_left;
				}
			}

			delete cur;
			return true;
		}
	}

	return false;
}

Small Tips : In the above code, we always point to curthe node to be deleted and parentto the parents of the node to be deleted, that is, curthe parents of . Deletion is generally divided into the three situations mentioned in Section 2.3. But there are still some details that we need to pay attention to, such as the situation of deleting the root node, that is, parent == nullptrwhen . In cases one and two, we also need to determine whether the node to be deleted curis the left or right child of its parent node parentto ensure that curthe children of and parentestablish a correct link relationship. Case 3: The node to be deleted has two children. What we do here is to find curthe largest node in the left subtree leftmaxand let it replace it curto help curbring up the "children". It is easy to find the node with the largest value in the left subtree. Just curstart from the left child of and go all the way to the right. Exchange the values ​​stored in curand after finding them. leftmaxAfter the exchange, leftmaxit becomes the node to be deleted, so you need to curredirect it to leftmaxthis node at this time. Since we want to delete leftmaxthe node, in order to facilitate later modification of the link relationship, here we also need to find the parent leftmaxnode of The meaning represented by inside is different from the meaning expressed outside. The former represents the parent node of the largest node in the left tree, and the latter represents the parent node of . Finally, we need to modify the link relationship to achieveparentparentparentparentcurcurcurWhen deleting a node, the link relationship here has the following two situations:

Scenario 1 :
Insert image description here
Small Tips : The exchange in Step 2 is to exchange the values ​​in the nodes, not to exchange two nodes. Ultimately leftmaxand curpoint to the same node.

Scenario 2 :
Insert image description here
Small Tips : The biggest difference between Scenario 2 and Scenario 1 is reflected in two places. First, in Scenario 2, it parentonly curmeans that we parentcannot let it go when defining and assigning the initial value. We parent = nullptrshould let it go parent = cur, otherwise the link relationship will be modified later. There will be a problem of accessing a null pointer. The second difference lies in modifying the link relationship. In case two, let parentthe left child of point to leftmaxthe left child of ; in case one, let parentthe right child point to leftmaxthe left child of . Therefore, when modifying the link relationship, judgment must be made to see what the situation is. There is another similarity in the second difference, that is, whether it is the parentleft child of or parentthe right child of , they ultimately point to leftmaxthe left child of . Why is this? The reason is actually very simple. leftmaxThe right child of must be empty, but the left child may not be empty. Why can we be sure that the right child is initially empty? Because leftmaxis the largest node in the left subtree. If its right child is not empty, it means that the current node leftmaxmust not be the largest node. Therefore, when modifying the link relationship, a connection must be established parentwith leftmaxthe left child of . Finally, it should be noted that after the exchange, curthe node can only be deleted by modifying the link relationship, not by recursive call, because this function starts searching from the root node every time, and the tree after the exchange does not satisfy the binary requirement temporarily. The structure of the search tree, taking case 1 as an example, it cannot find the node storing 8.

Recursive version :

private:
//删除递归版
	bool _eraseR(BSTNode*& root, const K& key)
	{
    
    
		if (root == nullptr)
		{
    
    
			return false;
		}

		if (key < root->_val)
		{
    
    
			return _eraseR(root->_left, key);
		}
		else if (key > root->_val)
		{
    
    
			return _eraseR(root->_right, key);
		}
		else
		{
    
    
			//相等了,需要进行删除了
			BSTNode* del = root;

			if (root->_left == nullptr)//左为空
			{
    
    
				root = root->_right;
			}
			else if (root->_right == nullptr)//右为空
			{
    
    
				root = root->_left;
			}
			else//左右都不为空
			{
    
    
				BSTNode* parent = root;
				BSTNode* leftmax = root->_left;//找到左孩子中最大的那个
				while (leftmax->_right)
				{
    
    
					parent = leftmax;
					leftmax = leftmax->_right;
				}
				swap(root->_val, leftmax->_val);
				
				return _eraseR(root->_left, key);
			}
			
			delete del;
			del = nullptr;
			return true;
		}
	}
public:
	//删除递归版
	bool eraseR(const K& key)
	{
    
    
		return _eraseR(_root, key);
	}

Small Tips : After the exchange, although the entire tree may not satisfy the structure of the binary search tree, the rootleft subtree of the node must satisfy the binary search tree, because what we exchange is rootthe node _valand the left subtree. The largest one _val, and rootthe node must be larger _valthan the largest one in the left subtree , so after the exchange , the left subtree still satisfies the structure of the binary search tree. At this time, we can call it recursively. Find the node to be deleted in the left subtree of , and the node to be deleted must become one of situation 1 or situation 2 after the exchange. The processing of case one and case two in the recursive version is much simpler, because is a reference. If one of the children of is found to be empty, just assign another child of to it. Remember to save the value before assigning . The node pointed by this value is the node to be deleted. Save this value before deleting it. Otherwise, there will be no pointer pointing to the node after the assignment, and there will be no way to release the space resources of this node, which will cause a memory leak. . Even if a reference is used in non-recursion, this cannot be done, because in non-recursion, a reference is always in a function stack frame, and the reference cannot change its pointer. But recursion is different. Each recursive call will open a new function stack frame, and each function stack frame is an alias for a different node._valrootrootrootrootrootrootdeleteroot

3.2.6 ~BinarySearchTree (destruction)

private:
	//析构子函数
	void Destruction(BSTNode*& root)
	{
    
    
		if (root == nullptr)
		{
    
    
			return;
		}

		//先去释放左孩子和右孩子的空间资源
		Destruction(root->_left);
		Destruction(root->_right);

		//再去释放root自己的空间资源
		delete root;
		root = nullptr;//形参root如果不加引用,这里置空是没有任何意义的,因为不加引用这里仅仅是一份拷贝

		return;
	}
public:
	//析构函数
	~BinarySearckTree()
	{
    
    
		Destruction(_root);
	}

3.2.7 BinarySearchTree(const Self& tree) (copy construction)

Note that the copy constructor cannot directly call insert. Because the order of data insertion is different, the final structure of the tree is also different. Although it ultimately conforms to the structure of a binary tree, it is still different from the copied tree. The correct approach is to perform a preorder traversal. When traversing a node of the tree, go to a new node and store the same value.

Writing method one :

//拷贝构造函数的子函数
private:
	void Construct(BSTNode*& root, BSTNode* copy)
	{
    
    
		if (copy == nullptr)
		{
    
    
			root = nullptr;
			return;
		}

		root = new BSTNode(copy->_val);//通过引用直接来实现链接关系
		Construct(root->_left, copy->_left);
		Construct(root->_right, copy->_right);
	}
public:
	//拷贝构造
	BinarySearchTree(const Self& tree)
		:_root(nullptr)
	{
    
    
		Construct(_root, tree._root);
	}

Writing method two :

private:
//拷贝构造子函数(写法二)
	BSTNode* Construct(BSTNode* root)
	{
    
    
		if (root == nullptr)
		{
    
    
			return nullptr;
		}

		BSTNode* newnode = new BSTNode(root->_val);
		newnode->_left = Construct(root->_left);//通过返回值来实现链接关系
		newnode->_right = Construct(root->_right);

		return newnode;
	}
public:
	//拷贝构造(写法二)
	BinarySearchTree(const Self& tree)
	{
    
    
		_root = Construct(tree._root);
	}

Tips : The difference between the above two writing methods is that the first method is to realize the link relationship through reference, and the second method is to realize the link relationship through return value.

3.2.8 operator= (assignment operator overloading)

public:
//赋值运算符重载(现代写法)
	Self& operator=(Self tree)
	{
    
    
		swap(_root, tree._root);//交换两颗搜索二叉树就是交换它们里面维护的根节点

		return *this;
	}

4. Application of Binary Search Tree

4.1 K model

The K model has only one Key as the key code. Only the Key needs to be stored in the structure, and the key code is the value that needs to be searched. 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.

The binary search tree we created above is the Key model, because the nodes of this tree can only store one value, and this value is the Key.

4.2 KV model

The KV model means that each key code Key has a corresponding value Value, that is, a key-value pair of <Key, Value>. 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. An 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>, which forms a key-value pair.

4.2.1 KV model hand tearing

#pragma once

namespace K_V
{
    
    
	template <class K, class V>
	struct BinarySearchTreeNode
	{
    
    
		typedef BinarySearchTreeNode<K, V> TNode;

		BinarySearchTreeNode(const K& key = K(), const V& val = V())
			:_key(key)
			, _val(val)
			, _left(nullptr)
			, _right(nullptr)
		{
    
    }

		K _key;
		V _val;
		TNode* _left;
		TNode* _right;
	};

	template <class K, class V>
	class BinarySearchTree
	{
    
    
		typedef BinarySearchTreeNode<K, V> BSTNode;
		typedef BinarySearchTree<K, V> Self;
	private:
		void _InOrder(BSTNode* root) const
		{
    
    
			if (root == nullptr)
			{
    
    
				return;
			}

			_InOrder(root->_left);
			cout << root->_key << "--" << root->_val << endl;
			_InOrder(root->_right);
		}

		BSTNode* _FindR(BSTNode* root, const K& key)//KV模型中的Key不能被修改,但是Val可以被修改
		{
    
    
			if (root == nullptr)
			{
    
    
				return nullptr;
			}

			if (key < root->_key)
			{
    
    
				return _FindR(root->_left, key);
			}
			else if (key > root->_key)
			{
    
    
				return _FindR(root->_right, key);
			}
			else
			{
    
    
				return root;
			}
		}

		//插入(递归---版本一)
		//bool _InsertR(BSTNode*& root, BSTNode* parent, const K& key)
		//{
    
    
		//	if (root == nullptr)//为空说明就是在该位置插入
		//	{
    
    
		//		BSTNode* newnode = new BSTNode(key);
		//		if (parent != nullptr)
		//		{
    
    
		//			if (key < parent->_key)
		//			{
    
    
		//				parent->_left = newnode;
		//			}
		//			else
		//			{
    
    
		//				parent->_right = newnode;
		//			}
		//		}
		//		else
		//		{
    
    
		//			root = newnode;
		//		}

		//		return true;
		//	}

		//	//root不为空说明还没有找到待插入的位置,还得继续找
		//	if (key < root->_key)
		//	{
    
    
		//		return _InsertR(root->_left, root, key);
		//	}
		//	else if (key > root->_key)
		//	{
    
    
		//		return _InsertR(root->_right, root, key);
		//	}
		//	else
		//	{
    
    
		//		return false;
		//	}
		//}

		//插入(递归---版本二)
		bool _InsertR(BSTNode*& root, const K& key, const V& val)
		{
    
    
			if (root == nullptr)//为空说明就是在该位置插入
			{
    
    
				root = new BSTNode(key, val);
				return true;
			}

			//root不为空说明还没有找到待插入的位置,还得继续找
			if (key < root->_key)
			{
    
    
				return _InsertR(root->_left, key, val);
			}
			else if (key > root->_key)
			{
    
    
				return _InsertR(root->_right, key, val);
			}
			else
			{
    
    
				return false;
			}
		}

		//删除递归版
		bool _eraseR(BSTNode*& root, const K& key)
		{
    
    
			if (root == nullptr)
			{
    
    
				return false;
			}

			if (key < root->_key)
			{
    
    
				return _eraseR(root->_left, key);
			}
			else if (key > root->_key)
			{
    
    
				return _eraseR(root->_right, key);
			}
			else
			{
    
    
				//相等了,需要进行删除了
				BSTNode* del = root;

				if (root->_left == nullptr)//左为空
				{
    
    
					root = root->_right;
				}
				else if (root->_right == nullptr)//右为空
				{
    
    
					root = root->_left;
				}
				else//左右都不为空
				{
    
    
					BSTNode* parent = root;
					BSTNode* leftmax = root->_left;//找到左孩子中最大的那个
					while (leftmax->_right)
					{
    
    
						parent = leftmax;
						leftmax = leftmax->_right;
					}
					std::swap(root->_key, leftmax->_key);

					return _eraseR(root->_left, key);
				}

				delete del;
				del = nullptr;
				return true;
			}
		}

		//析构子函数
		void Destruction(BSTNode*& root)
		{
    
    
			if (root == nullptr)
			{
    
    
				return;
			}

			//先去释放左孩子和右孩子的空间资源
			Destruction(root->_left);
			Destruction(root->_right);

			//再去释放root自己的空间资源
			delete root;
			root = nullptr;//形参root如果不加引用,这里置空是没有任何意义的,因为不加引用这里仅仅是一份拷贝

			return;
		}

		//拷贝构造函数的子函数(写法一)
		void Construct(BSTNode*& root, BSTNode* copy)
		{
    
    
			if (copy == nullptr)
			{
    
    
				root = nullptr;
				return;
			}

			root = new BSTNode(copy->_key);
			Construct(root->_left, copy->_left);
			Construct(root->_right, copy->_right);
		}

		//拷贝构造子函数(写法二)
		BSTNode* Construct(BSTNode* root)
		{
    
    
			if (root == nullptr)
			{
    
    
				return nullptr;
			}

			BSTNode* newnode = new BSTNode(root->_key);
			newnode->_left = Construct(root->_left);
			newnode->_right = Construct(root->_right);

			return newnode;
		}

	public:
		BinarySearchTree()
			:_root(nullptr)
		{
    
    }

		//拷贝构造(写法一)
		/*BinarySearchTree(const Self& tree)
			:_root(nullptr)
		{
			Construct(_root, tree._root);
		}*/

		//拷贝构造(写法二)
		BinarySearchTree(const Self& tree)
		{
    
    
			_root = Construct(tree._root);
		}

		//赋值运算符重载(现代写法)
		Self& operator=(Self tree)
		{
    
    
			swap(_root, tree._root);//交换两颗搜索二叉树就是交换它们里面维护的根节点

			return *this;
		}

		//插入(非递归)
		bool insert(const K& key, const V& val)
		{
    
    
			if (_root == nullptr)
			{
    
    
				_root = new BSTNode(key, val);
				return true;
			}

			BSTNode* newnode = new BSTNode(key, val);
			BSTNode* cur = _root;
			BSTNode* parent = nullptr;
			while (cur)
			{
    
    
				if (key < cur->_key)
				{
    
    
					parent = cur;
					cur = cur->_left;
				}
				else if (key > cur->_key)
				{
    
    
					parent = cur;
					cur = cur->_right;
				}
				else
				{
    
    
					return false;//相等就说明树中已经有了,就应该插入失败
				}
			}

			//if (parent->_left == cur)//左右都是空,每次就走上面这个了
			if (key < parent->_key)
			{
    
    
				parent->_left = newnode;
			}
			else
			{
    
    
				parent->_right = newnode;
			}

			return true;
		}

		//插入(递归)
		bool InsertR(const K& key, const V& val)
		{
    
    
			return _InsertR(_root, key, val);
		}

		//中序遍历
		void InOrder()
		{
    
    
			_InOrder(_root);
			cout << endl;
		}

		//查找(非递归)
		BSTNode* find(const K& key)
		{
    
    
			BSTNode* cur = _root;
			while (cur)
			{
    
    
				if (key < cur->_key)
				{
    
    
					cur = cur->_left;
				}
				else if (key > cur->_key)
				{
    
    
					cur = cur->_right;
				}
				else
				{
    
    
					return cur;
				}
			}

			return nullptr;
		}

		//查找(递归)
		BSTNode* FindR(const K& key)
		{
    
    
			return _FindR(_root, key);
		}

		//删除(非递归)
		bool erase(const K& key)
		{
    
    
			BSTNode* cur = _root;
			BSTNode* parent = nullptr;

			//先找需要删除的结点
			while (cur)
			{
    
    
				if (key < cur->_key)
				{
    
    
					parent = cur;
					cur = cur->_left;
				}
				else if (key > cur->_key)
				{
    
    
					parent = cur;
					cur = cur->_right;
				}
				else
				{
    
    
					//到这里说明cur就是待删除的节点
					if (cur->_left == nullptr)//如果cur只有一个孩子(只有右孩子),直接托孤
					{
    
    
						if (parent == nullptr)
						{
    
    
							_root = _root->_right;
						}
						else if (cur == parent->_left)//判断cur是左孩子还是右孩子
						{
    
    
							parent->_left = cur->_right;
						}
						else if (cur == parent->_right)
						{
    
    
							parent->_right = cur->_right;
						}
					}
					else if (cur->_right == nullptr)//如果cur只有一个孩子(只有左孩子)
					{
    
    
						if (parent == nullptr)
						{
    
    
							_root = _root->_left;
						}
						else if (cur == parent->_left)//判断cur是左孩子还是右孩子
						{
    
    
							parent->_left = cur->_left;
						}
						else if (cur == parent->_right)
						{
    
    
							parent->_right = cur->_left;
						}
					}
					else//到这里说明cur有两个孩子
					{
    
    
						BSTNode* parent = cur;
						BSTNode* leftmax = cur->_left;//找到左孩子中最大的那个
						while (leftmax->_right)
						{
    
    
							parent = leftmax;
							leftmax = leftmax->_right;
						}

						std::swap(cur->_key, leftmax->_key);
						cur = leftmax;

						//有一种极端情况就是左边只有一条路径
						if (leftmax == parent->_left)
						{
    
    
							parent->_left = leftmax->_left;
						}
						else
						{
    
    
							parent->_right = leftmax->_left;
						}
					}

					delete cur;
					return true;
				}
			}

			return false;
		}

		//删除递归版
		bool eraseR(const K& key)
		{
    
    
			return _eraseR(_root, key);
		}

		//析构函数
		~BinarySearchTree()
		{
    
    
			Destruction(_root);
		}

	private:
		BSTNode* _root = nullptr;
	};
}

void TestBSTree4()
{
    
    
	// 统计水果出现的次数
	string arr[] = {
    
     "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };
	K_V::BinarySearchTree<string, int> countTree;
	for (const auto& str : arr)
	{
    
    
		// 先查找水果在不在搜索树中
		// 1、不在,说明水果第一次出现,则插入<水果, 1>
		// 2、在,则查找到的节点中水果对应的次数++
		//BSTreeNode<string, int>* ret = countTree.Find(str);
		auto ret = countTree.FindR(str);
		if (ret == NULL)
		{
    
    
			countTree.insert(str, 1);
		}
		else
		{
    
    
			ret->_val++;
		}
	}
	countTree.InOrder();
}

Insert image description here
Small Tips : Although it has become a KV model, it is still a binary search tree, so the structure of the entire tree has not changed. The only change lies in the nodes of the tree. For the KV model, the nodes in the tree must store not only the Key, but also the Value. This further leads to the insertion of not only the Key, but also a key related to the Key. The corresponding Value. Secondly, for the KV model, the Key is not allowed to be modified, but the Value can be modified. Therefore, for the KV model, the pointer of the node should be returned when Find, so as to facilitate some subsequent operations.

5. 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 a function of the depth of the node in the binary search tree, that is, the deeper the node, the more The more times. However, for the same key set, if each key is inserted in a different order, binary search trees with different structures may be obtained.

Insert image description here

  • Under the optimal situation : the binary search tree is a complete binary tree (or close to a complete binary tree), and its average number of comparisons is: log 2 n log2^nlog2n

  • 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: N 2 \frac{N}{2}2N. 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.

6. Conclusion

Today’s sharing ends here! If you think the article is good, you can support it three times . There are many interesting articles on Chunren's homepage . Friends are welcome to comment. Your support is the driving force for Chunren to move forward!

Insert image description here

Guess you like

Origin blog.csdn.net/weixin_63115236/article/details/133243323