Binary search tree (find, insert, delete, explain and realize + pictures and texts)

Table of contents

1. Binary Search Tree (BST)

  1.1 Concept of Binary Search Tree

  1.2 Binary search tree operation

        1.2.1 Search in binary search tree

        1.2.2 Insertion of binary search tree 

        1.2.3 Deletion of binary search tree

2. Implementation of binary search tree

  2.1 Basic structure of BST

  2.2 BST operation member function (non-recursive)

  2.3 BST operation member function (recursion)

3. Application of binary search tree

4. Performance Analysis of Binary Search Tree


1. Binary Search Tree (BST)

  1.1 Concept of Binary Search Tree

A binary search tree, also known as a binary sort tree, 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

Let me give a few examples to see the structure more intuitively:

 

  1.2 Binary search tree operation

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

        1.2.1 Search in binary search tree

  • Start to compare and search from the root. If it is larger than the root, go to the right to search, and if it is smaller than the root, go to the left to search.
  • The height is searched for at most times , and the value does not exist if it is empty and has not been found.

        1.2.2 Insertion of 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, find the insertion position according to the nature of the binary search tree, and insert a new node (record the parent node, and judge whether the inserted node should be in the left subtree or the right subtree of the parent node) 

        1.2.3 Deletion of binary search tree

First find out whether the element is in the binary search tree, if not, return it, otherwise the node to be deleted may be divided into the following four situations
condition:
        a. The node to be deleted has no child nodes
        b. The node to be deleted is only the left child node
        c. The node to be deleted is only the right child node
        d. The node to be deleted has left and right child nodes

It seems that there are 4 cases of deleting nodes, but in fact a, b and c can be merged, so there are only 2 cases:

        a: The node to be deleted has no children/only one child: delete the node and make the parent node point to the child node of the deleted node (no child is regarded as a child is an empty node, just point to any one)

        b: The node to be deleted has left and right children: use the replacement method to find the smallest node in the right subtree of the deleted node (the leftmost node in the right subtree), and replace the value of the smallest node with the value of the deleted node. Then delete the smallest node (at this time, the smallest node either has no children or only one child, and can be deleted directly if it meets the condition a)

2. Implementation of binary search tree

  2.1 Basic structure of BST


Node:

template<class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;

	BSTreeNode(const K& key)
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
	{}
};

BST tree:

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
    //成员函数
private:
    Node* _root=nullptr;
};

Find:

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;
		}
        //找到
		else
		{
			return true;
		}
	}

	return false;
}

  2.2 BST operation member function (non-recursive)


insert:

bool Insert(const K& key)
{
    //树为空,则直接新增结点,赋值给_root指针
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}

	Node* parent = nullptr;
	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
		{
			return false;
		}
	}

	cur = new Node(key);
    //判断是插入父结点的左部还是右部
	if (parent->_key < key)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}

	return true;
}

delete:

bool Erase(const K& key)
{
	Node* parent = nullptr;
	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
		{
			// 开始删除
			// 1、左为空
			// 2、右为空
			// 3、左右都不为空
			if (cur->_left == nullptr)
			{
                //判断下当前节点是否是_root,若是,无法用parent(当前为nullptr,防止野指针错误)
				if (cur == _root)
				{
					_root = cur->_right;
				}
				else
				{
					if (cur == parent->_left)
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
				}

				delete cur;
				cur = nullptr;
			}
			else if (cur->_right == nullptr)
			{
				if (_root == cur)
				{
					_root = cur->_left;
				}
				else
				{
					if (cur == parent->_left)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
				}

				delete cur;
				cur = nullptr;
			}
			else
			{
				//记录删除节点父节点
				Node* minParent = cur;
				//找到右子树最小节点进行替换
				Node* min = cur->_right;
				while (min->_left)
				{
					minParent = min;
					min = min->_left;
				}
				swap(cur->_key, min->_key);
				//min在父的左孩子上
				if (minParent->_left == min)
					//万一最左节点还有右孩子节点,或者是叶子也直接指右为空
					minParent->_left = min->_right;
				//min在父的右孩子上(待删除节点在根节点,最左节点为根节点的右孩子)
				else
					minParent->_right = min->_right;
				delete min;
				min == nullptr;
			}
			return true;
		}
	}
	return false;
}

Other member functions will not be shown here, here is another small tip:

default: Force the compiler to generate the default structure - the usage of C++11

BSTree()=default;

  2.3 BST operation member function (recursion)

It is still recursive, and if you understand the above non-recursive, then you can transform it into recursive. 


Find:

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;
	}
}

insert:

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;
}

delete:

bool _EraseR(Node* root, const K& key)
{
	Node* del = root;
	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
	{
		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 _EraseR(root->_right, key); 
		}
		delete del;
		return true;
				
	}

}

3. Application of binary search tree

1. K model : The K model has only the key as the key code, and only the key needs to be stored in the structure, and the key code is the value to be searched.
For example: given a word word, judge whether the word is spelled correctly , the specific method is as follows:
Construct a binary search tree with each word in the set of all words in the thesaurus as a key
Retrieve whether the word exists in the binary search tree, if it exists, the spelling is correct, if it does not exist, the spelling is wrong.
2. KV model : Each key code has a corresponding value Value, that is, a key-value pair of <Key, Value>. This method
The formula is very common in real life:
For example, the English-Chinese dictionary is the corresponding relationship between English and Chinese . You can quickly find the corresponding Chinese through English. An English word and its corresponding Chinese <word, Chinese> constitute a key-value pair;
Another example is counting the number of words . After the statistics are successful, the number of occurrences of a given word can be quickly found. The word and the number of occurrences are <word, count> to form a key-value pair.

4. Performance Analysis of Binary Search Tree

Both insertion and deletion operations must be searched first, and the 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 finding 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.

But for the same set of key codes, if the insertion order of each key code is different, a binary search tree with a different structure may be obtained:

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(N)

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

 If it degenerates into a single branch tree, the performance of the binary search tree is lost. Can that be improved? No matter what order the key codes are inserted in, can it be optimal? This requires AVL trees and red-black trees.

Guess you like

Origin blog.csdn.net/bang___bang_/article/details/130869356