Simulation implementation of Binary Search Tree

Preface

        Why learn binary search trees? Because the bottom layer of set  and map is actually a binary search tree, but it has been subjected to some special processing. Understanding the underlying implementation of the binary search tree will help us better understand the principles of map and set. Binary search tree is also called binary sorting tree. It is either an empty tree or has the following properties: 1. If its left subtree is not empty, then all nodes on the left subtree are smaller than the root. If If its right subtree is not empty, then all nodes on its right subtree are greater than the root. Its left and right subtrees are also binary search trees. Let’s take a look!

Table of contents

 Implementation code of Binary Search Tree

1. Operation of binary search tree 

        1.1 Searching in Binary Search Tree

        1.2 Insertion into binary search tree

        1.3 Deletion of binary search tree

        1.4Inorder

3. Application of binary search tree

        3.1K model

        3.2KV model

4. Disadvantages of Binary Search Tree

        Its implementation is relatively simple. I believe you can learn it by reading this blog. 

Binary search tree diagram: 

 

 Implementation code of Binary Search Tree

namespace qyy
{
	template<class T>
	struct BSTNode//二叉搜索树的节点
	{
		typedef BSTNode<T> Node;
		BSTNode(const T&key = T())//构造函数初始化二叉搜索树节点,
			:_left(nullptr)
			,_right(nullptr)
			,_key(key)
		{ }
	public:
		Node* _left;
		Node* _right;
		T _key;
	};
	template<class T>
	class BSTree
	{
		typedef BSTNode<T> Node;
	public:
		BSTree(const T& key = T())//构造函数只需要将根节点初始化为空就好了
		{
			_root = nullptr;
		}
		//无需析构函数
		bool Insert(const T&key)//插入key值
		{
			if (_root == nullptr)//如果为空树
			{
				_root = new Node(key);//申请新的节点
				_root->_left = _root->_right = nullptr;//初始化
				return true;
			}
			Node* cur = _root;
			Node* parent = cur;//保存cur的父亲节点
			//这里通过双指法进行插入
			while (cur)//遍历搜索树找要插入的位置
			{
				if (cur->_key > key)//key的值小于当前节点的值,去节点的左边子树找位置
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_key < key)//key的值大于当前节点的值,去节点的右边子树找位置
				{
					parent = cur;
					cur = cur->_right;
				}
				else//key的值与当前节点的值相同无法插入,结束插入,因为二叉搜索树额key值不能是相同的,这是由它的性质决定的
				{
					return false;
				}
			}
			Node* tmp = new Node(key);//申请新的节点
			if (parent->_key > key)//通过比较节点的值和插入的key值来确认插入位置
				parent->_left = tmp;
			else
				parent->_right = tmp;
			return true;
		}
		bool Find(const T& key)
		{
			if (_root == nullptr)//如果根节点为空,找到key值
				return false;
			Node* cur = _root;
			while (cur)//在二叉搜索树中对应的节点的值
			{
				if (cur->_key > key)//key小于当前节点的值,到左子树中找
					cur = cur->_left;
				else if (cur->_key < key)//key大于当前节点的值,到右子树中找
					cur = cur->_right;
				else
					return true;//key的值等于当前节点的值,找到了返回true
			}
			return false;
		}
		void _Inorder(Node*root)//二叉搜索树的中序遍历
		{
			if (root == nullptr)
				return;
			_Inorder(root->_left);
			cout << root->_key<<" ";
			_Inorder(root->_right);
		}
		void Inorder()
		{
			_Inorder(_root);//调用中序遍历,进行遍历因为如果直接写在类的外面获取不到根节点的值就无法进行传参,所以这要写一个中序遍历的子函数,传参调用进行中序遍历
			cout << endl;
		}
		bool Erase(const T&key)//查找并删除key值
		{
			if (_root == nullptr)//如果根节点为空不需要查找
				return false;

			Node* cur = _root;
			Node* parent = cur;//用来保存cur的父亲节点的值
			if (cur->_key == key && cur->_left == nullptr && cur->_right == nullptr)//左右子树为空才相等,且要key的值等于当前节点的值。
			{
				delete cur;//删除根节点
				_root = nullptr;
				return true;
			}
			//查找并删除采用双指针的方法
			while (cur)//遍历查找并删除val值的节点
			{
				if (cur->_key > key)//在左子树中找
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_key < key)//在右子树中查找
				{
					parent = cur;
					cur = cur->_right;
				}
				else//找到含key值的节点
				{
					//删除节点
					if (cur->_left == nullptr  )//如果左子树为空
					{
						if (parent->_key > cur->_key)//判断父亲节点和要删除的节点的关系,
							parent->_left = cur->_right;//断开节点的链接
						else
							parent->_right = cur->_right;
						
						delete cur;//删除节点
						cur = nullptr;
						return true;
					}
					else if (cur->_right == nullptr )//如果右子树都为空
					{
						if (parent->_key > cur->_key)//判断父亲节点和要删除的节点的关系,
							parent->_left = cur->_left;//断开节点的链接
						else
							parent->_right = cur->_left;
						delete cur;//删除节点
						cur = nullptr;
						return true;
					}
					else //左右孩子都存在
					{
						Node* tmp = cur->_right;//如果要删除的节点的左右孩子都存在的话,就要考虑,找到这个节点的左子树中val值做大的节点
						//或者右子树中val值最小的节点,交换它们的值,然后删除左子树中这个最大的值的节点,或者右子树中最小的值的这个节点
						//采用双指针法来保存右子树中的最小值节点的父节点
						Node* tmpParent = nullptr;
						while (tmp->_left)//在节点的右子树中找最左节点,也就是右子树中节点值最小的节点
						{
							tmpParent = tmp;//保存父亲节点
							tmp = tmp->_left;//右子树中的最左孩子
						}
						cur->_key = tmp->_key;//用右子树中节点的最小值来替换要删除的节点
						if (tmpParent == nullptr)//说明要删除的节点的右节点就是要替换的节点
						{
							cur->_right = tmp->_right;//最小值所在的节点的右节点不为空链接cur和最小节点的右节点
						}
						else
						{
							if (tmpParent->_key > tmp->_key)//判断最小节点和它父节点的关系,它在父节点的左边还是右边
								tmpParent->_left = nullptr;
							else
								tmpParent->_right = nullptr;
						}
						delete tmp;//删除这个替换的节点,也就是右子树中最小的节点
						tmp = nullptr;
						return true;
					}
				}
			}
			return false;
		}
	private:
		Node* _root;
	};
}

         Test code:


