【C++】二叉搜索树

一、二叉搜索树的概念

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

  • 若它的左子树不为空,则左子树上所有结点的值都小于等于根结点的值。

  • 若它的右子树不为空,则右子树上所有结点的值都大于等于根结点的值。

  • 它的左右子树也分别为⼆叉搜索树。

⼆叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景定义。

比如:map / set / multimap / multiset 系列容器底层就是⼆叉搜索树,其中map/set不支持插入相等
值,multimap/multiset支持插入相等值。

二、二叉搜索树的性能分析

在二叉搜索树中,如果想要搜查到某一个数时,最多要找高度次。

下面分两种情况:

最优情况下,二叉搜索树为完全二叉树(或者接近完全⼆叉树),其时间复杂度为:O(\log_{2}N)
最差情况下,二叉搜索树退化为单支树(或者类似单支),其时间复杂度为:O(N)

那么像O(N)这样的效率显然是无法满足我们需求的,我们必须对最坏情况下的二叉搜索树进行控制,所以就有了⼆叉搜索树的变形,即平衡⼆叉搜索树(可以降低高度),平衡搜索树又衍生出了AVL树、红黑树、B树系列(B树、B+树、B*树)等,这几种树都可以满足查找,还有另外一种结构-哈希表,也可以进行查找。

大家先前应该学习过二分查找,这种查找效率也是很高的,它的时间复杂度是:O(\log_{2}N),但它有局限性:

  1. 需要存储在支持下标随机访问的结构中,并且有序。
  2. 插入和删除数据效率很低,因为存储在下标随机访问的结构中,插入和删除数据一般需要挪动数据。

 三、二叉搜索树的具体实现

1、框架

//二叉搜索树结点信息
template<class K>
struct BSTNode
{
	K _key;
	BSTNode<K>* _left;
	BSTNode<K>* _right;

	BSTNode(const K& key) //构造函数
		:_key(key)
		,_left(nullptr)
		,_right(nullptr)
	{}
};

//二叉搜索树(Binary Search Tree)
template <class K>
class BSTree
{
	//typedef BSTNode<K> Node;
	using Node = BSTNode<K>; //C++新增的using用法,这里的using的typedef的效果一样,只是用法形式不一样
public:
	//...

private:
	Node* _root = nullptr; //根节点指针,这里给一个缺省值,就不用显示写构造函数了
};

在"//..."这个位置上进行二叉搜索树的各种功能接口的实现。

2、二叉搜索树的插入

 ⼆叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,我们通常会将这两种情况分开实现,我们下面的实现以不支持插入相等的值为例进行操作,那么二叉树就遵循左子树上所有结点的值都小于根结点的值,右子树上所有结点的值都大于根结点的值。

插入的具体过程:

  1. 树为空:则直接新增结点,赋值给root指针。
  2. 树不为空:按⼆叉搜索树性质,插入值比当前结点大往右走,插入值比当前结点小往左走,找到空位置,插入新结点。
  3. 如果支持插入相等的值,插入值跟当前结点相等的值可以往右走,也可以往左走,找到空位置,插入新结点。(要注意的是要保持逻辑⼀致性,插入相等的值不要一会往右走,一会往左走)

代码实现:

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

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

	while (cur)  //找到空位置进行插入
	{
		if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false; //不允许插入相同的值
		}
	}

	Node* newnode = new Node(key); //创建新结点

	//这时cur不确定在parent的那一边,所以要进行判断,最后插入新结点
	//if (parent->_left == cur)
		//parent->_left = newnode;
	//else
		//parent->_right = newnode;
    //用上面这种方式进行判断是不可取的,因为parent可能是叶子节点,这时parent的左孩子为空,右孩子也为空,假设本意要插在parent的右边,结果if语句条件成立(因为此时cur是nullptr),最后插入到左边去了,那就可能会破坏二叉搜索树的特点

    //正确逻辑应该这样:
	if (parent->_key > key)  //根据parent当前的值进行判断
		parent->_left = newnode;
	else
		parent->_right = newnode;

	return true;
}

3、二叉搜索树中序遍历

根据二叉搜索树的特点,中序遍历的效果就相当于顺序遍历的效果。

