C++13: Searching a Binary Tree

Table of contents

Search Binary Tree Concept

Simulation implementation of searching binary tree

 Insert function implementation

 Insert function implementation (recursive)

Find function implementation

 Delete function implementation

 Delete function implementation (recursive)

Inorder traversal implementation

 copy constructor implementation

 Destructor implementation

 assignment overloading


When we first learned the binary tree, the first thing we came into contact with was the heap, but the structure was not a real binary tree. Later, we realized the real structural binary tree with the help of the linked list. The binary tree not only made things difficult for us on the OJ problem, but In fact, after a certain node logic is realized, a highly efficient data structure can also be formed. This binary tree is a search binary tree.

Search Binary Tree Concept

 For a search binary tree, the values ​​stored in its nodes follow the following rules

  • The value of the right subtree node must be greater than the value of the current node
  • The value of the left subtree node must be less than the value of the current node

 And for its structure

  • Searching a binary tree does not allow re-insertion of values ​​that already exist inside the binary tree
  • The left and right subtrees of the search binary tree must also be search binary trees
  •  The order of data insertion is different, and the shape of the search binary tree will be different 

 Such a structure makes the efficiency of searching the binary tree very high in the case of balance

  • 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: log_2 N

 

 But when the inserted data is ordered, the search binary tree will degenerate into a single fork tree

  • 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

 Its overall tree structure and logic are almost as described above, and the next step is the simulation implementation.

Simulation implementation of searching binary tree

 Insert function implementation

 First define the structure of the node, we add the template.

	template<class K>
	struct BSNode
	{

		BSNode<K>* _left;
		BSNode<K>* _right;
		K _key;

		BSNode(K val = K())
			:_left(nullptr)
			, _right(nullptr)
			, _key(val)
		{}
	};

 Then we implement the insertion function, the logic is not difficult, the summary is as follows:

  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 nature of the binary search tree, and insert a new node
  3. Insertion has success and failure. If the inserted value already exists in the tree, the insertion will fail.
  4. Insert the left subtree or the right subtree with the K value as the judgment condition, insert the right subtree larger than K, insert the left subtree smaller than K
  5. An additional parent node needs to be created to link the nodes.
		//以K值为判定条件插入左子树或者右子树,比K大插右子树,比K小插左子树
		//若遇到相等的,插入失败,不插

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

			Node* cur = _root;
			Node* parent = cur;

			while (cur)
			{
				//比K小,往左子树走
				if (val < cur->_key)
				{
					parent = cur;
					cur = cur->_left;

				}
				//比K大,往右子树走
				else if (val > cur->_key)
				{
					parent = cur;
					cur = cur->_right;
				}
				//相等,插入不了,返回一个false
				else
				{
					return false;
				}
			}

			//跳出while时,也就是遇到空了,新建一个节点赋值然后链接节点
			if (parent->_key < val)
                parent->_right = new Node(val);

			else if (parent->_key > val)
                parent->_left = new Node(val);

			return true;
		}

 Insert function implementation (recursive)

 The insertion of the binary tree can also be recursive, after all, the structure of the binary tree itself is suitable for recursion

Recursive logic:

  • Termination condition: if it encounters empty, then it is in place, or an empty node, create a node to prepare for linking
  • What this recursion should do: Check whether the currently inserted value is greater than K, greater than the right tree, less than the left tree
  • Returned information: Same as the loop version, return true or false

 But there is a problem here. It is easy to implement recursion inside the class, just pass the root node directly , but its access qualifier is private, which means that we cannot call this recursive insertion outside the class , so we need additional implementation A GetRoot or private inline recursive function to get the root.

 Among them, the formal parameter passed must be a reference , because of the recursive stack frame problem, if you want to link pointers between nodes on the way of recursion, you need an additional parent node, but it will make the program more complicated. A reference will The problem can be solved very cleverly, because the reference passed down from the previous layer is the alias of the parent node, and a direct link is enough.

Similarly, it can be achieved with the help of secondary pointers, but it is not as good as references.

		bool InsertR(const K& val)
		{
			return _InsertR(_root, val);
		}

private:
		bool _InsertR(Node*& root,const K& val)
		{
			//假如走到了空,那么就是到位了,或者是一个空树,创建节点准备链接
			if (root == nullptr)
			{
				root = new Node(val);
				return true;
			}
			//大于,向右走
			if (val > root->_key)
				return _InsertR(root->_right, val);
			else if (val < root->_key)
				return _InsertR(root->_left, val);
			else
				return false;
		}

