C++进阶--二叉树进阶(二叉搜索树)

二叉树进阶(二叉搜索树)

  • 一、二叉搜索树
    • 1.1 二叉搜索树的概念
  • 二、二叉搜索树的结构
    • 2.1 结点结构
    • 2.2 树结构
  • 三、二叉搜索树的操作(非递归)
    • 3.1 二叉搜索树的插入
    • 3.2 二叉搜索树的查找
    • 3.3 二叉搜索树的中序遍历
    • 3.4 二叉搜索树的删除
  • 四、二叉搜索树的操作(递归)
    • 4.1 二叉搜索树的查找
    • 4.2 二叉搜索树的插入
    • 4.3 二叉搜索树的删除
  • 五、其他相关成员函数的实现
    • 5.1 析构函数
    • 5.2 构造和拷贝构造
    • 5.3 赋值重载
  • 6、二叉搜索树的应用
    • 6.1 K模型
    • 6.2 KV模型
    • 6.3 KV模型代码实现
      • 6.3.1 将二叉搜索树循环版本改为KV模型
      • 6.3.2 映射--通过key的值找value
      • 6.3.3 统计水果出现的次数
  • 七、二叉搜索树的性能分析
  • 八、完整代码
    • 8.1 BSTree.h
    • 8.1 test.cpp

一、二叉搜索树

1.1 二叉搜索树的概念

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

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

衍生属性:

二叉搜索树不支持修改,因为修改之后会改变二叉搜索树的条件,使其变成非二叉搜索树。
二叉搜索树不能有重复的值,不支持重复值的搜索。在插入的时候会做到自动去重。
二叉搜索树的中序遍历Inorder是升序的。

在这里插入图片描述

二、二叉搜索树的结构

首先我们定义以下结点和搜索二叉树的结构,我们之前定义一个模板类,一般都喜欢用T(type),但是在这里比较喜欢用K(key)

2.1 结点结构

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

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

2.2 树结构

template<class K>
	class BSTree
	{
		typedef BSTreeNode<K> Node;
	public:
		//....
	}

三、二叉搜索树的操作(非递归)

3.1 二叉搜索树的插入

插入的具体过程如下:

a:树为空,则直接新增结点,赋值给root指针
b:树不为空,按二叉搜索树性质查找插入位置,插入新结点

在这里插入图片描述

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

3.2 二叉搜索树的查找

a:从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找
b:最多查找高度次,走到空,还没找到,这个值不存在

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

3.3 二叉搜索树的中序遍历

对于二叉搜索树而言,每一个结点的左子树值都比该结点的值小,右子树的值都比该结点的值要大。所以,中序遍历二叉搜索树是可以得到一个升序序列的。

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

			//void _InOrder(Node* root=_root)     
			// 1.缺省值必须是全局变量或者是常量,要在类里面访问到 
			//2.他需要用到this访问成员变量,是形参,this指针只能在函数内部使用
		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;

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

3.4 二叉搜索树的删除

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

a:要删除的结点无孩子结点
b:要删除的结点只有左孩子结点
c:要删除的结点只有右孩子结点
d:要删除的结点右左、右孩子结点

看起来有待删除结点有4种情况,实际情况a可以与情况b或者c合并起来

b:右子树为空
c:左子树为空
d:左右子树都不为空

因此真正的删除过程如下

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

情况a:
在这里插入图片描述

情况b和情况c:
在这里插入图片描述

情况d:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、二叉搜索树的操作(递归)

4.1 二叉搜索树的查找

  查找就是从根结点开始,如果大于根结点的值,就转换为右子树里面查找,如果小于根结点,就转换为去左子树查找,如果等于就直接返回;那对于子树也是这样,一步步转换为子问题。
如果最后都没找到,一直到空,那就返回false

		bool _FindR(Node* root, const K& key)
		{
			if (root == nullptr)
				return false;

			if (root->_key == key)
				return true;

			if (root->_key < key)
				return _FindR(root->_right, key);
			else
				return _FindR(root->_left, key);
		}

		bool FinR(const K& key)
		{
			return _FindR(_root, key);
		}

4.2 二叉搜索树的插入

  从根结点开始,如果要插入的结点大于根,就转换为去右子树插入;如果要插入的结点小于根,就转换为去左子树插入;如果相等,返回false;如果一直走到空,那就在该位置插入就行了。
但是有一个问题,我们找到空插入的时候,如何和它的父亲链接起来?