现有一个问题,中序遍历时需要传一个头结点的参数,但当前类中的头结点时私有成员,不能直接拿到,解决方法有两种:(1)单独写一个接口函数用来获取私有成员,(2)间接调用

我们这里用方法二来实现:

void InOrder()
{
	_InOrder(_root);
	cout << endl;
}

private:
void _InOrder(Node* root)
{
	if (root == nullptr)
		return;

	_InOrder(root->_left);
	cout << root->_key << " ";
	_InOrder(root->_right);
}

类外部直接调用InOrder即可。 

我们测试一下上面的功能是否正确:

int main()
{
	BSTree<int> t;
	int a[] = { 8,3,1,10,1,6,4,7,14,13 };

	for (auto e : a)
	{
		t.Insert(e);
	}

	t.InOrder();

	return 0;
}

运行结果:

   

结果没问题,数组中两个1,最后直插入了一个,打印顺序也满足从小到大。 

这里的二叉搜索树t的逻辑结构示意图如下:

 4、二叉搜索树的查找

查找规则:

(1)从根开始比较 ,查找x,x比根的值大则往右边走查找,x比根值小则往左边走查找。

(2)最多查找 高度次,走到到空,还没找到,这个值不存在。

(3)如果不支持 插入相等的值,找到x即可返回。

代码实现:

//利用循环实现
bool Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_key > key)
			cur = cur->left;
		else if (cur->_key < key)
			cur = cur->_right;
		else
			return true;
	}
	return false;
}

/************************************************/

//利用递归实现
public:
	bool Find(const K& key)
	{
		return _Find(_root, key);
	}
	
private:
	bool _Find(Node* _root, const K& key)
	{
		if (_root == nullptr)
			return false;

		bool ret1 = false;
		bool ret2 = false;

		if (_root->_key == key)
		{
			return true;
		}
		else if (_root->_key < key)
		{
			ret1 = Find(_root->_right, key);
		}
		else
		{
			ret2 = Find(_root->_left, key);
		}
		return ret1 || ret2;
	}

(4)如果支持 插入相等的值,意味着有多个x存在,一般要求查找中序的第一个x。如下图,查找3,要找到1的右孩子的那个3返回。

     

要想查找到中序遍历中第一个x,就必须先在左子树找,若找到x,就接着在当前位置的左子树找,直到左子树上找不到x了,那么当前位置就是中序遍历中的第一个x。 

5、二叉搜索树的删除

首先查找要删除的元素是否在⼆叉搜索树中,如果不存在,则返回false。如果查找元素存在则分以下四种情况分别处理(假设要删除的结点是N):

(1)N左右孩子均为空。

(2)N左孩子为空,右孩子不为空。

(3)N右孩子为空,左孩子不为空。

(4)N左右孩子均不为空。

针对以上四种情况的解决方案:

(1)把N结点的父亲对应孩子指针指向空,直接删除N结点(情况1可以当成情况2或情况3处理)

(2)把N结点的父亲对应孩子指针指向N的右孩子,直接删除N结点。

(3)把N结点的父亲对应孩子指针指向N的左孩子,直接删除N结点。

(4)无法直接删除N结点,因为N的两个孩子无处安放,可以用替换法删除。找N左子树的值最大结点R(最右节点)或N右子树的值最小结点R(最左节点)替代N,因为这两个结点中任意一个放到N的位置,都满足二叉搜索树的规则。替代N的意思就是将R的值赋给N,转而删除R结点,R结点又符合情况2或情况3,可以直接删除。

代码实现:

bool Erase(const K& key)
{
	Node* cur = _root;
	Node* parent = nullptr;

	while (cur)
	{
		if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_key > 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->_right;
				}
				delete cur;
			}
			//右为空
			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;
			}
			//左右都不为空
			else
			{	
				//这里用右子树的最小结点(最左结点)来代替要删除的结点
				Node* replace = cur->_right; 
				Node* replaceParent = nullptr; 
				while (replace->_left)
				{
					replaceParent = replace;
					replace = replace->_left;
				}

				//将replace结点的值赋给cur,再将replace删除即可
				cur->_key = replace->_key;
				replaceParent->_left = replace->_right;

				delete replace;
			}
			return true;
		}
	}

	return false;
}

我们来测试一下:

