C++ advanced--Binary search tree simulation implementation

Table of contents

Preface

1. Binary search tree

1. Binary search tree concept

2. Binary search tree operation

2. Binary search tree implementation

0. Define a node

1. Define a tree

2. Add, delete, modify and check

2.1. Search

2.2.Insert

2.3.Delete

2.3.1 Non-recursive deletion method

a. Only the left child--delete 14

b. Only the right child--delete 10

c. Have left and right children--Delete 8

2.3.2 Recursive deletion method

3. Binary search tree application

1.K model (solve the problem of presence and absence)

2.KV model

3. Binary search tree performance analysis

Summarize


Preface

The source code appearing in this article has been tested correctly under local vs2019 and uploaded to gitee:

https://gitee.com/a_young/binary-search-tree


1. Binary search tree

1. Binary search tree 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
  • The left and right subtrees are both binary search trees

2. Binary search tree operation

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

 1. Search in binary search tree

  1. Start comparing and searching from the root node. If the value is greater than the root node, go to the right to search. If the value is smaller than the root node, go to the left to search.
  2. The maximum number of times the height has been searched, and if an empty node is not found yet, it means that this value does not exist in the tree.

2. Insertion into binary search tree

  1. If it is an empty tree, add a new node directly and assign it to the root pointer.
  2. If the tree is not empty, find the insertion position according to the nature and insert the new node.

3. Deletion of binary search tree

  • First check whether the element is in the tree, and if it does not exist, return it. Otherwise, the nodes to be deleted are divided into the following four situations:
  1. The node to be deleted is a leaf node
  2. The node to be deleted only has the left child node
  3. The node to be deleted only has the right child node
  4. The node to be deleted has left and right child nodes

1 can be regarded as a situation of 2 and 3.

  • Only the left child node (as shown in Figure 14 above): delete the node and make the parent node of the deleted node point to the left child node of the deleted node
  • Only right child node: delete the node and make the parent node of the deleted node point to the right child node of the deleted node
  • There are left and right child nodes: first find the smallest node of the right tree (or the largest node of the left tree), use its value to fill in the deleted node, and then deal with the deletion problem of the node. Handle the code and pitfalls in detail below.

2. Binary search tree implementation

0. Define a node

template<class k>
struct BSTreeNode
{
    //三个成员
    BSTreeNode<T>* _left;
    BSTreeNode<T>* _right;
    K _key;

    //构造函数
    BSTreeNode(const k& key)
    :_left(nullptr)
    ,_right(nullptr)
    ,key(key)
    {
    }

};

1. Define a tree

Implement construction, copy construction, and destruction.

  • When constructing, use a node and initialize it to empty.
  • Copy structure, please note here that it must be a deep copy, as shallow copies are prone to wild pointers.
  • When destructing, just use post-order recursive deletion.
template<class k>
class BSTree
{
   typedef BSTreeNode<T> Node;
   public:
   /* BSTree()
    :_root(nullptr)
    {}
    */
    BSTree() = default; //指定强制生成默认构造
    //拷贝构造
    BSTree(const BSTree<K> & t)
    {
       _root = copy(t._root);
    }

    //跟前序创建类似 后序回来才链接
    Node* copy(Node * root)
    {
       if(root == nullptr)
            return nullptr;

       Node * new_root = new Node(root ->key);
       new_root ->_left = Copy(root->_left);
       nre_root->_right = Copy(root->_right);
       return new_root;
       
    }
    //析构
    ~BSTree()
    {
        //使用后序递归
       Destory(_root);
    
    }

     void Destroy(Node * root)
     {
         if(root == nullprt)
            return ;
         Destroy(root->left);
         Destroy(root->right);
         delete root;
      }

    //成员函数
    //实现增删改查 protected封装
    //赋值
    BSTree<k>& operator=(BSTree<k> t)
    {
         swap(_root,t._root);
         return *this;   
    }
   private:
    