直接用root的引用就可以了。
因为引用的话,走到空,他就是那个位置指针的引用,直接赋给它就链接上了。
还不用像上面循环实现的那样去判断要链接到哪边
那大家思考一下,我们上面循环的方式,可以用引用吗?
不可以的,因为C++中的引用时不能改变指向的,引用一旦引用一个实体之后,就不能再引用其他实体
而这里递归的话,每次递归都相当于创建了一个新的引用,而不是改变上一层的引用的指向。
比如要往左子树插入值,此时root为空,new一个节点插入值,root这个节点就是父节点的左指针的引用,如果没有加这个引用,那么修改函数里的指针并不会影响函数外面的指针,即使new了节点,外面父节点的指针还是指向空,不会受影响。而加了引用,root就是父节点指向左子树这个指针的别名,此时修改root,就相当于修改了父节点的左指针

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

4.3 二叉搜索树的删除

从根结点开始判断,如果要删除的值大于根,转换为去右子树删,小于根,转换为去左子树删,相等就进行删除,如果走到空,那就是找不到。

其实还是我们上面分析的那三种情况:
左为空、右为空或者左右都不为空。
那这里我们用引用,写起来还是比较简便的。
先写一下左为空和右为空的情况,这两个比较好处理
然后看一下比较麻烦的左右都不为空的情况
我们之前非递归版本的实现是,找一个符合条件的结点替换它,然后把替换的结点删除掉
这里也可以用同样的方法,但我觉得比较简便的方法是这样的:
还是先找一个可以替代要删除结点的结点(左子树最大结点或右子树最小结点),以左子树最大结点替换为例
交换它们两个的值,然后删除左子树里面的key这个结点就行了
先找到节点,删除节点还是分三种情况:1.root的左节点为空,把root先提前用指针存好,然后让root的右节点给自己,因为自己是父节点的指针,所以就相当于让父节点指向指向root的右节点(托孤),最后释放掉提前存好的root节点,就可以了;2.root的右节点为空,跟第一个差不多;3.root的左右节点都不为空,这里用的是交换值,然后到root的右子树中去删除原来的key,简化问题去处理。

		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);
					//_root->_key = maxleft->_key;  //不能使用赋值,只能使用交换

					return _EraseR(root->_left, key);   //转换成在子树去删除
					//return _EraseR(maxleft, key);     //不能使用maxleft,不然引用不起作用
				}

				delete del;

				return true;
			}
		}

		bool EraseR(const K& key)
		{
			return _EraseR(_root, key);
		}

五、其他相关成员函数的实现

5.1 析构函数

  析构的话我们这里还是用递归来实现,也可以用循环,但是比较麻烦。对于Destroy最好的做法就是后续销毁。

		~BSTree()
		{
			Destroy(_root);
			//_root = nullptr;
		}

		void Destroy(Node*& root)
		{
			if (root == nullptr)
				return;
			Destroy(root->_left);
			Destroy(root->_right);

			delete root;
			root = nullptr;
		}

5.2 构造和拷贝构造