#include<iostream>
using namespace std;
#include"BSTree.hpp"
void Test1()
{
	qyy::BSTree<int> b1;
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	for (auto& e : a)
		b1.Insert(e);
	b1.Inorder();
	cout<< b1.Find(20);
}
void Test2()
{
	qyy::BSTree<int> b2;
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	for (auto& e : a)
		b2.Insert(e);
	
	for (auto& e : a)
		b2.Erase(e);
	b2.Inorder();
}
int main()
{
	//Test1();
	Test2();
	return 0;
}

1. Operation of binary search tree 

       1.1 Searching in Binary Search Tree

        Start searching from the root node. If the value is smaller than the root node, search in the left subtree. If the value is larger than the root node, search in the right subtree. The maximum number of search heights is that if the height is reached and the value is not found, the value is empty.

                Code: 

	bool Find(const T& key)
	{
		if (_root == nullptr)//如果根节点为空,找到key值
			return false;
		Node* cur = _root;
		while (cur)//在二叉搜索树中对应的节点的值
		{
			if (cur->_key > key)//key小于当前节点的值,到左子树中找
				cur = cur->_left;
			else if (cur->_key < key)//key大于当前节点的值,到右子树中找
				cur = cur->_right;
			else
				return true;//key的值等于当前节点的值,找到了返回true
		}
		return false;//没有找到
	}

        1.2 Insertion into binary search tree

        Insertion process: 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, as shown in the figure: 

 

        Note that only key values ​​that are not in the binary search tree can be inserted . This is determined by the nature of the binary search tree. The places inserted into the tree are empty positions.

        Code:

        bool Insert(const T&key)//插入key值
		{
			if (_root == nullptr)//如果为空树
			{
				_root = new Node(key);//申请新的节点
				_root->_left = _root->_right = nullptr;//初始化
				return true;
			}
			Node* cur = _root;
			Node* parent = cur;//保存cur的父亲节点
			//这里通过双指法进行插入
			while (cur)//遍历搜索树找要插入的位置
			{
				if (cur->_key > key)//key的值小于当前节点的值,去节点的左边子树找位置
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_key < key)//key的值大于当前节点的值,去节点的右边子树找位置
				{
					parent = cur;
					cur = cur->_right;
				}
				else//key的值与当前节点的值相同无法插入,结束插入,因为二叉搜索树额key值不能是相同的,这是由它的性质决定的
				{
					return false;
				}
			}
			Node* tmp = new Node(key);//申请新的节点
			if (parent->_key > key)//通过比较节点的值和插入的key值来确认插入位置
				parent->_left = tmp;
			else
				parent->_right = tmp;
			return true;
		}

 Here, the double pointer method is used to insert, the parent pointer is used to save the parent node of cur, and finally the key value is inserted through judgment.

        1.3 Deletion of binary search tree

        The deletion of the binary search tree can be carried out in several situations. If only the root node is left, delete the root node and then leave the root node empty. As shown in the picture: 

        If you delete a leaf node, determine whether it is to the left or right of the parent node, and leave the left or right side of the parent node blank. Then delete it.

 

        If the left subtree of the deleted node is empty, you only need to determine the relationship between the parent node and it, then link its right subtree to the left or right side of the parent node, and then delete it.

 

         If the right subtree of the deleted node is empty, you only need to determine the relationship between the parent node and it, then link its left subtree to the left or right of the parent node, and then delete it. (Same as above, no further explanation here)

        If both the left and right nodes are empty, you need to find the smallest node of its right subtree to replace its value and then delete it. However, you need to note that if the left subtree of the smallest node is empty, you need to make a judgment and then delete the smallest node. The parent node of the node is linked to the parent node of the smallest node, and then the smallest node is deleted.

        

 

         Code:

 