int main()
{
	BSTree<int> t;
	int a[] = { 8,3,1,10,1,6,4,7,14,13 };

	for (auto e : a)
	{
		t.Insert(e);
	}

	t.InOrder();

	t.Erase(3);
	t.InOrder();

	return 0;
}

运行结果:

结果没有问题,但真的没有问题吗?我们现删除一下头结点8: 

int main()
{
	BSTree<int> t;
	int a[] = { 8,3,1,10,1,6,4,7,14,13 };

	for (auto e : a)
	{
		t.Insert(e);
	}

	t.InOrder();

	t.Erase(3);
	t.InOrder();

	t.Erase(8);
	t.InOrder();

	return 0;
}

运行结果:

程序崩溃了。

经过调试,发现:

在删除过程中,replaceParent是空指针,用空指针去访问_left肯定会有问题。

replaceParent为什么是空指针呢?

我们结合逻辑图来分析:

现在要删除8,我们的代码逻辑是找右子树的最小结点来间接替换8,现在8的右子树最小结点是10,我们的代码逻辑是将10赋值给8,然后删除10,while循环的条件是"replace->_left",但此时"replace->_left"为空,所以while循环进不去,那么replaceParent的值就是起初给的nullptr。所以程序崩溃。但是replaceParent实际上是8这个位置结点的指针,它不为nullptr。如果要删除3,那么replace就是4,replaceParent就是6,那就应该修改replaceParen->_left的指向;如果要删除8,那么replace就是10,replaceParent就是8,那就应该修改replaceParen->_right的指向,所以不能固定修改replaceParen->_left的指向,要具体判断。所以我们需要对代码进行如下修改:

bool Erase(const K& key)
	{
		Node* cur = _root;
		Node* parent = nullptr;

		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > 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->_right;
					}
					delete cur;
				}
				//右为空
				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;
				}
				//左右都不为空
				else
				{	
					//这里用右子树的最小结点(最左结点)来代替要删除的结点
					Node* replace = cur->_right; 
					Node* replaceParent = cur; //修改一
					while (replace->_left)
					{
						replaceParent = replace;
						replace = replace->_left;
					}

					//将replace结点的值赋给cur,再将replace删除即可
					cur->_key = replace->_key;

					//修改二
					if (replaceParent->_left == replace)
						replaceParent->_left = replace->_right;
					else
						replaceParent->_right = replace->_right;

					delete replace;
				}
				return true;
			}
		}

		return false;
	}

运行结果:

这次就能正常删除8这个结点了, 这就是删除的整体逻辑。

注意:二叉搜索树是不支持修改的,因为修改后可能就不是二叉搜索树了,破坏了二叉搜索树的性质。性质一旦被破坏,那原来的插入、查找、删除就无从谈起了。二叉搜索树中不单单是只能存放整形类型元素,其它可以比较的类型的元素都可以存放。

四、二叉搜索树的使用场景

1、key搜索场景

只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值,搜索场景只需要判断key在不在。key的搜索场景实现的二叉树搜索树支持增删查,但是不支持修改,修改key破坏搜索树结构了。

场景1:小区无人值守车库,小区车库买了车位的业主车才能进小区,那么物业会把买了车位的业主的车牌号(字符串)录入后台系统(放入二叉搜索树中),车辆进入时会扫描车牌(在二叉树中搜索),看是否在系统中,如果在则抬杆,不在则提示非本小区车辆,无法进入。

场景2:检查一篇英文文章单词拼写是否正确,将词库中所有单词放入二叉搜索树中,读取文章中的单词,查找是否在⼆叉搜索树中,不在则波浪线标红提示。

2、key/value搜索场景

每一个关键码key,都有与之对应的值value,value可以是任意类型对象。在二叉搜索树的结构中(结点)除了需要存储key还要存储对应的value,增/删/查还是以key为关键字走二叉搜索树的规则进行比较,可以快速查找到key对应的value。key/value的搜索场景实现的⼆叉树搜索树支持修改,但是不支持修改key,修改key破坏搜索树结构了,可以修改value。

场景1:简单中英互译字典,树的结构中(结点)存储key(英文)和value(中文),搜索时输入英文,则同时查找到了英文对应的中文。