深拷贝的拷贝构造我们还是用递归来实现,通过先序去拷贝创建就行了

	BSTree(const BSTree<K>& t)
		{
			_root = Copy(t._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;
		}

  有了拷贝构造之后,编译器就不会生成默认的构造函数了,那就需要我们呢自己写了。另外C++11有一个关键字–default,可以强制编译器生成默认构造
  有了默认构造,我们给了缺省值,它就走初始化列表的时候就会用缺省值去初始化。

5.3 赋值重载

赋值重载直接使用现代的写法就可以了

BSTree<K>& operator=(BSTree<K> t)
{
	swap(_root, t._root);
	return *this;
}

6、二叉搜索树的应用

6.1 K模型

  1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
    比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
    a:以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树。
    b:在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

6.2 KV模型

  1. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:
    a:比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
    b:再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。

6.3 KV模型代码实现

6.3.1 将二叉搜索树循环版本改为KV模型

namespace key_value
{
	template<class K,class V>
	struct BSTreeNode
	{
		BSTreeNode<K,V>* _left;
		BSTreeNode<K,V>* _right;
		K _key;
		V _value;

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


	template<class K,class V>
	class BSTree
	{
		typedef BSTreeNode<K,V> Node;
	public:
		/*BSTree()
			:_root(nullptr)
		{}*/

		BSTree() = default;  //指定强制生成默认构造

		BSTree(const BSTree<K,V>& t)
		{
			_root = Copy(t._root);
		}

		BSTree<K,V>& operator=(BSTree<K,V> t)
		{
			swap(_root, t._root);
			return *this;
		}

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

		~BSTree()
		{
			Destroy(_root);
			_root = nullptr;
		}

		void Destroy(Node* root)
		{
			if (root == nullptr)
				return;
			Destroy(root->_left);
			Destroy(root->_right);

			delete root;
			root = nullptr;
		}

		bool Insert(const K& key,const V& value)
		{
			if (_root == nullptr)
			{
				_root = new Node(key,value);
				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,value);
			//链接
			if (parent->_key < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}
			return true;
		}

		Node* 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 cur;
				}
			}
			return nullptr;
		}


		bool _FindR(Node* root, const K& key)
		{
			if (root == nullptr)
				return false;

			if (root->_key == key)
				return true;

			if (root->_key < key)
				return _FindR(root->_right, key);
			else
				return _FindR(root->_left, key);
		}

		bool FinR(const K& key)
		{
			return _FindR(_root, key);
		}

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

		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);
					//return _EraseR(maxleft, key);     //不能使用maxleft,不然引用不起作用
				}

				delete del;

				return true;
			}
		}

		bool EraseR(const K& key)
		{
			return _EraseR(_root, key);
		}


		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.左为空
					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;
					}
					//2.右为空
					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* pminRight = cur;
						Node* minRight = cur->_right;
						while (minRight->_left)
						{
							pminRight = minRight;
							minRight = minRight->_left;
						}

						cur->_key = minRight->_key;

						if (pminRight->_left == minRight)
						{
							pminRight->_left = minRight->_right;
						}
						else
						{
							pminRight->_right = minRight->_right;
						}

						delete minRight;

					}

					return true;
				}
			}

			return false;
		}

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

			//void _InOrder(Node* root=_root)      1.缺省值必须是全局变量或者是常量,要在类里面访问到 2.他需要用到this访问成员变量,是形参,this指针只能在函数内部使用
		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;

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

	private:
		Node* _root = nullptr;
	};
}

6.3.2 映射–通过key的值找value

void TestBSTree2()
{
	key_value::BSTree<string, string> dict;
	dict.Insert("sort", "排序");
	dict.Insert("left", "左边");
	dict.Insert("right", "右边");
	dict.Insert("string", "字符串");
	dict.Insert("insert", "插入");
	dict.Insert("erase", "删除");

	string str;
	while (cin >> str)
	{
		auto ret = dict.Find(str);
		if (ret)
		{
			cout << ":" << ret->_value << endl;
		}
		else
		{
			cout << "无此单词" << endl;
		}
	}

}

6.3.3 统计水果出现的次数

void TestBSTree3()
{
	// 统计水果出现的次数
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
	"苹果", "香蕉", "苹果", "香蕉" };
	key_value::BSTree<string, int> countTree;
	for (auto str : arr)
	{
		//key_value::BSTreeNode<string, int>* ret = countTree.Find(str);
		auto ret = countTree.Find(str);
		if (ret == nullptr)
		{
			countTree.Insert(str, 1);
		}
		else
		{
			ret->_value++;
		}
	}
	countTree.InOrder();
}

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

  插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
  对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
  但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
在这里插入图片描述
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为 N 2 \frac{N}{2} 2N

八、完整代码

8.1 BSTree.h

#pragma once