bool Erase(const T& key)//查找并删除key值
{
	if (_root == nullptr)//如果根节点为空不需要查找
		return false;

	Node* cur = _root;
	Node* parent = cur;//用来保存cur的父亲节点的值
	if (cur->_key == key && cur->_left == nullptr && cur->_right == nullptr)//左右子树为空才相等,且要key的值等于当前节点的值。
	{
		delete cur;//删除根节点
		_root = nullptr;
		return true;
	}
	//查找并删除采用双指针的方法
	while (cur)//遍历查找并删除val值的节点
	{
		if (cur->_key > key)//在左子树中找
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_key < key)//在右子树中查找
		{
			parent = cur;
			cur = cur->_right;
		}
		else//找到含key值的节点
		{
			//删除节点
			if (cur->_left == nullptr)//如果左子树为空
			{
				if (parent->_key > cur->_key)//判断父亲节点和要删除的节点的关系,
					parent->_left = cur->_right;//断开节点的链接
				else
					parent->_right = cur->_right;

				delete cur;//删除节点
				cur = nullptr;
				return true;
			}
			else if (cur->_right == nullptr)//如果右子树都为空
			{
				if (parent->_key > cur->_key)//判断父亲节点和要删除的节点的关系,
					parent->_left = cur->_left;//断开节点的链接
				else
					parent->_right = cur->_left;
				delete cur;//删除节点
				cur = nullptr;
				return true;
			}
			else //左右孩子都存在
			{
				Node* tmp = cur->_right;//如果要删除的节点的左右孩子都存在的话,就要考虑,找到这个节点的左子树中val值做大的节点
				//或者右子树中val值最小的节点,交换它们的值,然后删除左子树中这个最大的值的节点,或者右子树中最小的值的这个节点
				//采用双指针法来保存右子树中的最小值节点的父节点
				Node* tmpParent = nullptr;
				while (tmp->_left)//在节点的右子树中找最左节点,也就是右子树中节点值最小的节点
				{
					tmpParent = tmp;//保存父亲节点
					tmp = tmp->_left;//右子树中的最左孩子
				}
				cur->_key = tmp->_key;//用右子树中节点的最小值来替换要删除的节点
				if (tmpParent == nullptr)//说明要删除的节点的右节点就是要替换的节点
				{
					cur->_right = tmp->_right;//最小值所在的节点的右节点不为空链接cur和最小节点的右节点
				}
				else
				{
					if (tmpParent->_key > tmp->_key)//判断最小节点和它父节点的关系,它在父节点的左边还是右边
						tmpParent->_left = nullptr;
					else
						tmpParent->_right = nullptr;
				}
				delete tmp;//删除这个替换的节点,也就是右子树中最小的节点
				tmp = nullptr;
				return true;
			}
		}
	}
	return false;
}

        1.4Inorder

        In-order traversal of a binary search tree cannot be implemented directly. It needs to be implemented by calling a sub-function, because the root node cannot be passed to the function outside the class (the root node is a private member of the class).

        Code:

        void _Inorder(Node*root)//二叉搜索树的中序遍历
		{
			if (root == nullptr)
				return;
			_Inorder(root->_left);
			cout << root->_key<<" ";
			_Inorder(root->_right);
		}
		void Inorder()
		{
			_Inorder(_root);//调用中序遍历,进行遍历因为如果直接写在类的外面获取不到根节点的值就无法进行传参,所以这要写一个中序遍历的子函数,传参调用进行中序遍历
			cout << endl;
		}