场景2:商场无人值守车库,入口进场时扫描车牌,记录车牌(key)和入场时间(value),出口离场时,扫描车牌(key),查找入场时间(key对应的value),用当前时间 - 入场时间计算出停车时长,计算出停车费用,缴费后抬杆,车辆离场。

场景3:统计一篇文章中单词出现的次数,读取一个单词,查找单词(key)是否存在,不存在这个说明第一次出现(单词,1),单词存在,则++单词对应的次数(value)。

我们上面实现的代码是针对key搜索场景下的,那么对key搜索场景下的代码进行稍微修改,就可以变成key/value搜索场景下的代码。

五、key搜索场景下源码

namespace key
{
	//二叉搜索树结点信息
	template<class K>
	struct BSTNode
	{
		K _key;
		BSTNode<K>* _left;
		BSTNode<K>* _right;

		BSTNode(const K& key) //构造函数
			:_key(key)
			, _left(nullptr)
			, _right(nullptr)
		{}
	};

	//二叉搜索树(Binary Search Tree)
	template <class K>
	class BSTree
	{
		//typedef BSTNode<K> Node;
		using Node = BSTNode<K>; //C++新增的using用法,这里的using的typedef的效果一样,只是用法形式不一样
	public:

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

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

			while (cur)  //找到空位置进行插入
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false; //不允许插入相同的值
				}
			}

			Node* newnode = new Node(key); //创建新结点

			//这时cur不确定在parent的那一边,所以要进行判断,最后插入新结点
			if (parent->_key > key)
				parent->_left = newnode;
			else
				parent->_right = newnode;

			return true;
		}

		bool Find(const K& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (cur->_key > key)
					cur = cur->_left;
				else if (cur->_key < key)
					cur = cur->_right;
				else
					return true;
			}
			return false;
		}

		bool Erase(const K& key)
		{
			Node* cur = _root;
			Node* parent = nullptr;

			while (cur)
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > 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->_right;
						}
						delete cur;
					}
					//右为空
					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;
					}
					//左右都不为空
					else
					{
						//这里用右子树的最小结点(最左结点)来代替要删除的结点
						Node* replace = cur->_right;
						Node* replaceParent = cur; //修改一
						while (replace->_left)
						{
							replaceParent = replace;
							replace = replace->_left;
						}

						//将replace结点的值赋给cur,再将replace删除即可
						cur->_key = replace->_key;

						//修改二
						if (replaceParent->_left == replace)
							replaceParent->_left = replace->_right;
						else
							replaceParent->_right = replace->_right;

						delete replace;
					}
					return true;
				}
			}

			return false;
		}

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}

	private:
		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;

			_InOrder(root->_left);
			cout << root->_key << " ";
			_InOrder(root->_right);
		}

	private:
		Node* _root = nullptr; //根节点指针,这里给一个缺省值,就不用显示写构造函数了
	};
}

六、key/value搜索场景下源码

namespace key_value
{
	//二叉搜索树结点信息
	template<class K,class V>
	struct BSTNode
	{
		K _key;
		V _value;

		BSTNode<K, V>* _left;
		BSTNode<K, V>* _right;

		BSTNode(const K& key,const V& value) //构造函数
			:_key(key)
			,_value(value)
			, _left(nullptr)
			, _right(nullptr)
		{}
	};

	//二叉搜索树(Binary Search Tree)
	//K-key  V-value
	template <class K, class V>
	class BSTree
	{
		//typedef BSTNode<K, V> Node;
		using Node = BSTNode<K, V>; //C++新增的using用法,这里的using的typedef的效果一样,只是用法形式不一样
	public:

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

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