//BinarySearchTree -- BSTree
//SearchBinaryTree

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

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


	template<class K>
	class BSTree
	{
		typedef BSTreeNode<K> Node;
	public:
		/*BSTree()
			:_root(nullptr)
		{}*/

		BSTree() = default;  //指定强制生成默认构造

		BSTree(const BSTree<K>& t)
		{
			_root = Copy(t._root);
		}

		BSTree<K>& operator=(BSTree<K> t)
		{
			swap(_root, t._root);
			return *this;
		}

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

		~BSTree()
		{
			Destroy(_root);
			//_root = nullptr;
		}

		void Destroy(Node*& root)
		{
			if (root == nullptr)
				return;
			Destroy(root->_left);
			Destroy(root->_right);

			delete root;
			root = nullptr;
		}

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

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


		bool _FindR(Node* root, const K& key)
		{
			if (root == nullptr)
				return false;

			if (root->_key == key)
				return true;

			if (root->_key < key)
				return _FindR(root->_right, key);
			else
				return _FindR(root->_left, key);
		}

		bool FinR(const K& key)
		{
			return _FindR(_root, key);
		}

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

		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);
					//_root->_key = maxleft->_key;  //不能使用赋值,只能使用交换

					return _EraseR(root->_left, key);   //转换成在子树去删除
					//return _EraseR(maxleft, key);     //不能使用maxleft,不然引用不起作用
				}

				delete del;

				return true;
			}
		}

		bool EraseR(const K& key)
		{
			return _EraseR(_root, key);
		}


		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.左为空
					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;
					}
					//2.右为空
					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* pminRight = cur;
						Node* minRight = cur->_right;
						while (minRight->_left)
						{
							pminRight = minRight;
							minRight = minRight->_left;
						}

						cur->_key = minRight->_key;

						if (pminRight->_left == minRight)
						{
							pminRight->_left = minRight->_right;
						}
						else
						{
							pminRight->_right = minRight->_right;
						}

						delete minRight;

					}

					return true;
				}
			}

			return false;
		}


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

		//protected:

			//void _InOrder(Node* root=_root)      1.缺省值必须是全局变量或者是常量,要在类里面访问到 2.他需要用到this访问成员变量,是形参,this指针只能在函数内部使用
		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;

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

	private:
		Node* _root = nullptr;
	};
}


namespace key_value
{
	template<class K,class V>
	struct BSTreeNode
	{
		BSTreeNode<K,V>* _left;
		BSTreeNode<K,V>* _right;
		K _key;
		V _value;

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


	template<class K,class V>
	class BSTree
	{
		typedef BSTreeNode<K,V> Node;
	public:
		/*BSTree()
			:_root(nullptr)
		{}*/

		BSTree() = default;  //指定强制生成默认构造

		BSTree(const BSTree<K,V>& t)
		{
			_root = Copy(t._root);
		}

		BSTree<K,V>& operator=(BSTree<K,V> t)
		{
			swap(_root, t._root);
			return *this;
		}

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

		~BSTree()
		{
			Destroy(_root);
			_root = nullptr;
		}

		void Destroy(Node* root)
		{
			if (root == nullptr)
				return;
			Destroy(root->_left);
			Destroy(root->_right);

			delete root;
			root = nullptr;
		}

		bool Insert(const K& key,const V& value)
		{
			if (_root == nullptr)
			{
				_root = new Node(key,value);
				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,value);
			//链接
			if (parent->_key < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}
			return true;
		}

		Node* 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 cur;
				}
			}
			return nullptr;
		}


		bool _FindR(Node* root, const K& key)
		{
			if (root == nullptr)
				return false;

			if (root->_key == key)
				return true;

			if (root->_key < key)
				return _FindR(root->_right, key);
			else
				return _FindR(root->_left, key);
		}

		bool FinR(const K& key)
		{
			return _FindR(_root, key);
		}

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

		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);
					//return _EraseR(maxleft, key);     //不能使用maxleft,不然引用不起作用
				}

				delete del;

				return true;
			}
		}

		bool EraseR(const K& key)
		{
			return _EraseR(_root, key);
		}


		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.左为空
					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;
					}
					//2.右为空
					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* pminRight = cur;
						Node* minRight = cur->_right;
						while (minRight->_left)
						{
							pminRight = minRight;
							minRight = minRight->_left;
						}

						cur->_key = minRight->_key;

						if (pminRight->_left == minRight)
						{
							pminRight->_left = minRight->_right;
						}
						else
						{
							pminRight->_right = minRight->_right;
						}

						delete minRight;

					}

					return true;
				}
			}

			return false;
		}


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

		//protected:

			//void _InOrder(Node* root=_root)      1.缺省值必须是全局变量或者是常量,要在类里面访问到 2.他需要用到this访问成员变量,是形参,this指针只能在函数内部使用
		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;

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

	private:
		Node* _root = nullptr;
	};
}

8.1 test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

#include "BSTree.h"


void TestBSTree()
{
	int a[] = { 8,3,1,10,6,4,7,14,13 };
	key::BSTree<int> t1;

	for (auto e : a)
	{
		//t1.Erase(e);
		t1.Insert(e);
	}

	//t1.InOrder(t1.GetRoot());
	t1.InOrder();

	t1.Erase(4);
	t1.InOrder();
	t1.Erase(14);
	t1.InOrder();
	t1.Erase(3);
	t1.InOrder();
	t1.Erase(8);
	t1.InOrder();

	for (auto e : a)
	{
		t1.Erase(e);
	}

	t1.InOrder();

}


