[C++] Search the underlying implementation of binary tree

Table of contents

1. Concept

2. Implement analysis

1. Insert

(1.) Non-recursive version 

 (2.) Recursive version

 2. Print search binary tree

3. Find function

(1.) Non-recursive version

(2.) Recursive version

4. Delete functions (important and difficult) 

Analysis of common mistakes to ensure you learn

(1.) Delete the target without left or right children

(2.) Delete the target and have only one child

(3.) Delete target, have two children

code

(1.) Non-recursive version 

(2.) Recursive version

5. Destructor

6.Copy construction 

 Three, application

 4. Defects and optimization of searching binary trees

5. Code summary

Conclusion


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

Why is it also called a binary sorting tree? When the tree is traversed in level order, it is in ascending order.

2. Implement analysis

Experimental example:

int a[] = {8, 3, 1, 10, 6, 4, 5, 7, 14, 13}; 

1. Insert

(1.) Non-recursive version 

a. Start comparing and searching 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.
b. Search the height at most times. If it reaches empty, it has not been found yet. This value does not exist.

 It’s relatively simple, just put the code directly here:

template <class K>
class BinarySeaTree_node
{
	typedef BinarySeaTree_node<K> BST_node;
public:
	BinarySeaTree_node(const K& val)
		: _val(val)
		,_left(nullptr)
		,_right(nullptr)
	{}

	K _val;
	BST_node* _left;
	BST_node* _right;
};


template <class T>
class BSTree
{
	typedef BinarySeaTree_node<T> BST_node;
private:
	BST_node* root = nullptr;

public:
	bool Insert(const T& val)
	{
		BST_node* key = new BST_node(val);
		BST_node* cur = root;
		BST_node* parent = nullptr;
		while (cur)
		{
			if (key->_val < cur->_val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key->_val > cur->_val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return 0;
			}
		}

		// 查询好位置后,建立链接
		if (!root)
		{
			root = key;
			return 0;
		}

		if (key->_val > parent->_val)
		{
			parent->_right = key;
		}
		else
		{
			parent->_left = key;
		}
		return 1;
	}
};

 (2.) Recursive version

There's a lot going on here , everyone, pay attention!!!

bool Re_Insert(const T& val){  return Re_Insert_table(root, val);}

	bool Re_Insert_table(BST_node*& node, const T& val)
	{
		if (node == nullptr)
		{
			node = new BST_node(val);
			return 1;
		}

		if (val < node->_left)
		{
			return Re_Insert_table(node->_left, val);
		}
		else if (val > node->_right)
		{ 
			return Re_Insert_table(node->_right, val);
		}
		else
		{
			return 0;
		}
	}

To facilitate your understanding, I will give you a recursive expansion diagram.

 2. Print search binary tree

 

The specific process of insertion is as follows:
a. If the tree is empty, add a node directly and assign it to the root pointer.
b. The tree is not empty. Find the insertion position according to the properties of the binary search tree and insert the new node.

Here we only share the code: 

void Print_table() { Re_Print(root); }

	void Re_Print(const BST_node* node)
	{
		if (node == nullptr)
			return;
		Re_Print(node->_left);
		cout << node->_val << " ";
		Re_Print(node->_right);
	}

3. Find function

Idea: In fact, there is no idea. If the node is smaller than the parent node, look for the left side, otherwise look for the right side. 

(1.) Non-recursive version