			while (cur)  //找到空位置进行插入
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false; //不允许插入相同的值
				}
			}

			Node* newnode = new Node(key, value); //创建新结点

			//这时cur不确定在parent的那一边,所以要进行判断,最后插入新结点
			if (parent->_key > key)
				parent->_left = newnode;
			else
				parent->_right = newnode;

			return true;
		}

		Node* Find(const K& key) //支持修改value,所以这里返回值为结点指针,便于修改value
		{
			Node* cur = _root;
			while (cur)
			{
				if (cur->_key > key)
					cur = cur->_left;
				else if (cur->_key < key)
					cur = cur->_right;
				else
					return cur;
			}
			return nullptr;
		}

		bool Erase(const K& key)
		{
			Node* cur = _root;
			Node* parent = nullptr;

			while (cur)
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > 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->_right;
						}
						delete cur;
					}
					//右为空
					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;
					}
					//左右都不为空
					else
					{
						//这里用右子树的最小结点(最左结点)来代替要删除的结点
						Node* replace = cur->_right;
						Node* replaceParent = cur; //修改一
						while (replace->_left)
						{
							replaceParent = replace;
							replace = replace->_left;
						}

						//将replace结点的值赋给cur,再将replace删除即可
						cur->_key = replace->_key;

						//修改二
						if (replaceParent->_left == replace)
							replaceParent->_left = replace->_right;
						else
							replaceParent->_right = replace->_right;

						delete replace;
					}
					return true;
				}
			}

			return false;
		}

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}

	private:
		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;

			_InOrder(root->_left);
			cout << root->_key << ":" << root->_value << endl;
			_InOrder(root->_right);
		}

	private:
		Node* _root = nullptr; //根节点指针,这里给一个缺省值,就不用显示写构造函数了
	};
}

针对key/value搜索场景下,我们可以写点代码来看看它的作用:

int main()
{
	key_value::BSTree<string, string> dict; //key的类型是string,value的类型也是string
	dict.Insert("left", "左边");
	dict.Insert("right", "右边");
	dict.Insert("insert", "插入");
	dict.Insert("string", "字符串");

	string str;
	while (cin >> str)
	{
		auto ret = dict.Find(str);
		if (ret) //如果找到就返回该结点指针
		{
			cout << "->" << ret->_value << endl;
		}
		else
		{
			cout << "无此单词,请重新输入" << endl;
		}
	}
	return 0;
}

运行结果: 

这样就可以达到输入英文出现对应汉语的效果,如果想要达到输入汉语输出对应英文那么只需把key和value的值互换即可。因为我们没有在dict中插入hello,所以在Find时就找不到。

这里在给出另外一种场景供大家理解:

int main()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };
	key_value::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 == nullptr)
		{
			countTree.Insert(str, 1);
		}
		else
		{
			// 修改value
			ret->_value++;
		}
	}
	countTree.InOrder();

	return 0;
}

运行结果:

这样就可以统计每个key出现的次数。

其实set容器底层就是key搜索场景,map容器底层就是key/value搜索场景。

set/map容器中是不含重复元素的,multiset/multimap容器中可以含重复元素。

七、其它注意事项

二叉搜索树是一个类,是类就有对象,是对象就有深浅拷贝问题。所以我们还需考虑拷贝构造、赋值重载、析构函数等等。接下来针对两种搜索场景来进行补充:

1、key

public:
    //若只写拷贝构造,那么系统生成的默认拷贝构造就不再提供
    //所以我们要在此强制生成默认构造
    //BSTree() {} 
    BSTree() = default;  //C++11新语法

    //拷贝构造
    BSTree(const BSTree & t)
    {
	    _root = Copy(t._root);
    }

    //赋值重载
    BSTree& operator=(BSTree tmp)
    {
	    swap(_root, tmp._root);
	    return *this;
    }

    //析构函数
    ~BSTree()
    {
	    Destroy(_root);
	    _root = nullptr;
    }

private:
    void Destroy(Node* root)
    {
	    if (root == nullptr)
	    	return;

	    Destroy(root->_left);
	    Destroy(root->_right);
	    delete root;
    }

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

2、key/value

public:
    //若只写拷贝构造,那么系统生成的默认拷贝构造就不再提供
    //所以我们要在此强制生成默认构造
    //BSTree() {} 
    BSTree() = default;  //C++11新语法

    //拷贝构造
    BSTree(const BSTree & t)
    {
	    _root = Copy(t._root);
    }

    //赋值重载
    BSTree& operator=(BSTree tmp)
    {
	    swap(_root, tmp._root);
	    return *this;
    }

    //析构函数
    ~BSTree()
    {
	    Destroy(_root);
	    _root = nullptr;
    }

