C++之搜索二叉树


前言

本文介绍了二叉搜索树的相关概念,主要介绍了如何使用和实现搜索二叉树以及搜索二叉树具体的例题练习。


一、二叉搜索树概念

二叉搜索树也称为二叉排序树。
二叉树有以下性质
1.空树是二叉搜索树;
2.二叉搜索树的非空左子树的所有节点小于根节点的值;
3.二叉搜索树的非空右子树的所有节点大于根节点的值;
4.二叉搜索树的左右子树也是二叉搜索树。

二、二叉搜索树操作

1.增

  1. 树为空,直接新增节点,赋值给root指针;
  2. 树不为空,根据二叉树的性质查找插入位置,插入新节点。

2.删

  1. 查找该节点是否在树中,如果不在则不进行操作;
  2. 如果在树中,则要删除该节点,分以下四种情况:
    a.无孩子,则直接删除该节点;
    b.要删除的节点无左孩子,则删除该节点,同时让该节点的父节点指向该节点的右孩子;
    c.要删除的节点无右孩子,则删除该节点,同时让该节点的父节点指向该节点的左孩子;
    d.左右孩子都有,则让该节点的右孩子的最左节点(右孩子的最小孩子)赋值给该节点,然后在右子树中删除最左节点即可。(替换删除法
    在这里插入图片描述

3.查

  1. 从根节点出发,比根节点的值大的去它的右子树找,比根节点小的去它的左子树找;
  2. 最多查找高度次(二叉树的高度),如果走到空还没找到,说明该树没有这个值。

搜索二叉树不支持修改。

三、二叉搜索树实现

1.查

		pnode Find(K k)//查
		{
			pnode cur = _pnode;
			while (cur)
			{
				if (cur->_k > k)
				{
					cur = cur->left;
				}
				else if (cur->_k < k)
				{
					cur = cur->right;
				}
				else
				{
					return cur;
				}
			}
			return cur;
		}

2.增

		bool insert(K k)//增
		{
			pnode newnode = new node(k);
			//如果该树没有节点就直接插入新节点为根节点
			if (_pnode == nullptr)
			{
				_pnode = newnode;
				return true;
			}
			pnode cur = _pnode;
			pnode parent = cur;//记录当前节点的父节点,因为如果要插入就要插入在当前节点的父节点的左右孩子处
			while (cur)
			{
				parent = cur;
				if (cur->_k > k)
				{
					cur = cur->_left;
				}
				else if (cur->_k < k)
				{
					cur = cur->_right;
				}
				else//如果该节点已经在树中存在就插入失败
				{
					return false;
				}
			}
			//插入元素
			if (parent->_k > k) parent->_left = newnode;
			else parent->_right = newnode;
			return true;
		}

3.删

		bool erase(K k)//删
		{
			//树为空删除失败
			if (_pnode == nullptr) return false;
			pnode cur = _pnode;
			pnode parent = cur;
			while (cur)
			{
				if (cur->_k > k)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_k < k)
				{
					parent = cur;
					cur = cur->_right;
				}
				else//删除元素(cur->_k == k)
				{
					//无孩子
					if (cur->_left == nullptr && cur->_right == nullptr)
					{
						if (cur == parent->_left) parent->_left = nullptr;
						else parent->_right = nullptr;
						delete cur;
						cur = nullptr;
					}
					//有左孩子,没有右孩子
					else if (cur->_left && cur->_right == nullptr)
					{
						if (cur == parent->_left) parent->_left = cur ->_left;
						else parent->_right = cur ->_left;
						delete cur;
						cur = nullptr;
					}
					//有右孩子,没有左孩子
					else if (cur->_left == nullptr && cur->_right)
					{
						if (cur == parent->_left) parent->_left = cur->_right;
						else parent->_right = cur->_right;
						delete cur;
						cur = nullptr;
					}
					//两个孩子都有(替换删除法)
					else if (cur->_left&&cur->_right)
					{
						pnode pR = cur->_right;//cur的右子树的根节点
						pnode pRL = cur->_right;//cur的右子树的最左节点
						while (pRL->_left)
						{
							pRL = cur->_left;
						}
						K temp = pRL->_k;
						erase(temp);
						cur->_k = temp;
					}
					return true;
				}
			}
			//树中没有该元素删除失败
			return false;
		}