    Node * _root = nullptr; 

2. Add, delete, modify and check

2.1. Search

bool Find(const k& key)
{
    Node * cur = _root;
    while(cur)
    {
        if(cur->_ley <key)
            cur = cur->right;
        else if(cur->_key >key)
            cur = cur ->right;
        else
            {
                return true;
            }

    return false;
}
  

2.2.Insert

//非递归解法
bool Insert(const k& key)
{
    //如果是一个空树
    if(_root == nullptr)
      {
           _root = new Node(key);        
            return true;
      }
      
    Node * parent = nullptr;
    Node * cur = _root;
    while(cur)
    {
        if (cur->_key < key)
		{
			parent = cur;
            //注意这里,cur更新前不是空,更新后才是空,更新前还可以访问right
		    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;
}
    
            
        

Here we use recursion to insert, a very clever example of using references . Suppose I want to insert a 2 in the picture above. First find the appropriate position. The right of 1 is empty. A new Node is created here with a value of 2 and returned at the same time. At this time, the Node here is the natural upper layer of the stack. Root->right in the frame, perfect link .

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


bool InsertR(const k& key)
{
    return _InsertR(_root, key);
}

2.3.Delete

2.3.1 Non-recursive deletion method

bool _Erase(Node * root,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->leftt;
        }

        //找到了,开始删除
        else
        {
            //a.只有左孩子
            //b.只有右孩子
            //c.左右孩子都有
            
            return true;

        }

}
bool Erase(const K& key)
{
   return  bool _Erase(Node * root,const K& key);
}

a. Only the left child--delete 14

Search through the above code and find 14. At this time, parent points to 10, cur points to 14, 14 has only the left child, link it, the left child of 14 becomes the right child of 10, delete 14.

if(cur ->_right  == nullptr)
 {
       if(parent ->_left == cur)
            parent ->_left = cur->left;
        
        else if(parent ->_right == cur)
            parent ->_right = cur->_right;

        delete cur;
}
        return true;
    

But there are special cases: if there is a tree with only two nodes, and the node to be deleted is the root node , then its parent node parent is a null pointer, and the deletion cannot be judged. So we need to make a judgment here. If it is, update root and delete cur.

if(cur == _root)
{
    _root = cur->_left;
}

b. Only the right child--delete 10

10 is found through search, cur points to 10, parent points to 8, and deletion starts.

if(cur == _root)
{
    _root = cur->_right;
}


else if( parent ->_left == cur)
{
        parent->left = cur->_right;
}

else if( parent ->_right == cur)
{
        parent ->right = cur->_right;
 }


delete cur;
return true;

c. Have left and right children--Delete 8

At this time, you need to find the rightmost node (largest) in the left tree or the leftmost node (smallest) in the right tree in the subtree to replace the root node. As follows, we use the leftmost node in the right tree to replace it. At this time, the leftmost node may also have a right child , but it definitely does not have a left child. So we need to perform orphaning, that is, we need to find the father of this leftmost node .

Suppose you delete the root node 8 in the tree . The tree 10 has a left node 9, and 9 has its own right child 9.5. So first find the leftmost node 9 of the right tree, its father 10, and its own right child 9.5, which is 10 The left child . 9 replaces 8, and finally the structure at the minRight position is deleted .

You also need to be careful to avoid pitfalls here. If 8 is deleted here, minRight points to 10, there is no minRight->left, and pminRight is empty (so pminRight cannot be empty at the beginning, and needs to be cur). And 10 only has its own right child 14, which will become the right child of 8 , so you need to make judgment when entrusting an orphan.

/*Node* pminRight = nullptr;
Node * minRight = cur->_right;
while(minRight->_left)
{
    pminRight = minRight;
    minRight = minRight->left;
}

    cur ->_key = minRight ->_key;
    pminRight ->left = minRight->_right;

    delete minRight;
 */ 


Node * pminRight = cur;
Node * minRight = cur->_right;
while(minRight->_left)
{
    pminRight = minRight;
    minRight = minRight ->left;
}

    cur ->_key = minRight->_key;
    //进行托孤
    if(pminRight->left == minRight->right)
        pminRight->left = minRight->right;
    else if(pminRight->right == minRight->left)
        pminRight->right = minRight->right;

    delete minRight;

                                          

2.3.2 Recursive deletion method

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;

		// 开始准备删除
		if (root->_right == nullptr)
		{
		    root = root->_left;
		}
		else if (root->_left == nullptr)
		{
			root = root->_right;
		}
		else
        {
            //这里找左树的最右节点
            Node * maxleft = root->_left;
            while(maxleft->_right)
            {
                maxleft = maxleft ->right;
            }

 
            swap(root->_key, maxleft->_key);
            return _EraseR(root->_left,key);
          }
        delete del;
        return true;
}

3. Binary search tree application

1.K model (solve the problem of presence and absence)

The K model only has key as a keyword, and only the key is stored in the structure. The key code is the value that needs to be searched.

  • For example, the access control system: there is personal information in the chip, and it is searched in the database based on the personal information. If it is there, it is passed.

2.KV model

Each key code key has a corresponding value, that is, the key-value pair of <key, value>. This method is very common in life.

  • For example, the English-Chinese dictionary is the correspondence between Chinese and English. You can quickly find the corresponding Chinese through English. English words and their corresponding Chinese <word, chinese> form a key-value pair.
  • Count the number of words. When the statistics is over, you can quickly find the number of occurrences of a given word. <key,count> forms a key-value pair.                
  • //改造二叉搜索树为kv结构
    template<class k, class v>
    struct BSTNode
    {
        BSTNode(const k& key = k(), const v&value = v())
        :_pLeft(nullptr),_pRight(nullptr),_key(key),_value(vaule)
        {}
    
        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;
     }
     }
    }
    void TestBSTree()
    {
     // 统计水果出现的次数
     string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", 
    "苹果", "香蕉", "苹果", "香蕉" };
     BSTree<string, int> countTree;
     for (const auto& str : arr)
     {
     // 先查找水果在不在搜索树中
     // 1、不在,说明水果第一次出现,则插入<水果, 1>
     // 2、在,则查找到的节点中水果对应的次数++
     //BSTreeNode<string, int>* ret = countTree.Find(str);
     auto ret = countTree.Find(str);
     if (ret == NULL)
     {
     countTree.Insert(str, 1);
     }
     else
     {
     ret->_value++;
     }
     }
     countTree.InOrder();
    }
                                                              

3. Binary search tree performance analysis

Both insertion and deletion must be searched first, and the efficiency of search represents the performance of each operation in the binary search tree.

For a binary search tree with n nodes, if the search probability of each element is equal, the virus search is a function of the depth of the binary tree. The more nodes there are, the more comparisons there are.

Optimal: complete binary tree, O(logN)

Worst: single O(N)

If it degenerates into a single branch, the performance of the binary search tree is very poor, so AVL trees and red-black trees are used, which will be introduced in subsequent articles.


Summarize

This article mainly introduces the implementation and application of binary search trees. The technology is limited. Please correct me if there are any errors.

Guess you like

Origin blog.csdn.net/jolly0514/article/details/132153682