private:
    void Destroy(Node* root)
    {
	    if (root == nullptr)
	    	return;

	    Destroy(root->_left);
	    Destroy(root->_right);
	    delete root;
    }

    Node* Copy(Node* root)
    {
	    if (root == nullptr)
			return nullptr;

		Node* newRoot = new Node(root->_key, root->_value);
		newRoot->_left = Copy(root->_left);
		newRoot->_right = Copy(root->_right);
		return newRoot;
	}

八、源码

1、BinarySearch.h

#pragma once
#include <iostream>
using namespace std;

namespace key
{
	//二叉搜索树结点信息
	template<class K>
	struct BSTNode
	{
		K _key;
		BSTNode<K>* _left;
		BSTNode<K>* _right;

		BSTNode(const K& key) //构造函数
			:_key(key)
			, _left(nullptr)
			, _right(nullptr)
		{}
	};

	//二叉搜索树(Binary Search Tree)
	template <class K>
	class BSTree
	{
		//typedef BSTNode<K> Node;
		using Node = BSTNode<K>; //C++新增的using用法,这里的using的typedef的效果一样,只是用法形式不一样
	public:
		//若只写拷贝构造,那么系统生成的默认拷贝构造就不再提供
		//所以我们要在此强制生成默认构造
		//BSTree() {} 
		BSTree() = default;  //C++11新语法

		//拷贝构造
		BSTree(const BSTree & t)
		{
			_root = Copy(t._root);
		}

		//赋值重载
		BSTree& operator=(BSTree tmp)
		{
			swap(_root, tmp._root);
			return *this;
		}

		//析构函数
		~BSTree()
		{
			Destroy(_root);
			_root = nullptr;
		}

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

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

			while (cur)  //找到空位置进行插入
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false; //不允许插入相同的值
				}
			}

			Node* newnode = new Node(key); //创建新结点

			//这时cur不确定在parent的那一边,所以要进行判断,最后插入新结点
			if (parent->_key > key)
				parent->_left = newnode;
			else
				parent->_right = newnode;

			return true;
		}

		bool Find(const K& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (cur->_key > key)
					cur = cur->_left;
				else if (cur->_key < key)
					cur = cur->_right;
				else
					return true;
			}
			return false;
		}

		bool Erase(const K& key)
		{
			Node* cur = _root;
			Node* parent = nullptr;

			while (cur)
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > 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->_right;
						}
						delete cur;
					}
					//右为空
					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;
					}
					//左右都不为空
					else
					{
						//这里用右子树的最小结点(最左结点)来代替要删除的结点
						Node* replace = cur->_right;
						Node* replaceParent = cur; //修改一
						while (replace->_left)
						{
							replaceParent = replace;
							replace = replace->_left;
						}

						//将replace结点的值赋给cur,再将replace删除即可
						cur->_key = replace->_key;

						//修改二
						if (replaceParent->_left == replace)
							replaceParent->_left = replace->_right;
						else
							replaceParent->_right = replace->_right;

						delete replace;
					}
					return true;
				}
			}

			return false;
		}

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}

	private:
		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;

			_InOrder(root->_left);
			cout << root->_key << " ";
			_InOrder(root->_right);
		}

		void Destroy(Node* root)
		{
			if (root == nullptr)
				return;

			Destroy(root->_left);
			Destroy(root->_right);
			delete root;
		}

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

	private:
		Node* _root = nullptr; //根节点指针,这里给一个缺省值,就不用显示写构造函数了
	};
}


/***********************************************************************/


namespace key_value
{
	//二叉搜索树结点信息
	template<class K,class V>
	struct BSTNode
	{
		K _key;
		V _value;

		BSTNode<K, V>* _left;
		BSTNode<K, V>* _right;

		BSTNode(const K& key,const V& value) //构造函数
			:_key(key)
			,_value(value)
			, _left(nullptr)
			, _right(nullptr)
		{}
	};

	//二叉搜索树(Binary Search Tree)
	//K-key  V-value
	template <class K, class V>
	class BSTree
	{
		//typedef BSTNode<K, V> Node;
		using Node = BSTNode<K, V>; //C++新增的using用法,这里的using的typedef的效果一样,只是用法形式不一样
	public:

		//若只写拷贝构造,那么系统生成的默认拷贝构造就不再提供
		//所以我们要在此强制生成默认构造
		//BSTree() {} 
		BSTree() = default;  //C++11新语法