Find function implementation

The logic of search is no different from that of insertion, and a recursive version can also be implemented, so the logic will not be listed here.

		//查找函数,找到了返回true,找不到返回false
		bool Find(const K& val)
		{
			Node* cur = _root;

			while (cur)
			{
				//大于就向右边找
				if (val > cur->_key)
					cur = cur->_right;
				//小于就向左边查找
				else if (val < cur->_key)
					cur = cur->_left;
				//相等就找着了
				else
					return true;
			}
			//没找着
			return false;		
		}

 Delete function implementation

 There are many cases of deletion and some complexity, the logic is as follows:

        There are three cases of deletion

  1. Delete a node with children, if its left is empty, give the right to the parent node,
  2. If the right side is empty, give the left child to the parent node
  3.  Deleting a node with multiple children is more complicated and needs to be replaced and deleted. During the period, you also need to pay attention to the deletion of the root. Find the smallest node of the right subtree, that is, the leftmost node of the right subtree and exchange it with the deleted node. , and then delete the leftmost node of the right subtree.

Situation 1,2 Schematic diagram

 Schematic diagram of case 3, which shows the logic of the replacement method, and additional processing is required to delete the root node.

 Then the code is implemented as follows

		bool erase(const K& val)
		{
			Node* cur = _root;
			Node* parent = nullptr;


			while (cur)
			{
				if (val > cur->_key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (val < cur->_key)
				{
					parent = cur;
					cur = cur->_left;
				}
				//相等就找到了,找到后进行删除
				else
				{
					//左孩子为空,右孩子为空,以及左右都不为空,其中这三种情况下,还需要特殊处理删除根节点的时候
					//其中只有一个孩子的情况下,只需要托孤即可
					if (cur->_left == nullptr)
					{
						//如果左孩子为空,那么先判断一下是不是根节点。
						if (cur == _root)
						{
							//是根节点,直接让cur-的右边做根
							_root = cur->_right;
						}
						//删的不是根,直接托孤给parent
						else
						{
							//需要被托孤的是到
							if (parent->_left == cur)
								parent->_left = cur->_right;
							else
								parent->_right = cur->_right;
						}

						delete cur;
						cur = nullptr;
					}
					//有左孩子,没右孩子
					else if (cur->_right == nullptr)
					{
						//如果是根,就把根给到左孩子
						if (cur == _root)
							_root = cur->_left;
						else
						{
							if (parent->_left == cur)
								parent->_left = cur->_left;
							else
								parent->_right = cur->_left;
						}
						delete cur;
						cur = nullptr;
					}
					//左右孩子都不是空,需要替换删除,期间也需要注意删除根的情况
					else
					{
						//找右子树的最小节点,也就是右子树的最左边
						//先找最小值,有根判根


						//留一个parent,以防止min后面还有节点
						Node* minparent = cur;
						Node* min = cur->_right;

						while (min->_left)
						{
							minparent = min;
							min = min->_left;
						}

							cur->_key = min->_key;
							//给完跟在删除min之前还要把min后面的节点都街上
							if (minparent->_left == min)
								minparent->_left = min->_right;
							else
								minparent->_right = min->_right;

						delete min;
						min = nullptr;

					}

					return true;
				}
			}
			//没找着
			return false;
		}

 Delete function implementation (recursive)

 The main logic is not much different from the loop version. It should be noted that the part deleted by the replacement method can be cleverly called again to delete the replaced node.

		bool _earseR(Node*& root, const K& val)
		{
			if (root == nullptr)
				return false;


			if (root->_key > val)
			{
				return _earseR(root->_left, val);
			}
			else if(root->_key < val)
			{
				return _earseR(root->_right, val);
			}
			else//找到了
			{
				//叶子节点无需特殊处理,在处理单子树的过程中顺带解决了
				//没有左孩子,那么一定有右孩子,直接链接右孩子到父节点,这种情况是根节点的完全没有左子树,也就是直接更新根
				//用一个节点保存前根,
				Node* del = root;
				if (root->_left == nullptr)
				{
					root = root->_right;
				}
				else if (root->_right == nullptr)
				{
					root = root->_left;
				}
				else//交换,但是走的是递归,可以很巧妙的交换两个节点的值,然后再走递归去删掉替死鬼。
				{
					Node* min = root->_right;
					while (min->_left)
						min = min->_left;
					swap(root->_key, min->_key);

					return _earseR(root->_right, val);
				}
				delete del;
				del = nullptr;

				return true;
			}

		}


		Node* _root = nullptr;

	};

