[C++ Advanced] Binary Tree Search Tree

⭐Blog homepage: ️CS semi homepage
⭐Welcome to follow: like, favorite and leave a message
⭐ Column series: Advanced C++
⭐Code repository: Advanced C++
Home It is not easy for people to update. Your likes and attention are very important to me. Friends, please like and follow me. Your support is the biggest motivation for my creation. Friends are welcome to send private messages to ask questions. Family members, don’t forgetLike and collect + follow! ! !


1. Binary search tree

1. 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 , then 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, then 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

The following are all search binary trees. In short, they are small on the left and large on the right.
Insert image description here

2. Implementation of searching binary trees (non-recursive version and recursive version)

(1) Build a framework

To implement a binary search tree, we need to build a node class framework:
##1. The node class contains three member variables: node value, left pointer , right pointer.
##2. The node class only needs one constructor, which is used to construct nodes with specified node values.

// 先来个结点的定义
template<class K>
// struct为了默认是开放的
struct BSTreeNode
{
    
    
	// 左树右树和值
	BSTreeNode<K>* _right;
	BSTreeNode<K>* _left;
	K _key;
	// 构造一下左右结点和值(构造函数)
	BSTreeNode(const K& key)
		:_right(nullptr)
		, _left(nullptr)
		, _key(key)
	{
    
    }
};
// 二叉搜索树的定义
template<class K>
class BSTree
{
    
    
public:
	// 结点重命名为Node
	typedef BSTreeNode<K> Node;
	// 构造函数
	BSTree()
		:_root(nullptr)
	{
    
    }
private:
	Node* _root;
};

(2) Search

i. Non-recursive version

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.
Insert image description here

// 查找
	bool Find(const K& key)
	{
    
    
		// 定义一下当前节点为根节点
		Node* cur = _root;
		while (cur)
		{
    
    
			// 值小则往右找
			if (cur->_key < key)
			{
    
    
				cur = cur->_right;
			}
			// 值大则往左找
			else if (cur->_key > key)
			{
    
    
				cur = cur->_left;
			}
			// 找到则返回true
			else
			{
    
    
				return true;
			}
		}
		// 找不到返回false
		return false;
	}
ii. Recursive version

Code:

// 查找 -- 递归版本
	bool FindR(const K& key)
	{
    
    
		return _FindR(_root, key);
	}

	// 查找的子函数
	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;
		}
	}

Analysis:
Insert image description here

(3) Insert

i. Non-recursive version
  1. If the tree is empty, add a node directly and assign it to the root pointer.
  2. The tree is not empty. Find the insertion position according to the properties of the binary search tree (small on the left and large on the right) and insert the new node
    (1) If the value of the node to be inserted is less than the root node value, the node needs to be inserted into the left subtree.
    (2) If the value of the node to be inserted is greater than the value of the root node, the node needs to be inserted into the right subtree.
    (3) If the value of the node to be inserted is equal to the value of the root node, the node insertion fails.
    Continue like this until a node with the same value as the node to be inserted is found and the insertion fails, or it is finally inserted into the left and right subtrees of a leaf node (i.e. empty among trees).

Insert image description here

// 插入
	bool Insert(const K& key)
	{
    
    
		// _root为空
		if (_root == nullptr)
		{
    
    
			_root = new Node(key);
			return true;
		}

		// _root不为空
		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;
			}
			// 值相等则返回false
			else
			{
    
    
				return false;
			}
		}
		// 创建一个cur的带值的信结点
		cur = new Node(key);
		// 判断一层parent的值与key的值的大小
		if (parent->_key < key)
		{
    
    
			parent->_right = cur;
		}
		else
		{
    
    
			parent->_left = cur;
		}
		return true;
	}
ii. Recursive version

God's hand: & - const K& key - My next recursive version is your reference. I am you, so there is no need to pass parameters.

Insert image description here

// 查找
bool FindR(const K& key)
{
    
    
	return _FindR(_root, key);
}
// 插入的子函数
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;
	}
}

(4) Delete

Relatively speaking, it is more difficult. First, check whether the element is in the binary search tree. If it does not exist, return false and the deletion fails. Otherwise, the node to be deleted may be divided into the following three situations:

&&&Childless type: There is only one situation, which is the leaf node. Delete the leaf node and directly point its parent node to null. Just leave this node blank.
&&&One-child family type: There is only one child node, delete itself, and link the child node and the parent node.
&&&Two-child type: You can let the node to be deleted find the largest node in its left subtree to save the value, and then The value of the node to be deleted is replaced by the value of the largest node of the left subtree, and finally the largest node of the left subtree is deleted. Or find the node with the smallest value in the right subtree of the node to be deleted and save the value, then replace the value of the node to be deleted with the value of the smallest node of the right subtree, and finally delete the smallest node of the right subtree. .
There are two situations when two children replace nodes: If the replacement node happens to be a leaf node that has no children, just delete it and leave it blank.
If the replacement node has one child (regardless of the left and right children), it will be exactly the same as the one-child family model!