		//拷贝构造
		BSTree(const BSTree& t)
		{
			_root = Copy(t._root);
		}

		//赋值重载
		BSTree& operator=(BSTree tmp)
		{
			swap(_root, tmp._root);
			return *this;
		}

		//析构函数
		~BSTree()
		{
			Destroy(_root);
			_root = nullptr;
		}


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

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

			while (cur)  //找到空位置进行插入
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false; //不允许插入相同的值
				}
			}

			Node* newnode = new Node(key, value); //创建新结点

			//这时cur不确定在parent的那一边,所以要进行判断,最后插入新结点
			if (parent->_key > key)
				parent->_left = newnode;
			else
				parent->_right = newnode;

			return true;
		}

		Node* Find(const K& key) //支持修改value,所以这里返回值为结点指针,便于修改value
		{
			Node* cur = _root;
			while (cur)
			{
				if (cur->_key > key)
					cur = cur->_left;
				else if (cur->_key < key)
					cur = cur->_right;
				else
					return cur;
			}
			return nullptr;
		}

		bool Erase(const K& key)
		{
			Node* cur = _root;
			Node* parent = nullptr;

			while (cur)
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > 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->_right;
						}
						delete cur;
					}
					//右为空
					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;
					}
					//左右都不为空
					else
					{
						//这里用右子树的最小结点(最左结点)来代替要删除的结点
						Node* replace = cur->_right;
						Node* replaceParent = cur; //修改一
						while (replace->_left)
						{
							replaceParent = replace;
							replace = replace->_left;
						}

						//将replace结点的值赋给cur,再将replace删除即可
						cur->_key = replace->_key;

						//修改二
						if (replaceParent->_left == replace)
							replaceParent->_left = replace->_right;
						else
							replaceParent->_right = replace->_right;

						delete replace;
					}
					return true;
				}
			}

			return false;
		}

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}

	private:
		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;

			_InOrder(root->_left);
			cout << root->_key << ":" << root->_value << endl;
			_InOrder(root->_right);
		}

		void Destroy(Node* root)
		{
			if (root == nullptr)
				return;

			Destroy(root->_left);
			Destroy(root->_right);
			delete root;
		}

		Node* Copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;

			Node* newRoot = new Node(root->_key, root->_value);
			newRoot->_left = Copy(root->_left);
			newRoot->_right = Copy(root->_right);
			return newRoot;
		}
	private:
		Node* _root = nullptr; //根节点指针,这里给一个缺省值,就不用显示写构造函数了
	};
}

2、Test.cpp

#include "BinarySearch.h"

//int main()
//{
//	key::BSTree<int> t;
//	int a[] = { 8,3,1,10,1,6,4,7,14,13 };
//
//	for (auto e : a)
//	{
//		t.Insert(e);
//	}
//
//	t.InOrder();
//
//	t.Erase(3);
//	t.InOrder();
//
//	t.Erase(8);
//	t.InOrder();
//
//	for (auto e : a)
//	{
//		t.Erase(e);
//		t.InOrder();
//	}
//
//	return 0;
//}

//int main()
//{
//	key_value::BSTree<string, string> dict; //key的类型是string,value的类型也是string
//	dict.Insert("left", "左边");
//	dict.Insert("right", "右边");
//	dict.Insert("insert", "插入");
//	dict.Insert("string", "字符串");
//
//	string str;
//	while (cin >> str)
//	{
//		auto ret = dict.Find(str);
//		if (ret) //如果找到就返回该结点指针
//		{
//			cout << "->" << ret->_value << endl;
//		}
//		else
//		{
//			cout << "无此单词,请重新输入" << endl;
//		}
//	}
//	return 0;
//}


//int main()
//{
//	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };
//	key_value::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 == nullptr)
//		{
//			countTree.Insert(str, 1);
//		}
//		else
//		{
//			// 修改value
//			ret->_value++;
//		}
//	}
//	countTree.InOrder();
//
//
//	//key_value::BSTree<string, int> copy = countTree;
//	//copy.InOrder();
//
//	return 0;
//}

九、结语

本篇到这里就结束了,主要讲了二叉搜索树的基本概念以及实现逻辑,希望对大家有所帮助,祝生活愉快,我们下篇再见! 

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值