【二叉搜索树】二叉搜索树的用法以及实现

图片名称

博主首页: 有趣的中国人

专栏首页: C++进阶

博主会持续更新

    本篇文章主要讲解 二叉搜索树 的相关内容


      1. 二叉搜索树

      1.1 二叉搜索树概念


      二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

      • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
      • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
      • 它的左右子树也分别为二叉搜索树。

      在这里插入图片描述

      1.2 二叉搜索树操作


      下图是一个二叉搜索树:
      图片名称

      二叉搜索树的查找find

      • 从根开始比较,查找,比根则往边走查找,比根则往边走查找;
      • 最多查找高度次,走到空,还没找到,这个值不存在。

      二叉搜索树的插入Insert


      插入的具体过程如下:

      • 树为空,则直接新增节点,赋值给root指针;
      • 树不空,按二叉搜索树性质查找插入位置,插入新节点。
        在这里插入图片描述

      二叉搜索树的删除Erase

      首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情

      1. 要删除的结点无孩子结点
      2. 要删除的结点只有左孩子结点
      3. 要删除的结点只有右孩子结点
      4. 要删除的结点有左、右孩子结点

      看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程
      如下:

      • 情况2:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点–直接删除;
      • 情况3:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点–直接删除;
      • 情况4:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题–替换法删除。

      在这里插入图片描述


        2. 二叉搜索树的实现


        定义节点类BSTNode

        对于二叉树的每个节点,都应该有一个值_key,和指向左右孩子的指针_left、_right,以及一个构造函数。

        template<class K>
        struct BSTNode
        {
        	typedef BSTNode<K> Node;
        	K _key;
        	Node* _left;
        	Node* _right;
        
        	BSTNode(const K& key)
        		:_key(key)
        		,_left(nullptr)
        		,_right(nullptr)
        	{}
        };
        

        定义二叉搜索树类成员变量

        二叉搜索树中应该有一个节点类型的指针_root

        template<class K>
        class BSTree
        {
        public:
            typedef BSTNode<K> Node;
        private:
        	Node* _root = nullptr;
        };
        

        插入操作Insert


        刚才说了具体的思路,这边直接写代码了:

        bool Insert(const K& key)
        {
        	// 树为空
        	if (_root == nullptr)
        	{
        		Node* newnode = new Node(key);
        		_root = newnode;
        		return true;
        	}
        	// 树不为空
        	Node* cur = _root;
        	Node* parent = nullptr;
        	while (cur)
        	{
        		// 大于往右走
        		if (key > cur->_key)
        		{
        			parent = cur;
        			cur = cur->_right;
        		}
        		// 小于往左走
        		else if (key < cur->_key)
        		{
        			parent = cur;
        			cur = cur->_left;
        		}
        		// 等于不插入
        		else
        		{
        			return false;
        		}
        	}
        	// 创建新节点插入
        	Node* newnode = new Node(key);
        	if (key > parent->_key)
        	{
        		parent->_right = newnode;
        	}
        	else
        	{
        		parent->_left = newnode;
        	}
        	return true;
        }
        

        中序遍历InOrder

        中序遍历要传入根节点_root,但是这里的根节点是私有成员变量,不能在类外进行访问,(最好不要用友元),我们可以这样写:

        publicvoid InOrder()
        	{
        		_InOrder(_root);
        		cout << endl;
        	}
        private:
        	void _InOrder(Node* root)
        	{
        		if (root == nullptr)
        		{
        			return;
        		}
        		_InOrder(root->_left);
        		cout << root->_key << " ";
        		_InOrder(root->_right);
        	}
        
        • 测试一下:
        int main()
        {
        	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
        	BSTree<int> t1;
        	for (auto e : a)
        	{
        		t1.Insert(e);
        	}
        	t1.InOrder();
        	return 0;
        }
        

        在这里插入图片描述


        查找操作Find

        查找操作就很简单了:

        Node* Find(const K& key)
        {
        	Node* cur = _root;
        	Node* parent = nullptr;
        	while (cur)
        	{
        		// 大于往右走
        		if (key > cur->_key)
        		{
        			parent = cur;
        			cur = cur->_right;
        		}
        		// 小于往左走
        		else if (key < cur->_key)
        		{
        			parent = cur;
        			cur = cur->_left;
        		}
        		// 等于返回
        		else
        		{
        			return cur;
        		}
        	}
        	return nullptr;
        }
        

        删除操作Erase

        bool Erase(const K& key)
        {
        	Node* cur = _root;
        	Node* parent = nullptr;
        	while (cur)
        	{
        		// 大于往右走
        		if (key > cur->_key)
        		{
        			parent = cur;
        			cur = cur->_right;
        		}
        		// 小于往左走
        		else if (key < cur->_key)
        		{
        			parent = cur;
        			cur = cur->_left;
        		}
        		// 等于删除
        		else
        		{
        			// 左孩子为空,父亲的指针指向它的右孩子
        			if (cur->_left == nullptr)
        			{
        				// 删除根节点时
        				if (cur == _root)
        				{
        					_root = cur->_right;
        				}
        				// 不是根节点
        				else
        				{
        					if (parent->_left == cur)
        					{
        						parent->_left = cur->_right;
        					}
        					else
        					{
        						parent->_right = cur->_left;
        					}
        				}
        				delete cur;
        				cur = nullptr;
        				return true;
        			}
        			// 右孩子为空,父亲的指针指向它的左孩子
        			else if (cur->_right == nullptr)
        			{
        				// 删除根节点时
        				if (cur == _root)
        				{
        					_root = cur->_right;
        				}
        				// 不是根节点
        				else
        				{
        					if (parent->_left == cur)
        					{
        						parent->_left = cur->_right;
        					}
        					else
        					{
        						parent->_right = cur->_left;
        					}	
        				}
        				delete cur;
        				cur = nullptr;
        				return true;
        			}
        			// 左右孩子都不为空
        			else
        			{
        				// 找右子树最小的值
        				Node* RightMinNode = cur->_right;
        				Node* RightMinNodeParent = cur;
        				while (RightMinNode->_left)
        				{
        					RightMinNodeParent = RightMinNode;
        					RightMinNode = RightMinNode->_left;
        				}
        				swap(RightMinNode->_key, cur->_key);
        				if (RightMinNodeParent->_left == RightMinNode)
        				{
        					RightMinNodeParent->_left = RightMinNode->_right;
        				}
        				else
        				{
        					RightMinNodeParent->_right = RightMinNode->_right;
        				}
        				delete RightMinNode;
        				RightMinNode = nullptr;
        				return true;
        			}
        		}
        	}
        	return false;
        }
        
        • 测试一下:
        int main()
        {
        	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
        	BSTree<int> t1;
        	for (auto e : a)
        	{
        		t1.Insert(e);
        	}
        	t1.InOrder();
        	for (auto e : a)
        	{
        		t1.Erase(e);
        		t1.InOrder();
        	}
        	return 0;
        }
        

        在这里插入图片描述

        这里要注意的细节很多:

        1. 当删除的节点的左子树或者右子树为空,但是这个节点就是root的时候,这个时候要进行特殊判断,因为我们定义的 Node* parent = nullptr,会造成空指针解引用的问题,例如这种情况:

        在这里插入图片描述

        1. 当左右子树的指针都不为空的时候,定义 RightMinNodeParent的时候,不能初始化为空,要是cur,因为当删除root的时候还是会造成空指针解引用的问题。
          在这里插入图片描述
        2. 交换之后,一定要判断,不能直接让 RightMinNodeParent->_right = RightMinNode->_right;,一般情况下,删除非root的节点,都没啥问题,但是如果删除root的节点,例如这种情况,就有坑了,所以一定要判断:
          在这里插入图片描述

        LeetCode 450. 删除二叉搜索树中的节点

        LeetCode上有一道关于二叉搜索树删除节点的题目:

        这是链接:450. 删除二叉搜索树中的节点

        这是博主的解答,和上面的思路一样,只是返回值不同而已:

        /**
         * Definition for a binary tree node.
         * struct TreeNode {
         *     int val;
         *     TreeNode *left;
         *     TreeNode *right;
         *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
         *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
         *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
         * };
         */
        class Solution 
        {
        public:
            typedef TreeNode Node;
            TreeNode* deleteNode(Node* root, int key) 
            {
                Node* cur = root;
                Node* parent = nullptr;
                while (cur)
                {
                    if (key < cur->val)
                    {
                        parent = cur;
                        cur = cur->left;
                    }
                    else if (key > cur->val)
                    {
                        parent = cur;
                        cur = cur->right;
                    }
                    else
                    {
                        if (cur->left == nullptr)
                        {
                            if (cur == root)
                            {
                                root = cur->right;
                            }
                            else
                            {
                                if (parent->left == cur)
                                {
                                    parent->left = cur->right;
                                }
                                else
                                {
                                    parent->right = cur->right;
                                }
                                delete cur;
                                cur = nullptr;
                            }
                            return root;
                        }
                        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;
                            }
                            return root;
                        }
                        else
                        {
                            Node* RightMinNode = cur->right;
                            Node* RightMinNodeParent = cur;
                            while (RightMinNode->left)
                            {
                                RightMinNodeParent = RightMinNode;
                                RightMinNode = RightMinNode->left; 
                            }
                            swap(RightMinNode->val, cur->val);
                            if (RightMinNodeParent->left == RightMinNode)
                            {
                                RightMinNodeParent->left = RightMinNode->right;
                            }
                            else
                            {
                                RightMinNodeParent->right = RightMinNode->right;
                            }
                            return root;
                        }
                    }
                }
                return root;
            }
        };
        

        在这里插入图片描述


          3. 二叉搜索树的应用

          1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
          • 比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
            • 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树;
            • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
          1. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:
          • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英
            文单词与其对应的中文<word, chinese>就构成一种键值对;

          • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出
            现次数就是<word, count>就构成一种键值对。

          3.1 英汉词典简易实现

          英汉词典简易实现

          这个KV模型只要区别就是每个节点和插入的时候的区别,其他区别不大,我就展示节点和插入时候的区别:

          template<class K, class V>
          struct BSTNode
          {
          	typedef BSTNode<K, V> Node;
          	K _key;
          	V _val;
          	Node* _left;
          	Node* _right;
          
          	BSTNode(const K& key, const V& val)
          		:_key(key)
          		,_val(val)
          		, _left(nullptr)
          		, _right(nullptr)
          	{}
          };
          
          template<class K, class V>
          class BSTree
          {
          public:
          	typedef BSTNode<K, V> Node;
          	bool Insert(const K& key, const V& val)
          	{
          		// 树为空
          		if (_root == nullptr)
          		{
          			Node* newnode = new Node(key, val);
          			_root = newnode;
          			return true;
          		}
          		// 树不为空
          		Node* cur = _root;
          		Node* parent = nullptr;
          		while (cur)
          		{
          			// 大于往右走
          			if (key > cur->_key)
          			{
          				parent = cur;
          				cur = cur->_right;
          			}
          			// 小于往左走
          			else if (key < cur->_key)
          			{
          				parent = cur;
          				cur = cur->_left;
          			}
          			// 等于不插入
          			else
          			{
          				return false;
          			}
          		}
          		// 创建新节点插入
          		Node* newnode = new Node(key, val);
          		if (key > parent->_key)
          		{
          			parent->_right = newnode;
          		}
          		else
          		{
          			parent->_left = newnode;
          		}
          		return true;
          	}
          }
          
          • 测试一下:
          void test_KV_BSTree()
          {
          	BSTree<string, string> dict;
          	dict.Insert("hello", "你好");
          	dict.Insert("world", "世界");
          	dict.Insert("humorous", "幽默");
          	string str;
          	while (cin >> str)
          	{
          		if (dict.Find(str) != nullptr)
          		{
          			cout << dict.Find(str)->_val << endl;
          		}
          		else
          		{
          			cout << "未查询到此单词,如有需要,请更新词库" << endl;
          		}
          	}
          }
          

          在这里插入图片描述

          3.2 统计单词次数

          统计单词次数

          这里的BSTree代码和上面的简易实现英汉词典方法类似,只是InOrder稍微变一下,要输出一个val

          void test_KV_BSTree2()
          {
          	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
          "苹果", "香蕉", "苹果", "香蕉" , "草莓", "草莓" };
          	BSTree<string, int> countTree;
          	for (auto& str : arr)
          	{
          		auto ret = countTree.Find(str);
          		if (ret == nullptr)
          		{
          			countTree.Insert(str, 1);
          		}
          		else
          		{
          			++ret->_val;
          		}
          	}
          	countTree.InOrder();
          }
          

          在这里插入图片描述

          • 38
            点赞
          • 23
            收藏
            觉得还不错? 一键收藏
          • 17
            评论

          “相关推荐”对你有帮助么?

          • 非常没帮助
          • 没帮助
          • 一般
          • 有帮助
          • 非常有帮助
          提交
          评论 17
          添加红包

          请填写红包祝福语或标题

          红包个数最小为10个

          红包金额最低5元

          当前余额3.43前往充值 >
          需支付:10.00
          成就一亿技术人!
          领取后你会自动成为博主和红包主的粉丝 规则
          hope_wisdom
          发出的红包
          实付
          使用余额支付
          点击重新获取
          扫码支付
          钱包余额 0

          抵扣说明:

          1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
          2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

          余额充值