模拟实现搜索二叉树的完整代码在文末。

四、二叉搜索树应用

1.K模型

只有K作为关键码,结构中只需要存储K值即可,关键码即为需要搜索到的值

例题,判断一个单词拼写是否正确?

  1. 将词库中所有单词集合中每一个单词作为K值,构建一棵二叉树;
  2. 在这个二叉树中检索是否存在这个单词,如果存在说明拼写正确,如果不存在则说明拼写错误。

2.KV模型

每一个关键码K都有一个对应的V值,即<K,V>键值对。

例如,英汉词典,通过英文可以快速找到中文,英文与中文可以构成一个键值对<English,Chinese>。
例如,计算单词出现的次数,用单词可以找到它出现的次数,单词与它出现的次数可以构成一个键值对<word,count>。

英汉词典的例子:

namespace kv
{
	template<class K,class V>
	struct BSTnode
	{
		BSTnode(const K& k = K(),const V& v = V())
		:_k(k)
		, _v(v)
		, _left(nullptr)
		, _right(nullptr)
		{}
		K _k;
		V _v;
		BSTnode<K, V>* _left;
		BSTnode<K, V>* _right;
	};

	template<class K, class V>
	class BSTree
	{
		typedef BSTnode<K,V> node;
		typedef node* pnode;
	public:
		BSTree()//构造
			:_pnode(nullptr)
		{}
		BSTree(const BSTree<K,V>& t)//拷贝构造
		{
			_pnode = Copy(t._pnode);
		}
		BSTree<K,V>& operator=(const BSTree<K,V> t)//赋值运算符重载
		{
			swap(_pnode, t._pnode);
			return *this;
		}
		~BSTree()//析构
		{
			destory(_pnode);
			_pnode = nullptr;
		}
		pnode Find(const K& k)//查
		{
			pnode cur = _pnode;
			while (cur)
			{
				if (cur->_k > k)
				{
					cur = cur->_left;
				}
				else if (cur->_k < k)
				{
					cur = cur->_right;
				}
				else
				{
					return cur;
				}
			}
			return cur;
		}
		bool insert(const K& k, const V& v)//增
		{
			pnode newnode = new node(k,v);
			//如果该树没有节点就直接插入新节点为根节点
			if (_pnode == nullptr)
			{
				_pnode = newnode;
				return true;
			}
			pnode cur = _pnode;
			pnode parent = cur;//记录当前节点的父节点,因为如果要插入就要插入在当前节点的父节点的左右孩子处
			while (cur)
			{
				parent = cur;
				if (cur->_k > k)
				{
					cur = cur->_left;
				}
				else if (cur->_k < k)
				{
					cur = cur->_right;
				}
				else//如果该节点已经在树中存在就插入失败
				{
					return false;
				}
			}
			//插入元素
			if (parent->_k > k) parent->_left = newnode;
			else parent->_right = newnode;
			return true;
		}
		bool erase(const K& k)//删
		{
			//树为空删除失败
			if (_pnode == nullptr) return false;
			pnode cur = _pnode;
			pnode parent = cur;
			while (cur)
			{
				if (cur->_k > k)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_k < k)
				{
					parent = cur;
					cur = cur->_right;
				}
				else//删除元素(cur->_k == k)
				{
					//无孩子
					if (cur->_left == nullptr && cur->_right == nullptr)
					{
						if (cur == parent->_left) parent->_left = nullptr;
						else parent->_right = nullptr;
						delete cur;
						cur = nullptr;
					}
					//有左孩子,没有右孩子
					else if (cur->_left && cur->_right == nullptr)
					{
						if (cur == parent->_left) parent->_left = cur->_left;
						else parent->_right = cur->_left;
						delete cur;
						cur = nullptr;
					}
					//有右孩子,没有左孩子
					else if (cur->_left == nullptr && cur->_right)
					{
						if (cur == parent->_left) parent->_left = cur->_right;
						else parent->_right = cur->_right;
						delete cur;
						cur = nullptr;
					}
					//两个孩子都有(替换删除法)
					else if (cur->_left&&cur->_right)
					{
						pnode pR = cur->_right;//cur的右子树的根节点
						pnode pRL = cur->_right;//cur的右子树的最左节点
						while (pRL->_left)
						{
							pRL = cur->_left;
						}
						K temp = pRL->_k;
						erase(temp);
						cur->_k = temp;
					}
					return true;
				}
			}
			//树中没有该元素删除失败
			return false;
		}
		//中序遍历
		void inorder()
		{
			_inorder(_pnode);
			cout << endl;
		}
	private:
		void destory(pnode p)
		{
			if (p == nullptr) return;
			destory(p->_left);
			destory(p->_right);
			delete p;
		}
		pnode Copy(pnode root)
		{
			if (root == nullptr) return nullptr;
			pnode newnode = new node(root->_k);
			newnode->_left = Copy(root->_left);
			newnode->_right = Copy(root->_right);
			return newnode;
		}
		void _inorder(pnode root)
		{
			if (root == nullptr) return;
			_inorder(root->_left);
			cout << '<'<<root->_k<<','<<root->_v<<'>' << "  ";
			_inorder(root->_right);
		}
		pnode _pnode;
	};
	void test()
	{
		kv::BSTree<string, string> dict;
		dict.insert("sort", "排序");
		dict.insert("string", "字符串");
		dict.insert("left", "左边");
		dict.insert("right", "右边");
		string str;
		while (cin >> str)
		{
			kv::BSTnode<string, string>* ret = dict.Find(str);
			if (ret)
			{
				cout << ret->_v << endl;
			}
			else
			{
				cout << "无此单词" << endl;
			}
		}
	}
}