Explain why the replacement node either has no children or only one child: because the left is small and the right is large, if there are two children, then the replacement node must not be the node with the largest left subtree or the smallest right subtree!

Insert image description here

i. Non-recursive version

Code level analysis:
There are actually four types:
1. The left subtree is empty
2 , the right subtree is empty
3. The left and right subtrees are both empty
4. The root node to be deleted happens to be the root node

Insert image description here

// 删除左子树的最右结点
bool Erase(const K& key)
{
    
    
	Node* parent = nullptr; // 刚开始定义parent为NULL
	Node* cur = _root; // 当前节点就是根节点

	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->_right == cur) // 前面确保parent已经往后找过结点了
					{
    
    
						parent->_right = cur->_right; // 直接连接parent的右和cur的右
					}
					else
					{
    
    
						parent->_left = cur->_right; // 连接左对右
					}
				}
			}
			// 右为空
			else if (cur->_right == nullptr)
			{
    
    
				// 要删除的结点刚好是根节点
				if (cur == _root)
				{
    
    
					_root = cur->_left;
				}
				else
				{
    
    
					if (parent->_right == cur)
					{
    
    
						parent->_right = cur->_left;
					}
					else
					{
    
    
						parent->_left = cur->_left;
					}
				}
			}
			else // 都不为空
			{
    
    
				// 找替换节点
				Node* parent = cur; // 精妙部分就是parent定义为当前节点,因为保证parent节点不为空
				Node* LeftMax = cur->_left;  // 法一:用的是左树的最右节点
				while (LeftMax->_right)
				{
    
    
					parent = LeftMax;// parent永远比cur节点往前走一步
					LeftMax = LeftMax->_right;
				}
				swap(cur->_key, LeftMax->_key); // 交换待删结点的值和
				// 判断LeftMax在哪里的问题
				if (parent->_left == LeftMax)
				{
    
    
					parent->_left = LeftMax->_left;
				}
				else
				{
    
    
					parent->_right = LeftMax->_left;
				}
				cur = LeftMax; // 当前节点就是最右节点
			}
			delete cur;
			return true;
		}
	}
	return false;
}
// 删除右子树的最左节点
// 删除
bool Erase(const K& key)
{
    
    
	Node* parent = nullptr; // 刚开始定义parent为NULL
	Node* cur = _root; // 当前节点就是根节点

	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->_right == cur) // 前面确保parent已经往后找过结点了
					{
    
    
						parent->_right = cur->_right; // 直接连接parent的右和cur的右
					}
					else
					{
    
    
						parent->_left = cur->_right; // 连接左对右
					}
				}
			}
			// 右为空
			else if (cur->_right == nullptr)
			{
    
    
				// 要删除的结点刚好是根节点
				if (cur == _root)
				{
    
    
					_root = cur->_left;
				}
				else
				{
    
    
					if (parent->_right == cur)
					{
    
    
						parent->_right = cur->_left;
					}
					else
					{
    
    
						parent->_left = cur->_left;
					}
				}
			}
			//else // 都不为空
			//{
    
    
			//	// 找替换节点
			//	Node* parent = cur; // 精妙部分就是parent定义为当前节点,因为保证parent节点不为空
			//	Node* LeftMax = cur->_left;  // 法一:用的是左树的最右节点
			//	while (LeftMax->_right)
			//	{
    
    
			//		parent = LeftMax;// parent永远比cur节点往前走一步
			//		LeftMax = LeftMax->_right;
			//	}
			//	swap(cur->_key, LeftMax->_key); // 交换待删结点的值和
			//	// 判断LeftMax在哪里的问题
			//	if (parent->_left == LeftMax)
			//	{
    
    
			//		parent->_left = LeftMax->_left;
			//	}
			//	else
			//	{
    
    
			//		parent->_right = LeftMax->_left;
			//	}
			//	cur = LeftMax; // 当前节点就是最右节点
			//}
			else
			{
    
    
				Node* parent = cur;
				Node* RightMin = cur->_right;
				while (RightMin->_left)
				{
    
    
					parent = RightMin;
					RightMin = RightMin->_left;
				}
				swap(cur->_key, RightMin->_key);
				if (parent->_right = RightMin)
				{
    
    
					parent->_right = RightMin->_right;
				}
				else
				{
    
    
					parent->_right = cur->_left;
				}
				cur = RightMin;
			}
			delete cur;
			return true;
		}
	}
	return false;
}
ii. Recursive version

The recursive version is easy to understand,
1. If the tree is an empty tree. The node deletion fails and false is returned.
2. If the given key value is less than the value of the root node of the tree, the problem becomes deleting the node whose value is key in the left subtree.
3. If the given key value is greater than the value of the root node of the tree, the problem becomes deleting the node whose value is key in the right subtree.
4. If the given key value is the value of the root node, you can still find the smallest node of the left subtree or the rightmost node of the right subtree and follow the steps below.