3. Application of binary search tree

        3.1K model 

        The K model only has Key as the key code . Only K needs to be stored in the structure. The key code is the value to be searched.

        For example, swiping your ID card when entering a train station is to determine whether you are taking a train at this station today.

        The specific method is as follows: Create a binary search tree through the ticket purchase ID number of the station on a certain day.

        Search whether the ID number exists in the binary search tree to determine whether to take the bus here on that day.

        3.2KV model

        Each key K has its corresponding value Value  , that is, the key-value pair of <Key, Value>. This method is very common in reality. For example: Retrieve book information by book number in the library. You can quickly find the book information you need by using the book number. In book retrieval, the book number and the corresponding book information constitute a key-value pair.

        For example, in an English-Chinese dictionary to query the Chinese meaning through English, the English word and the corresponding Chinese form a key-value pair.

        For example:

//改造的二叉搜索树 KV模型
#include<string>
#include<vector>
#include<iostream>
using namespace std;
template<class K,class V>
struct BSTNode//节点的定义
{
	BSTNode(const K& key = K(),const V&value =V())//构造函数初始化节点
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
		,_value(value)
	{ }
	BSTNode<K,V>* _left;
	BSTNode<K,V>* _right;//
	K _key;
	V _value;
};
template<class K,class V>
class BSTree
{
public:
	typedef BSTNode<K,V> node;
	BSTree()//构造函数初始化根节点为空
		:_root(nullptr)
	{ }
	bool Insert(const K& key,const V&value)//插入节点
	{
		if (_root == nullptr)//树为空
		{
			//初始化根节点
			_root = new node(key,value);
			return true;
		}
		//树不为空,插入节点,按照左小右大
		node* cur = _root;
		node* parent = cur;//用来保存上一层的节点,便于插入
		while (cur)
		{
			if (cur->_key > key)//走左边
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)//走右边
			{
				parent = cur;
				cur = cur->_right;
			}
			else//key值重复插入失败直接退出
			{
				return false;
			}
		}
		//判断key值应该插在左边还是右边
		node* newnode = new node(key,value);
		if (parent->_key > key)//插在左边
			parent->_left = newnode;
		else
			parent->_right = newnode;
		return true;
	}
	//删除节点与节点的位置有很大的关系
	//如果是删除末尾的节点直接删除就好
	//如果删除的节点缺少左子树或者右子树,只需要记录另外一个子树的连接然后删除节点之后重新连接就好了
	//如果删除的节点左右子树都存在,就要找到一个能够替换它的节点,然后交换数据删除替换的节点就好了
	bool erase(const K& key)//删除节点
	{
		if (_root == nullptr)//树为空
			return false;
		node* cur = _root;
		node* parent = cur;
		if (cur->_left == nullptr && cur->_right == nullptr)//左右子树为空才相等
		{
			delete cur;//删除根节点
			_root = nullptr;
			return true;
		}
		while (cur)//查找并删除key节点的值
		{
			if (cur->_key > key)//走左边
			{
				parent = cur;//记录cur的上一层节点
				cur = cur->_left;
			}
			else if (cur->_key < key)//走右边找
			{
				parent = cur;
				cur = cur->_right;
			}
			else//找到节点
			{
				//删除节点
				if (cur->_left == nullptr)//左节点为空
				{

					if (cur->_key > parent->_key)
						parent->_right = cur->_right;//cur是parent的右子树
					else
						parent->_left = cur->_right;;//链接cur的右子树	
					delete cur;
				}
				else if (cur->_right == nullptr)//右子树为空
				{
					if (cur->_key > parent->_key)//cur是parent的右子树
						parent->_right = cur->_left;
					else
						parent->_left = cur->_left;
					delete cur;
				}
				else//左右子树都不为空
				{
					//找到右子树的最左节点然后交换 key 值
					node* tmp = cur->_right;
					node* parentTmp = cur;
					while (tmp->_left)
					{
						parentTmp = tmp;//保存tmp的上一层
						tmp = tmp->_left;
					}
					cur->_key = tmp->_key;
					if (cur->_right == tmp && tmp->_right == nullptr)//说明cur的右子树仅仅只有一个节点
					{
						cur->_right = nullptr;
					}
					else
					{
						if (tmp->_key >= parentTmp->_key)//parentTmp的右节点置空
							parentTmp->_right = tmp->_right;
						else
							parentTmp->_left = tmp->_right;//左节点置空
					}
					delete tmp;//删除tmp
				}
				return true;
			}
		}
		return false;
	}
	node* Find(const K& key)
	{
		if (_root == nullptr)//树为空
			return nullptr;
		node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)//去左边找
				cur = cur->_left;
			else if (cur->_key < key)//去右边找
				cur = cur->_right;
			else//找到了
				return cur;
		}
		return nullptr;
	}
	void _InOrder(node* root)//中序遍历
	{
		//node* root = _root;
		if (root == nullptr)
			return;//递归结束
		_InOrder(root->_left);//左子树
		cout << root->_key << "  ";
		_InOrder(root->_right);//右子树
	}
	void InOrder()//中序遍历无法直接遍历节点,因为this指针是隐含的无法显示调用必须借助于其他子函数
	{
		_InOrder(_root);
		cout << endl;
	}