测试运行结果:
在这里插入图片描述

五、二叉搜索树性能

二叉树的插入和删除都需要先进行查找,因此查找的效率代表着二叉树的性能
对有n个节点的二叉树,若每个元素的查找概率相同,那么二叉搜索树平均查找次数是二叉树的高度次,即二叉树越高比较次数越多。
但对于同一个关键码的集合,如果各个关键码的插入次序不同,所得到的二叉树的结构可能不同。
在这里插入图片描述
二叉搜索树性能最好是它的结构为完全二叉树(或接近完全二叉树),它的平均比较次数为 l o g 2 N log_2 N log2N
二叉搜索树性能最差是他的结构退化成单支,如图中右边的二叉树,它的平均比较次数为 N 2 \frac{N}{2} 2N
如果搜索二叉树退化成单支,那么它的性能就丢失了,如何对其进行改进呢?这就涉及我们之后介绍的AVL树和红黑树。

六、模拟实现搜索二叉树

namespace Jinger
{
	template<class K>
	struct BSTnode
	{
		BSTnode(const K& k = K())
		:_k(k)
		,_left(nullptr)
		, _right(nullptr)
		{}
		K _k;
		BSTnode<K>* _left;
		BSTnode<K>* _right;
	};

	template<class K>
	class BSTree
	{
		typedef BSTnode<K> node;
		typedef node* pnode;
	public:
		BSTree()//构造
			:_pnode(nullptr)
		{}
		BSTree(const BSTree<K>& t)//拷贝构造
		{
			_pnode = Copy(t._pnode);
		}
		BSTree<K>& operator=(const BSTree<K> t)//赋值运算符重载
		{
			swap(_pnode,t._pnode);
			return *this;
		}
		~BSTree()//析构
		{
			destory(_pnode);
			_pnode = nullptr;
		}
		pnode Find(const K& k)//查
		{
			pnode cur = _pnode;
			while (cur)
			{
				if (cur->_k > k)
				{
					cur = cur->left;
				}
				else if (cur->_k < k)
				{
					cur = cur->right;
				}
				else
				{
					return cur;
				}
			}
			return cur;
		}
		bool insert(const K& k)//增
		{
			pnode newnode = new node(k);
			//如果该树没有节点就直接插入新节点为根节点
			if (_pnode == nullptr)
			{
				_pnode = newnode;
				return true;
			}
			pnode cur = _pnode;
			pnode parent = cur;//记录当前节点的父节点,因为如果要插入就要插入在当前节点的父节点的左右孩子处
			while (cur)
			{
				parent = cur;
				if (cur->_k > k)
				{
					cur = cur->_left;
				}
				else if (cur->_k < k)
				{
					cur = cur->_right;
				}
				else//如果该节点已经在树中存在就插入失败
				{
					return false;
				}
			}
			//插入元素
			if (parent->_k > k) parent->_left = newnode;
			else parent->_right = newnode;
			return true;
		}
		bool erase(const K& k)//删
		{
			//树为空删除失败
			if (_pnode == nullptr) return false;
			pnode cur = _pnode;
			pnode parent = cur;
			while (cur)
			{
				if (cur->_k > k)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_k < k)
				{
					parent = cur;
					cur = cur->_right;
				}
				else//删除元素(cur->_k == k)
				{
					//无孩子
					if (cur->_left == nullptr && cur->_right == nullptr)
					{
						if (cur == parent->_left) parent->_left = nullptr;
						else parent->_right = nullptr;
						delete cur;
						cur = nullptr;
					}
					//有左孩子,没有右孩子
					else if (cur->_left && cur->_right == nullptr)
					{
						if (cur == parent->_left) parent->_left = cur ->_left;
						else parent->_right = cur ->_left;
						delete cur;
						cur = nullptr;
					}
					//有右孩子,没有左孩子
					else if (cur->_left == nullptr && cur->_right)
					{
						if (cur == parent->_left) parent->_left = cur->_right;
						else parent->_right = cur->_right;
						delete cur;
						cur = nullptr;
					}
					//两个孩子都有(替换删除法)
					else if (cur->_left&&cur->_right)
					{
						pnode pR = cur->_right;//cur的右子树的根节点
						pnode pRL = cur->_right;//cur的右子树的最左节点
						while (pRL->_left)
						{
							pRL = cur->_left;
						}
						K temp = pRL->_k;
						erase(temp);
						cur->_k = temp;
					}
					return true;
				}
			}
			//树中没有该元素删除失败
			return false;
		}
		//中序遍历
		void inorder()
		{
			_inorder(_pnode);
			cout<<endl;
		}
	private:
		void destory(pnode p)
		{
			if (p == nullptr) return;
			destory(p->_left);
			destory(p->_right);
			delete p;
		}
		pnode Copy(pnode root)
		{
			if (root == nullptr) return nullptr;
			pnode newnode = new node(root->_k);
			newnode->_left = Copy(root->_left);
			newnode->_right = Copy(root->_right);
			return newnode;
		}
		void _inorder(pnode root)
		{
			if (root == nullptr) return;
			_inorder(root->_left);
			cout << root->_k << "  ";
			_inorder(root->_right);
		}
		pnode _pnode;
	};
	//测试插入和删除
	void test1()
	{
		int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
		BSTree<int> t;
		for (auto e : a)
		{
			t.insert(e);
		}
		t.inorder();
		t.erase(3);
		t.inorder();
		t.erase(13);
		t.inorder();
	}
}

总结

以上就是今天要讲的内容,本文介绍了二叉搜索树的相关概念。本文作者目前也是正在学习C++相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

codeJinger

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值