void TestBSTree1()
{
	int a[] = { 8,3,1,10,6,4,7,14,13 };
	key::BSTree<int> t1;

	for (auto e : a)
	{
		t1.InsertR(e);
	}

	t1.InOrder();

	t1.EraseR(4);
	t1.InOrder();
	t1.EraseR(14);
	t1.InOrder();
	t1.EraseR(3);
	t1.InOrder();
	t1.EraseR(8);
	t1.InOrder();


	key::BSTree<int> t2(t1);
	t2.InOrder();

}


void TestBSTree2()
{
	key_value::BSTree<string, string> dict;
	dict.Insert("sort", "排序");
	dict.Insert("left", "左边");
	dict.Insert("right", "右边");
	dict.Insert("string", "字符串");
	dict.Insert("insert", "插入");
	dict.Insert("erase", "删除");

	string str;
	while (cin >> str)
	{
		auto ret = dict.Find(str);
		if (ret)
		{
			cout << ":" << ret->_value << endl;
		}
		else
		{
			cout << "无此单词" << endl;
		}
	}

}

void TestBSTree3()
{
	// 统计水果出现的次数
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
	"苹果", "香蕉", "苹果", "香蕉" };
	key_value::BSTree<string, int> countTree;
	for (auto str : arr)
	{
		//key_value::BSTreeNode<string, int>* ret = countTree.Find(str);
		auto ret = countTree.Find(str);
		if (ret == nullptr)
		{
			countTree.Insert(str, 1);
		}
		else
		{
			ret->_value++;
		}
	}
	countTree.InOrder();
}