private:
	node* _root;//根节点
};
void Test()//英汉词典模型
{
	BSTree<string, string> Bt;
	Bt.Insert("sort", "排序");
	Bt.Insert("function", "函数");
	Bt.Insert("English", "英文");
	Bt.Insert("china", "中国");
	BSTNode<string,string>*p=Bt.Find("sort");
	if (p)//指针不为空
		cout<< p->_value<<endl;
	//Bt.InOrder();
}
void Test1()//统计物品出现的次数
{
	BSTree<string,int> Bt;
	string s1[] = { "西瓜" ,"黄瓜" , "西瓜" , "黄瓜" , "西瓜" , "香蕉" , "西瓜" , "苹果" , "草莓" , "西瓜" , "冬瓜" , "西瓜" , "西瓜" , "冬瓜" };
	for (auto& e : s1)
	{
		BSTNode<string, int>* p =Bt.Find(e);
		if (p==nullptr)
		{
			Bt.Insert(e,1);//如果第一次插入Value值等于1
		}
		else
		{
			p->_value++;//不是第一次插入Value值++
		}
	}
	BSTNode<string, int>* p = Bt.Find("西瓜");
	if (p)
		cout <<"西瓜:"<< p->_value;
}

4. Disadvantages of Binary Search Tree

        If the inserted data is ordered or nearly ordered , then the binary search tree is a straight line or approximately a straight line. It only has the right subtree or the left subtree. At this time, its search efficiency will be very slow, O ( N) , if unordered data is inserted into a binary search tree, its search efficiency will be very high, and it only needs to be done many times of its height, which is O(logN) ; so it also has certain flaws. , and map and set achieve efficient search by solving its defects.

        I would like to correct my bad writing.

Guess you like

Origin blog.csdn.net/m0_68641696/article/details/131288716