Inorder traversal implementation

 There is nothing to say about this, except remember that an additional recursive function needs to be embedded.

		void InOrder()
		{
			Node* root = GetRoot();
			_inorder(root);
		}
		void _inorder(Node* _root)
		{
			if (_root == nullptr)
			{
				return;
			}

			_inorder(_root->_left);
			cout << _root->_key << " ";
			_inorder(_root->_right);

		}

 copy constructor implementation

 Since the insertion order of searching the binary tree has an effect on the shape, we use recursion to copy its nodes one by one.

		//拷贝构造
		//拷贝构造走一个前序遍历构建,走一个递归。每前序遍历一个节点就新建一个节点
		BSTree(const BSTree<K>& t2)
		{
			_root = Copy(t2._root);
		}

		// 1.终止条件?走到空结束
		// 2.这次递归应该完成的任务?创建节点,
		// 3.返回的信息?返回节点的指针,空反指针并将节点链接起来
		//
		Node* Copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;

			//这层的任务,新建节点。
			Node* newRoot = new Node(root->_key);
			newRoot->_left = Copy(root->_left);
			newRoot->_right = Copy(root->_right);

			return newRoot;

		}

 Destructor implementation

		//析构函数
		~BSTree()
		{
			Destory();
			_root = nullptr;
		}
		//销毁
		void Destory()
		{
			while (_root)
				erase(_root->_key);
		}

 assignment overloading

 Like vector, we use the shaking method to achieve assignment overloading.

		BSTree<K>& operator = (const BSTree<K> t)
		{
			if (t == this)
				return *this;

			swap(_root, t._root);
			return *this;

		}

Then the above is a search binary tree with the most basic functions. Next, we try to realize its KV structure. 

The KV structure, which is similar to a Pair structure, binds a Key to the corresponding Val, and finds the corresponding Val by comparing the Key.

template<class K, class V>
	class KVTree
	{
	public:
		typedef KVNode<K,V> Node;

		bool Insert(const K& key,const V& val)
		{
			if (_root == nullptr)
			{
				_root = new Node(key,val);
				return true;
			}

			Node* cur = _root;
			Node* parent = cur;

			while (cur)
			{
				//比K小,往左子树走
				if (key < cur->_key)
				{
					parent = cur;
					cur = cur->_left;

				}
				//比K大,往右子树走
				else if (key > cur->_key)
				{
					parent = cur;
					cur = cur->_right;
				}
				//相等,插入不了,返回一个false
				else
				{
					return false;
				}
			}

			//跳出while时,也就是遇到空了,新建一个节点赋值然后链接节点
			if (parent->_key < key)
			{
				parent->_right = new Node(key,val);

			}
			else if (parent->_key > key)
			{
				parent->_left = new Node(key,val);
			}
			return true;
		}
		Node* Find(const K& key)
		{
			Node* cur = _root;

			while (cur)
			{
				//大于就向右边找
				if (key > cur->_key)
				{
					cur = cur->_right;
				}
				//小于就向左边查找
				else if (key < cur->_key)
				{
					cur = cur->_left;
				}
				//相等就找着了
				else
				{
					return cur;
				}
			}
			//没找着
			return nullptr;
		}

		void InOrder()
		{
			Node* root = GetRoot();
			_inorder(root);
		}
		void _inorder(Node* _root)
		{
			if (_root == nullptr)
				return;

			_inorder(_root->_left);
			cout << _root->_key << ":"<< _root->_val<<endl;
			_inorder(_root->_right);

		}
	private:

		Node* GetRoot()
		{
			return _root;
		}
		Node* _root = nullptr;

	};

Test it with the help of a count of the number of fruit occurrences

	void TextKVtree1()
	{
		KVTree<string, int> KV;
		string str[] ={ "菠萝","荔枝","草莓","菠萝","菠萝" ,"西瓜" ,"草莓" ,"橙子" ,"荔枝" ,"牛油果" ,"西瓜" ,"西瓜" };

		for (auto& e : str)
		{
			KVNode<string, int>* ret = KV.Find(e);
			if (ret)
			{
				ret->_val++;
			}
			else
			{
				KV.Insert(e,1);
			}
		}

		KV.InOrder();
	}

 The above is the basic implementation of a binary search tree

 

Guess you like

Origin blog.csdn.net/m0_53607711/article/details/130038732