int main()
{
	//TestBSTree();
	TestBSTree1();
	//TestBSTree2();
	//TestBSTree3();

	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/280335.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

软件测试/测试开发丨Selenium如何复用已打开浏览器

步骤说明&#xff1a; 将浏览器启动方式添加到环境变量。便于我们在终端任意位置启动浏览器终端中使用命令行&#xff0c;打开浏览器debug模式代码中创建driver时&#xff0c;添加debugger_address设置 以Chrome浏览器为例&#xff0c;设置步骤如下&#xff1a; 将浏览器启动…

设计模式——行为型模式

模板方法模式 行为型模式用于描述程序在运行时复杂的流程控制&#xff0c;即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务&#xff0c;它涉及算法与对象间职责的分配。 行为型模式分为类行为模式和对象行为模式&#xff0c;前者采用继承机制来在类间…

c++简易AI

今天小编一时雅兴大发&#xff0c;做了一个c的简易AI&#xff0c;还是很垃圾的&#xff01; 题外话&#xff08;每期都会有&#xff09;&#xff1a;我的蛋仔名叫酷影kuying&#xff0c;大家能加我好友吗&#xff1f; 上代码咯&#xff01; #include<bits/stdc.h> #in…

荔枝派nano(f1c100s)基于I2C子系统的BME280驱动

硬件环境&#xff1a; 1、荔枝派nano&#xff08;f1c100s&#xff09; 2、使用f1c100s的i2c0&#xff0c;PE11和PE12引脚 软件环境&#xff1a; 1、Linux 4.15 2、BME280使用介绍 文章目录 一、I2C子系统1、应用层访问i2c设备2、驱动层访问i2c设备2.1、i2c总线设备驱动模型2.2、…

Matlab:K-means算法

K-means算法是一种常见的聚类算法&#xff0c;它将一组数据划分为K个不同的簇&#xff0c;以最小化每个簇内部数据点与簇中心之间的平方距离的总和为目标实现聚类。 1、基本步骤&#xff1a; 1.选择要划分的簇数K&#xff1b; 2.选择K个数据点作为初始的聚类中心&#xff1b…

链表精选题集

目录 1 链表翻转 题目链接&#xff1a; 解题&#xff1a; 试错版&#xff1a; 2 找中间节点 题目链接: 题解&#xff1a; 3 找倒数第k个节点 题目链接&#xff1a; 题解&#xff1a; 4 将两个升序链表合并为一个升序链表 题目链接&#xff1a; 题解&#xff1a; …

数据结构与算法 - 查找

文章目录 第1关&#xff1a;实现折半查找第2关&#xff1a;实现散列查找 第1关&#xff1a;实现折半查找 代码如下&#xff1a; /*************************************************************date: April 2009copyright: Zhu EnDO NOT distribute this code. ***********…

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《面向平稳氢气需求的综合制氢系统鲁棒优化配置方法》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主的专栏栏目《论文与完整程序》 这个标题涉及到针对平稳氢气需求的综合制氢系统鲁棒优化配置方法。让我们逐步解读这个标题的关键要素&#xff1a; 面向平稳氢气需求&#xff1a; 这部分指…

超实用!CSDN个人数据Chrome插件开发

插件简介 相信写过博客的都知道&#xff0c;每天会经常打开自己的主页无数次&#xff0c;尤其是写了一篇新文章&#xff0c;就为了看文章浏览量增长了多少&#xff0c;文章获得了多少个赞&#xff0c;有多少人评论&#xff08;谁不想自己写的文章成为爆款呢&#xff5e;&#…

C# WPF上位机开发(Web API联调)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 很多时候&#xff0c;客户需要开发的不仅仅是一个上位机系统&#xff0c;它还有其他很多配套的系统或设备&#xff0c;比如物流小车、立库、数字孪…

【Maven】报错合集

问题1&#xff1a;com.github.everit-org.json-schema:org.everit.json.schema:pom:1.12.1 failed to transfer from http://localhost:8081/repository/maven-public/ during a previous attempt 发现原来是maven的settings.xml文件配置出现了问题。首先是之前maven进阶学习时…

【Java】一文讲解Java类加载机制

Java 类加载机制是 Java 运行时的核心组成部分&#xff0c;负责在程序运行过程中动态加载和连接类文件&#xff0c;并将其转换为可执行代码。理解类加载机制&#xff0c;能更容易理解你一行行敲下的Java代码是如何在JVM虚拟机上运行起来。并且理解类加载机制之后&#xff0c;我…

SpringBoot整合Canal

一 linux docker compose版本 1.第一步&#xff1a;基础环境 &#xff08;1&#xff09;第1步&#xff1a;安装jak、maven、git、nodejs、npm yum install maven mvn -v 安装maven时会帮安装jdkyum install git git --version 2.27.0yum in…

提升客户体验!十大热门客户服务软件解决方案推荐

现代企业深切认识到客户关系对于成功至关重要。如今&#xff0c;顾客越来越偏向于个性化和情境化服务的企业。根据Forrester的研究&#xff0c;将优先考虑建立更好客户关系以实现长期增长将是2023年业务成功的关键。 为了评估和改善客户关系&#xff0c;您需要一个系统化的方式…

DataFunSummit:2023年数据湖架构峰会-核心PPT资料下载

一、峰会简介 现今&#xff0c;很多企业每天都有PB级的数据注入到大数据平台&#xff0c;经过离线或实时的ETL建模后&#xff0c;提供给下游的分析、推荐及预测等场景使用。面对如此大规模的数据&#xff0c;无论是分析型场景、流批一体、增量数仓都得益于湖仓一体等数据湖技术…

数据分析硬核工具Origin各版本安装指南

下载链接 https://pan.baidu.com/s/12mENFtRFdNaLzVKmE6w_Uw?pwd0531 1.鼠标右击【Origin 2022(64bit)】压缩包&#xff08;win11及以上系统需先点击显示更多“选项”&#xff09;选择【解压到 Origin 2022(64bit)】。 2.双击打开解压后的【Origin 2022(64bit)】文件夹。 3.…

【Matlab】CNN卷积神经网络时序预测算法

资源下载&#xff1a; https://download.csdn.net/download/vvoennvv/88681558 一&#xff0c;概述 CNN&#xff08;Convolutional Neural Network&#xff0c;卷积神经网络&#xff09;是一种前馈神经网络&#xff0c;主要用于处理具有类似网格结构的数据&#xff0c;例如图像…

2023十大编程语言及未来展望

2023十大编程语言及未来展望 1. 2023年十大编程语言排行榜2. 十大编程语言未来展望PythonCCJavaC#JavaScriptPHPVisual BasicSQLAssembly language 1. 2023年十大编程语言排行榜 TIOBE排行榜是根据互联网上有经验的程序员、课程和第三方厂商的数量&#xff0c;并使用搜索引擎&a…

Python中的用户交互函数详解,提升用户体验!

更多Python学习内容&#xff1a;ipengtao.com 用户进行交互的Python应用程序&#xff0c;有许多常用的用户交互函数可以帮助创建更具吸引力和友好的用户界面。本文将介绍一些常用的Python用户交互函数&#xff0c;并提供详细的示例代码&#xff0c;以帮助大家更好地理解它们的用…