BST_node* Find(const T& val)
	{
		//直接跟寻找位置一样
		BST_node* parent = nullptr;
		BST_node* cur = root; // 以返回cur的方式返回

		while (cur)   // 非递归版本
		{
			if (val < cur->_val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (val > cur->_val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return cur;
			}
		}
		return cur;
	}

(2.) Recursive version

BST_node* Re_Find(const T& val){   return Re_Find_table(root, val); }
	
	BST_node* Re_Find_table(BST_node* node, const T& val)
	{
		if (node == nullptr)
			return nullptr;

		if (val < node->_val)
		{
			return Re_Find_table(node->_left, val);
		}
		else if (val > node->_val)
		{
			return Re_Find_table(node->_right, val);
		}
		else
		{
			return node;
		}
	}

4. Delete functions (important and difficult) 

We briefly searched for ideas, as follows:

But these ideas are only general directions, and there are many pitfalls hidden in them. Next, I will take you to analyze these error-prone points .

First, query the target:

This is relatively simple and will not be explained here. 

       //首先寻找到目标,并且记录到parent
		BST_node* parent = nullptr;
		BST_node* cur = root;
		while (cur)
		{
			if (val < cur->_val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (val > cur->_val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				break;
			}
		}
		if (!cur)
		{
			return 0;
		}

Analysis of common mistakes to ensure you learn

(1.) Delete the target without left or right children

 

(2.) Delete the target and have only one child

General idea: 

 But, there is a loophole!

promise:

(3.) Delete target, have two children

 Okay, now that the appetizers are served, let’s take a look at this time’s main course.

code

(1.) Non-recursive version 

bool Erase(const T& val)
	{
		//首先寻找到指定值,并且记录到parent
		BST_node* parent = nullptr;
		BST_node* cur = root;
		while (cur)
		{
			if (val < cur->_val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (val > cur->_val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				break;
			}
		}
		if (!cur)
		{
			return 0;
		}

		// 查询成功,开始删除
		if (!cur->_left && !cur->_right) // cur没有左右孩子
		{   // 当要删除目标是根
			if (cur == root)
			{
				root = nullptr;
				delete cur;
			}
			// 判断cur是左右孩子
			else if (cur->_val < parent->_val)
			{
				parent->_left = nullptr;
				delete cur;
			}
			else
			{
				parent->_right = nullptr;
				delete cur;
			}
			return 1;
		}
		else if (!cur->_left || !cur->_right)  // 只有一个孩子
		{
			if (!parent)  // 判断是否是目标是根
			{
				root = cur->_left != nullptr ? cur->_left : cur->_right;
				delete cur;
			}
			// 判断cur为啥孩子
			else if (cur->_val < parent->_val) // 左侧
			{
				parent->_left = cur->_left != nullptr ? cur->_left : cur->_right;
				delete cur;
			}
			else                          // 右侧
			{
				parent->_right = cur->_left != nullptr ? cur->_left : cur->_right;
				delete cur;
			}
		}
		else   // 有2个孩子
		{  // 使用左侧最大的孩子来领养
			// 寻找左侧最大
			BST_node* maxnode = cur->_left;
			BST_node* max_parent = cur;
			while (maxnode->_right)
			{
				max_parent = maxnode;
				maxnode = maxnode->_right;
			}
			
			// 现在又进入一种特殊情况,1.max_parent就没进入循环,2.进入了循环
			if (max_parent == cur)
			{
				max_parent->_left = maxnode->_left;
			}
			else
			{
				max_parent->_right = maxnode->_left;
			}
			// 值转移
			cur->_val = maxnode->_val;
			delete maxnode;
		}
		return 1;
	}

(2.) Recursive version

bool Re_Erease( const T& val)
	{
		return Re_Erease_table(root, val);
	}

	bool Re_Erease_table(BST_node*& node, const T& val)
	{
		// 首先我们先找到值
		if (node == nullptr)
		{
			return 0; // 如果访问到了空,则说明删除失败,原因是:不存在
		}

		if (val < node->_val)
		{
			return Re_Erease_table(node->_left, val);
		}
		else if (val > node->_val)
		{
			return Re_Erease_table(node->_right, val);
		}
		else
		{
			// 开始删除目标数据。方法如下;
			// 1. 就按照非递归的思路,不用改多少代码 
			// 2. 使用递归方法,优势就是代码简洁
			// 这里使用方法2
			BST_node* del = node;  // 保存每次访问的对象,如果是目标,就备份好了
			if (node->_left == nullptr)
			{
				node = node->_right;
			}
			else if (node->_right == nullptr)
			{
				node = node->_left;
			}
			else
			{
				//处理左右都有孩子的目标
				// 左侧查找最大值,右侧查找最小值
				BST_node* max_node = node->_left;
				while (max_node->_right)
				{
					max_node = max_node->_right;
				}
				// 完成循环后,max_node最多有左孩子,然后数据交换,我们以目标左侧树为起点
				// 再次递归删除替换数据。
				swap(max_node->_val, node->_val);
				return Re_Erease_table(node->_left, val); //已经完成删除,就直接退出,以免触发删除delete
			}			
			//处理前两种情况
			delete del;
		}
	}

5. Destructor

Idea:

~BSTree()
	{  
		Distroy_Re(root);
		root = nullptr;   
	}
void Distroy_Re(BST_node*& node) // 我们采用递归删除
	{
		if (node == nullptr)
			return ;
		// 先处理左右孩子
		Distroy_Re(node->_left);
		Distroy_Re(node->_right);
		delete node;
		node = nullptr;
	}

6.Copy construction 

    // 我们实现了拷贝构造,默认构造函数则不会生成 
	// 解决方案:1.实现构造函数 2.使用default关键字,强制生成默认构造
	BSTree()                 
	{
	}
	 // BSTree() = default

     BSTree(const BSTree& Tree) // 拷贝构造
	{
		root = copy(Tree.root);
	}

	BST_node* copy(BST_node* root)
	{
		if (root == nullptr)
			return nullptr;

		BST_node* new_node = new BST_node(root->_val);
		new_node->_left = copy(root->_left);
		new_node->_right = copy(root->_right);
		return new_node;
	}

 Three, application

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 what needs to be searched.
value .
     For example: given a word word, determine whether the word is spelled correctly . The specific method is as follows: use each word in the collection of all words in the vocabulary as a key, build a binary search tree, and retrieve whether the word is spelled correctly 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 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. English words and their 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 (this is relatively simple, just modify it)

 4. Defects and optimization of searching binary trees

For a binary search tree with n nodes, if the probability of searching for each element is equal, then 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 the order of insertion of each key is different, binary search trees with different structures may be obtained:

Worst case: N

Average case: O(logN)

Problem: If it degenerates into a single tree, the performance of the binary search tree is lost. Can it be improved so that the performance of the binary search tree can be optimized no matter what order the key codes are inserted? Then the AVL tree and red-black tree we will learn in subsequent chapters can be used.

5. Code summary

namespace key
{
template <class K>
class BinarySeaTree_node
{
	typedef BinarySeaTree_node<K> BST_node;
public:
	BinarySeaTree_node(const K& val)
		: _val(val)
		,_left(nullptr)
		,_right(nullptr)
	{}

	K _val;
	BST_node* _left;
	BST_node* _right;
};

template <class T>
class BSTree
{
public:
	typedef BinarySeaTree_node<T> BST_node;

	// 我们实现了拷贝构造,默认构造函数则不会生成 
	// 解决方案:1.实现构造函数 2.使用default关键字,强制生成默认构造
	BSTree()
	{
	}

	// BSTree() = default
   BSTree(const BSTree& Tree) // 拷贝构造
   {
	   root = copy(Tree.root);
   }

   BSTree<T>& operator=(BSTree<T> t)
   {
	   swap(root, t.root);
	   return *this;
   }

   BST_node* copy(BST_node* root)
   {
	   if (root == nullptr)
		   return nullptr;

	   BST_node* new_node = new BST_node(root->_val);
	   new_node->_left = copy(root->_left);
	   new_node->_right = copy(root->_right);
	   return new_node;
   }

   bool Re_Insert(const T& val) { return Re_Insert_table(root, val); }
   void Re_Print() { Re_Print_table(root); }
   bool Re_Erease(const T& val) { return Re_Erease_table(root, val); }
   BST_node* Re_Find(const T& val) { return Re_Find_table(root, val); }

   bool Insert(const T& val)
   {
	   BST_node* key = new BST_node(val);
	   BST_node* cur = root;
	   BST_node* parent = nullptr;
	   while (cur)
	   {
		   if (key->_val < cur->_val)
		   {
			   parent = cur;
			   cur = cur->_left;
		   }
		   else if (key->_val > cur->_val)
		   {
			   parent = cur;
			   cur = cur->_right;
		   }
		   else
		   {
			   return 0;
		   }
	   }

	   // 查询好位置后,建立链接
	   if (!root)
	   {
		   root = key;
		   return 0;
	   }

	   if (key->_val > parent->_val)
	   {
		   parent->_right = key;
	   }
	   else
	   {
		   parent->_left = key;
	   }
	   return 1;
   }

   BST_node* Find(const T& val)
   {
	   //直接跟寻找位置一样
	   BST_node* parent = nullptr;
	   BST_node* cur = root; // 以返回cur的方式返回

	   while (cur)   // 非递归版本
	   {
		   if (val < cur->_val)
		   {
			   parent = cur;
			   cur = cur->_left;
		   }
		   else if (val > cur->_val)
		   {
			   parent = cur;
			   cur = cur->_right;
		   }
		   else
		   {
			   return cur;
		   }
	   }
	   return cur;
   }

   bool Erase(const T& val)
   {
	   //首先寻找到指定值,并且记录到parent
	   BST_node* parent = nullptr;
	   BST_node* cur = root;
	   while (cur)
	   {
		   if (val < cur->_val)
		   {
			   parent = cur;
			   cur = cur->_left;
		   }
		   else if (val > cur->_val)
		   {
			   parent = cur;
			   cur = cur->_right;
		   }
		   else
		   {
			   break;
		   }
	   }
	   if (!cur)
	   {
		   return 0;
	   }

	   // 查询成功,开始删除
	   if (!cur->_left && !cur->_right) // cur没有左右孩子
	   {   // 当要删除目标是根
		   if (cur == root)
		   {
			   root = nullptr;
			   delete cur;
		   }
		   // 判断cur是左右孩子
		   else if (cur->_val < parent->_val)
		   {
			   parent->_left = nullptr;
			   delete cur;
		   }
		   else
		   {
			   parent->_right = nullptr;
			   delete cur;
		   }
		   return 1;
	   }
	   else if (!cur->_left || !cur->_right)  // 只有一个孩子
	   {
		   if (!parent)  // 判断是否是目标是根
		   {
			   root = cur->_left != nullptr ? cur->_left : cur->_right;
			   delete cur;
		   }
		   // 判断cur为啥孩子
		   else if (cur->_val < parent->_val) // 左侧
		   {
			   parent->_left = cur->_left != nullptr ? cur->_left : cur->_right;
			   delete cur;
		   }
		   else                          // 右侧
		   {
			   parent->_right = cur->_left != nullptr ? cur->_left : cur->_right;
			   delete cur;
		   }
	   }
	   else   // 有2个孩子
	   {  // 使用左侧最大的孩子来领养
		   // 寻找左侧最大
		   BST_node* maxnode = cur->_left;
		   BST_node* max_parent = cur;
		   while (maxnode->_right)
		   {
			   max_parent = maxnode;
			   maxnode = maxnode->_right;
		   }

		   // 现在又进入一种特殊情况,1.max_parent就没进入循环,2.进入了循环
		   if (max_parent == cur)
		   {
			   max_parent->_left = maxnode->_left;
		   }
		   else
		   {
			   max_parent->_right = maxnode->_left;
		   }
		   // 值转移
		   cur->_val = maxnode->_val;
		   delete maxnode;
	   }
	   return 1;
   }

   ~BSTree()
   {
	   Distroy_Re(root);
	   root = nullptr;
   }

protected:
	bool Re_Insert_table(BST_node*& node, const T& val)
	{
		if (node == nullptr)
		{
			node = new BST_node(val);
			return 1;
		}

		if (val < node->_val)
		{
			return Re_Insert_table(node->_left, val);
		}
		else if (val > node->_val)
		{
			return Re_Insert_table(node->_right, val);
		}
		else
		{
			return 0;
		}
	}

	void Re_Print_table(const BST_node* node)
	{
		if (node == nullptr)
			return;
		Re_Print_table(node->_left);
		cout << node->_val << " ";
		Re_Print_table(node->_right);
	}

	BST_node* Re_Find_table(BST_node* node, const T& val)
	{
		if (node == nullptr)
			return nullptr;

		if (val < node->_val)
		{
			return Re_Find_table(node->_left, val);
		}
		else if (val > node->_val)
		{
			return Re_Find_table(node->_right, val);
		}
		else
		{
			return node;
		}
	}

	bool Re_Erease_table(BST_node*& node, const T& val)
	{
		// 首先我们先找到值
		if (node == nullptr)
		{
			return 0; // 如果访问到了空,则说明删除失败,原因是:不存在
		}

		if (val < node->_val)
		{
			return Re_Erease_table(node->_left, val);
		}
		else if (val > node->_val)
		{
			return Re_Erease_table(node->_right, val);
		}
		else
		{
			// 开始删除目标数据。方法如下;
			// 1. 就按照非递归的思路,不用改多少代码 
			// 2. 使用递归方法,优势就是代码简洁

			// 这里使用方法2
			BST_node* del = node;  // 保存每次访问的对象,如果是目标,就备份好了
			if (node->_left == nullptr)
			{
				node = node->_right;
			}
			else if (node->_right == nullptr)
			{
				node = node->_left;
			}
			else
			{
				//处理左右都有孩子的目标
				// 左侧查找最大值,右侧查找最小值
				BST_node* max_node = node->_left;
				while (max_node->_right)
				{
					max_node = max_node->_right;
				}
				// 完成循环后,max_node最多有左孩子,然后数据交换,我们以目标左侧树为起点
				// 再次递归删除替换数据。
				swap(max_node->_val, node->_val);
				return Re_Erease_table(node->_left, val); //已经完成删除,就直接退出,以免触发删除delete
			}
			// 查找到删除数据
			delete del;
		}
	}

	void Distroy_Re(BST_node*& node) // 我们采用递归删除
	{
		if (node == nullptr)
			return;
		// 先处理左右孩子
		Distroy_Re(node->_left);
		Distroy_Re(node->_right);
		delete node;
		node = nullptr;
	}
private:
	BST_node* root = nullptr;
 };
}

Conclusion

   That’s it for this section. Thank you for browsing. If you have any suggestions, please leave them in the comment area. If you bring something to your friends, please leave a like. Your likes and attention will become a blog post . Main motivation for creation

Guess you like

Origin blog.csdn.net/qq_72112924/article/details/132890262