The left subtree of the root is empty, and the new node is the right side of the root
The right subtree of the root is empty, and the new node is the left side of the root
If the left and right subtrees of the root are both empty, you can find the maximum value of the left subtree or the minimum value of the right subtree, exchange and then delete the current node.

// 删除
bool EraseR(const K& key)
{
    
    
	return _EraseR(_root, key);
}
	// 删除的子函数
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
	{
    
    
		Node* del = root;
		// 1、左为空
		// 2、右为空
		// 3、左右都不为空
		if (root->_left == nullptr)
		{
    
    
			root = root->_right;
		}
		else if (root->_right == nullptr)
		{
    
    
			root = root->_left;
		}
		else
		{
    
    
			Node* leftmax = root->_left;
			while (leftmax->_right)
			{
    
    
				leftmax = leftmax->_right;
			}
			swap(leftmax->_key, root->_key);
			return _EraseR(root->_left, key);
		}
		delete del;
		return true;
	}
}

(5) In-order traversal

The idea is left subtree-root-right subtree, recursive left subtree printing and then recursive right subtree.

// 中序遍历
void InOrder()
{
    
    
	_InOrder(_root);
	cout << endl;
}
	// 中序遍历的子函数
void _InOrder(Node* root)
{
    
    
	if (root == nullptr)
	{
    
    
		return;
	}

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

(7) Copy

The copy is a deep copy. It creates a copyroot tree and then recursively copies the left subtree, copies the right subtree, and finally returns the copyroot tree.

// 拷贝构造 -- 深拷贝
BSTree(const BSTree<K>& t)
{
    
    
	_root = Copy(t._root);
}
// 拷贝构造子函数
Node* Copy(Node* root)
{
    
    
	if (root == nullptr)
		return nullptr;
	Node* copyroot = new Node(root->_key);
	root->_left = Copy(root->_left); // 拷贝左子树
	root->_right = Copy(root->_right); // 拷贝右子树
	return copyroot;
}

(8) Assignment

Use the swap library function to assign values ​​directly.

// 赋值
BSTree<K>& operator=(BSTree<K> t)
{
    
    
	swap(_root, t._root);
	return *this;
}
	

(9) Destroy

// 销毁
void Destroy(Node*& root)
{
    
    
	if (root == nullptr)
		return;
	Destroy(root->_left);
	Destroy(root->_right);
	delete root;
	root = nullptr;
}

2. Performance analysis of binary trees

In the optimal case, the binary search tree is a complete binary tree (or close to a complete binary tree), and its average number of comparisons is: log2N.

In the worst case, the binary search tree degenerates into a single branch tree (or similar to a single branch), and its average number of comparisons is: N/2.

Insert image description here


3. Application of Binary Search Tree

Key model

Key search model to determine whether the keyword is present

For example, if we swipe our card to enter the dormitory, we can connect to the terminal to find the person’s information.
Check an English article for spelling errors.

Key-Value model

KV model: Each key code key has a corresponding value Value, that is, a key-value pair of <Key, Value>.

For example: for conversion between Chinese and English, the corresponding Chinese translation < word, chinese > can be found by inputting English words to form a key pair value.

Count the number of words and count the frequency of occurrence of this word, < word, count >.

// 改造二叉搜索树为KV结构
template<class K, class V>
struct BSTNode
{
    
    
	BSTNode(const K& key = K(), const V& value = V())
		: _PLeft(nullptr), _PRight(nullptr), _key(key), _Value(value)
	{
    
    }
	BSTNode<T>* _PLeft;
	BSTNode<T>* _PRight;
	K _key;
	V _value
};
template<class K, class V>
class BSTree
{
    
    
	typedef BSTNode<K, V> Node;
	typedef Node* PNode;
public:
	BSTree() 
		: _PRoot(nullptr) 
	{
    
    }
	PNode Find(const K& key);
	bool Insert(const K& key, const V& value);
	bool Erase(const K& key);
private:
	PNode _PRoot;
}

void TestBSTree()
{
    
    
	// 输入单词,查找单词对应的中文翻译
	BSTree<string, string> dict;
	dict.Insert("string", "字符串");
	dict.Insert("tree", "树");
	dict.Insert("left", "左边、剩余");
	dict.Insert("right", "右边");
	dict.Insert("sort", "排序");
	// 插入词库中所有单词
	string str;
	while (cin >> str)
	{
    
    
		BSTreeNode<string, string>* ret = dict.Find(str);
		if (ret == nullptr)
		{
    
    
			cout << "单词拼写错误,词库中没有这个单词:" << str << endl;
		}
		else
		{
    
    
			cout << str << "中文翻译:" << ret->_value << endl;
		}
	}
}

Guess you like

Origin blog.csdn.net/m0_70088010/article/details/132045226