C++实现二叉搜索树

news2024/11/25 22:43:00

文章目录

  • 前言
  • 1.二叉树搜索树的相关介绍
  • 2.二叉搜索树的实现
    • 1.二叉搜索树插入数据的实现
    • 2.二叉搜索树的查找实现
    • 3.二叉搜索树的删除实现
    • 4.构造函数和析构函数以及赋值重载的实现
  • 3.二叉搜索树的应用
    • 1.将之前的K模型二叉搜索树改造成KV模型
    • 2.代码演示
  • 4.二叉搜索树的性能分析

前言

二叉搜索树是一种特别有用的数据结构,AVL树,红黑树的原型都是二叉搜索树。本文将会对二叉搜索树进行初步介绍,从而入门二叉搜索树,为以后深入学习AVL树和红黑树打下基础。


1.二叉树搜索树的相关介绍

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:若它的左子树不为空,则左子树上所有节点的值都小于根节点的值。若它的右子树不为空,则右子树上所有节点的值都大于根节点的值,它的左右子树也分别为二叉搜索树。

在这里插入图片描述
在这里插入图片描述

根据二叉搜索的特性我们知道左子树都比根节点小,右子树都比根节点大。因此我们中序遍历这颗树就是顺序遍历数据。C++中基于二叉搜索树实现的容器在遍历数据的时候都是采用中序遍历。

2.二叉搜索树的实现

知道了二叉树搜索的特性,我们就来实现二叉搜索树。我们先把树的结构和节点结构定义出来。

template<class K>
struct Node
{
	Node(const K& val)
	:_left(nullptr),_right(nullptr),_key(val)
	{
		;
	}
	Node<K>* _left;
	Node<K>*_right;
	K _key;
};
template<class K>
class BST
{
public:
	typedef Node<K> node;
private:
 node* _root;
 }

节点中key是用来存储数据的,leftl指针和righ指针是用来连接左右子树的,因为我们是用C++实现的,因此节点先定义出节点的结构体,之后在创建出二叉搜索树的结构体,这里为了方便就把节点类型重定义了一下。类模板中Node只是类名,类型是Node< k >.

1.二叉搜索树插入数据的实现

我们知道二叉搜索的特性,二叉搜索树主要是用来搜索的,因此一般来说二叉树中不允许出现重复的数据。根据二叉搜索树的特性,我们知道当插入的key比当前节点的key大就走右子树,如果插入的key比当前节点key小就走左子树,如果相等就插入失败。

在这里插入图片描述
非递归版本插入实现

bool Insert(const K& val)
	{
		if (_root == nullptr)
		{
			_root = new node(val);
			return true;
		}
		node* cur = _root;
		node* parent = nullptr;
		while (cur)
		{
			if (cur->_key < val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		cur = new node(val);
		if (val < parent->_key)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		return true;
	}

我们通过一个cur节点一路比较key值遍历,根据比较结果选择遍历路径走到空的时候,这个时候就该链接节点了,为了连接上节点,我们一路遍历的时候需要用一个paent节点记录cur的父节点,最后根据parents的key与插入的key的大小关系选择链接到parents的哪个左右指针上。上述代码核心在于根据大小关系选择要插入的左右子树,while循环就是干这件事,插入的数据为了能够链接上,就需要parents节点。

递归实现插入数据

//注意这里的root参数是引用这是一个技巧
	bool _InsertR(node*& root, const K& val)
	{
		if (root == nullptr)
		{
			root = new node(val);
			return true;
		}
		if (val > root->_key)
		{
			_InsertR(root->_right, val);
		}
		else if (val < root->_key)
		{
			_InsertR(root->_left, val);
		}
		else
		{
			return false;
		}

	}
	bool InsertR(const K&val)
	{
		return _InsertR(_root,val);
	}

在使用的递归的时候,一般都是根据树的节点来访问树,在类中可以天然的访问到root节点,因此我们写个子函数,插入函数调用这个子函数即可。要递归子树所以这个参数肯定是不能省略的,可能有人会说可以给个_root缺省值,但是这也是不行的,缺省值只能是常量和全局变量,而将_root设为缺省值需要用到this指针。所以这个子函数是最好的解决方法。

注意这里的参数是是引用,这样就能能把每个节点顺利链接上了,这是一个细节技巧

2.二叉搜索树的查找实现

二叉搜索树的查找实现也很简单和插入类似,如果要查找的值比当前节点的key大就去当节点的右树去查找,如果要查找的值比当前节点的key小就去当前节点左树去查找。如果相等就说明找到了,这样不断比较遍历即可。

非递归实现二叉搜索树查找

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

我们再来看看二叉搜索树的递归实现查找

bool _FindR( node*& root, const K& val)
	{
		if (root == nullptr)
		{
			return false;
		}
		if (root->_key == val)
		{
			return true;
		}
		else if (root->_key > val)
		{
			return _FindR(root->_left, val);
		}
		else
		{
			return _FindR(root->_right, val);
		}
		return false;
	}
	bool FindR(const K& val)
	{
		return _FindR(_root, val);
	}

这里递归也很简单,val比当前节点的key小就递归左树查找,val比当前节点的key大就递归右树查找。

3.二叉搜索树的删除实现

二叉搜索树删除节点的实现才是二叉树搜索最重要的点,这也是二叉搜索树接口中最难实现的一个点。我们来分析一下二叉搜索树删除节点的几种情况。

在这里插入图片描述

二叉树搜索节点的删除难点在于该节点可能有孩子节点,删除这个节点后,它的孩子节点应该怎么妥善处理才能删除后的树依然是一颗二叉搜索树。其实,通过上面的分析删除节点可以分为两大类,该节点有只有一个孩子和该节点只有两个节点。该节点没有孩子都可以采用该节点只有一个孩子的处理方式,即托孤处理。如果当前节点是在它父节点的左树上,那么对于当前节点的父节点来说,唯一的孙子的节点一定是要链接在爷爷节点的左树上的。如果当前节点是在它父节点的右树上,那么对于当前节点的父节点来说,唯一的孙子的节点一定是要链接在爷爷节点的右树上的。这就是托孤处理,让爷爷节点来管理孙子节点。

如果要删除的节点有两个节点怎么办呢?我们只用在该节点左右子树选择一个适合的节点的key替代要删除节点的key,之后在删除这个适合的节点节点即可。这个合适的节点是要删除节点的左子树中的最右节点或者右子树中最左节点。因为该节点是最右节点或者最左节点因此它肯定是没有孩子节点,所以直接删除即可。不用在处理孩子节点了。这是一种替代删除法

代码示例

bool erase(const K& val)
	{    //删除节点要先找到将要删除的节点
		node* parent = nullptr;
		node* cur = _root;
		if (_root == nullptr)
		{
			//cout << "Tree NULL" << endl;
			return false;
		}
		while (cur)
		{
			if (cur->_key > val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				//找到节点开始删除 
				//分为3种大情况 cur 左为空 右为空 或者左右都为空
				//1.左为空
				if (cur->_left == nullptr)
				{    //如果删除的是根节点单独处理
					if (cur == _root)
					{
						_root = cur->_right;
						delete cur;
						return true;
					}
					else
					{
						//判断cur是parent的右孩子还是右孩子
						if (parent->_left == cur)
						{
							parent->_left = cur->_right;
							delete cur;
							return true;
						}
						else
						{
							parent->_right = cur->_right;
							delete cur;
							return true;
						}
					}
				}
				//2.右为空
				else if (cur->_right == nullptr)
				{   //1.cur为根节点单独判断
					if (cur == _root)
					{
						_root = cur->_left;
						delete cur;
						return true;
					}
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_left;
							delete cur;
							return true;
						}
						else
						{
							parent->_right = cur->_left;
							delete cur;
							return true;
						}
					}
				}
				//3.左右都不为空
				else
				{
				//找当前节点的左树的最右节点或者右数的最左节点进行替换
					/*注意不能这样写,
					如果cur->_left就是左树的的最右节点就会有问题
					node pmaxleft=nullptr;
					* node maxleft=cur->_left;
					*/
					node* cur_parent = cur;
					node* left_max = cur->_left;
					while (left_max->_right)
					{
						cur_parent = left_max;
						left_max = left_max->_right;
					}
					//替换key值
					cur->_key = left_max->_key;
					if (cur_parent->_left == left_max)
					{
						cur_parent->_left = left_max->_left;
					}
					else
					{
						cur_parent->_right = left_max->_left;
					}
					delete left_max;
					return true;
				}
			}
		}
		return false;
	}

先找到要删除的节点,在找节点的过程中需要记录该节点的父节点。找到以后在判断该节点的孩子节点哪一个为空,对于第一种情况:左为空,我们需要链接右孩子,右为空,我们需要链接左孩子,在链接的时候还需要判断爷爷节点和父亲节点的左右关系,才能确定这个孙子节点和爷爷节点链接关系,这里需要单独处理一下参删除的节点是根节点的情况。对于第二种情况:上述代码中我是找左树的最右节点来进行cur节点key值的替换。左树的最右节点是左子树中的最大节点,右树的最左节点就是右树的最小节点,这两个节点的key刚好符合根节点key的特性。这样删除之后这颗树依旧是一颗二叉搜索树。

那我们再来看看这个删除节点递归实现

递归实现删除节点

bool EraseR(const K& val)
	{
		return _EraseR(_root, val);
	}
	bool  _EraseR(node*& root, const K& val)
	 {
		if (root == nullptr)
		{  
			//cout << "Tree NULL" << endl;
			return false;
		}
		 if (root->_key > val)
		{
			_EraseR(root->_left, val);
		}
		 else if (root->_key < val)
		 {
			 _EraseR(root->_right, val);
		 }
		 else
		 {
			 node* del = root;
			 if (root->_left == nullptr)
			 {
				 root = root->_right;
			 }
			 else if (root->_right == nullptr)
			 {
				 root = root->_left;
			 }
			 else
			 {
				 node* maxleft = root->_left;
				 while (maxleft->_right)
				 {
					 maxleft = maxleft->_right;
				 }
				 swap(maxleft->_key, del->_key);
				 return _EraseR(root->_left, val);
			 }
			 delete del;
			 return true;
		 }
		 return false;
	}

这里递归的时候引用起了很大的作用。

在这里插入图片描述

这里我们就不用parents指针了,就很方便。当要删除的节点左右孩子都不为空时,还是使用替换删除的方法,这里依旧是找左子树的最右节点的key值作为根节点新的key值,不过这这里是交换key值,之后在递归到要删除节点的左子树中进行删除,这样一定会遇到要删除的节点是左右孩子为空的情况,从而实现了正确的递归删除。


4.构造函数和析构函数以及赋值重载的实现

这里默认的构造函数直接将根节点初始化为nullptr即可,我们主要来实现一下这个拷贝构造。

BST(const BST<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;
	}
	BST()
		:_root(nullptr)
	{

	}

这里拷贝构造调用capy函数直接递归复制创建一颗二叉树即可。

赋值重载

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

赋值重载的话还是和以前的方法我们直接使swap函数交换两棵树的根节点即可,这里swap函数是传值传参,会调用拷贝构造函数创建出一个t,并不会影响到这个实际的赋值对象。


析构函数

~BST()
	{
		Destroy(_root);
		_root = nullptr;
	}
	void Destroy(node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
	}

这里析构函数调用Destroy函数也是使用递归进行节点空间的释放,当然这里参数也可以设设置引用,在Destroy函数中将根节点置空,这里要注意需要采用后序遍历方式进行删除,也就是自底向上删除节点。

完整代码

#include<iostream>
using namespace std;
template<class K>
struct Node
{
	Node(const K& val)
	:_left(nullptr),_right(nullptr),_key(val)
	{
		;
	}
	Node<K>* _left;
	Node<K>*_right;
	K _key;
};

template<class K>
class BST
{
public:
	typedef Node<K> node;
	bool Find(const K& val)
	{
		node* cur = _root;
		while (cur)
		{
			if (cur->_key > val)
			{
				cur = cur->_left;
			}
			else if (cur->_key < val)
			{
				cur = cur->_right;
			}
			else
			{
				return true;
			}
		}
		return false;
	}
	void _Inorder(node*root)
	{
		if (root == nullptr)
		{   
			return;
		}
		_Inorder(root->_left);
		cout << root->_key << " ";
		_Inorder(root->_right);
	
	}
	void Inorder()
	{   
		if (_root == nullptr)
		{
			cout << "Tree NULL" << endl;
			return;
		}
		_Inorder(_root);
		cout << endl;
	}
	bool Insert(const K& val)
	{
		if (_root == nullptr)
		{
			_root = new node(val);
			return true;
		}
		node* cur = _root;
		node* parent = nullptr;
		while (cur)
		{
			if (cur->_key < val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		cur = new node(val);
		if (val < parent->_key)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		return true;
	}
	bool erase(const K& val)
	{    //删除节点要先找到将要删除的节点
		node* parent = nullptr;
		node* cur = _root;
		if (_root == nullptr)
		{
			//cout << "Tree NULL" << endl;
			return false;
		}
		while (cur)
		{
			if (cur->_key > val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				//找到节点开始删除 
				//分为3种大情况 cur 左为空 右为空 或者左右都为空
				//1.左为空
				if (cur->_left == nullptr)
				{    //如果删除的是根节点单独处理
					if (cur == _root)
					{
						_root = cur->_right;
						delete cur;
						return true;
					}
					else
					{
						//判断cur是parent的右孩子还是右孩子
						if (parent->_left == cur)
						{
							parent->_left = cur->_right;
							delete cur;
							return true;
						}
						else
						{
							parent->_right = cur->_right;
							delete cur;
							return true;
						}
					}
				}
				//2.右为空
				else if (cur->_right == nullptr)
				{   //1.cur为根节点单独判断
					if (cur == _root)
					{
						_root = cur->_left;
						delete cur;
						return true;
					}
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_left;
							delete cur;
							return true;
						}
						else
						{
							parent->_right = cur->_left;
							delete cur;
							return true;
						}
					}
				}
				//3.左右都不为空
				else
				{
				//找当前节点的左树的最右节点或者右数的最左节点进行替换
					/*注意不能这样写,
					如果cur->_left就是左树的的最右节点就会有问题
					node pmaxleft=nullptr;
					* node maxleft=cur->_left;
					*/
					node* cur_parent = cur;
					node* left_max = cur->_left;
					while (left_max->_right)
					{
						cur_parent = left_max;
						left_max = left_max->_right;
					}
					cur->_key = left_max->_key;
					if (cur_parent->_left == left_max)
					{
						cur_parent->_left = left_max->_left;
					}
					else
					{
						cur_parent->_right = left_max->_left;
					}
					delete left_max;
					return true;
				}
			}
		}
		return false;
	}
	//注意这里的root参数是引用这是一个技巧
	bool _InsertR(node*& root, const K& val)
	{
		if (root == nullptr)
		{
			root = new node(val);
			return true;
		}
		if (val > root->_key)
		{
			_InsertR(root->_right, val);
		}
		else if (val < root->_key)
		{
			_InsertR(root->_left, val);
		}
		else
		{
			return false;
		}

	}
	bool InsertR(const K&val)
	{
		return _InsertR(_root,val);
	}
	bool _FindR( node*& root, const K& val)
	{
		if (root == nullptr)
		{
			return false;
		}
		if (root->_key == val)
		{
			return true;
		}
		else if (root->_key > val)
		{
			return _FindR(root->_left, val);
		}
		else
		{
			return _FindR(root->_right, val);
		}
		return false;
	}
	bool FindR(const K& val)
	{
		return _FindR(_root, val);
	}
	bool EraseR(const K& val)
	{
		return _EraseR(_root, val);
	}
	bool  _EraseR(node*& root, const K& val)
	 {
		if (root == nullptr)
		{  
			//cout << "Tree NULL" << endl;
			return false;
		}
		 if (root->_key > val)
		{
			_EraseR(root->_left, val);
		}
		 else if (root->_key < val)
		 {
			 _EraseR(root->_right, val);
		 }
		 else
		 {
			 node* del = root;
			 if (root->_left == nullptr)
			 {
				 root = root->_right;
			 }
			 else if (root->_right == nullptr)
			 {
				 root = root->_left;
			 }
			 else
			 {
				 node* maxleft = root->_left;
				 while (maxleft->_right)
				 {
					 maxleft = maxleft->_right;
				 }
				 swap(maxleft->_key, del->_key);
				 return _EraseR(root->_left, val);
			 }
			 delete del;
			 return true;
		 }
		 return false;
	}
	void Destroy(node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
	}
	~BST()
	{
		Destroy(_root);
		_root = nullptr;
	}
	BST(const BST<K>& t)
	{
		_root = Copy(t._root);
	}
	BST<K> operator=(const BST<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;
	}
	BST()
		:_root(nullptr)
	{

	}
private:
	node* _root=nullptr;
};

3.二叉搜索树的应用

听名字就知道二叉搜索树的主要引用场景是用来搜索,这里二叉搜索树主要有两种模型。

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

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

1.将之前的K模型二叉搜索树改造成KV模型

namespace key_val
{
	template<class K,class V>
	struct Node
	{
		Node(const K& key,const V&val)
			:_left(nullptr), _right(nullptr), _key(key),_value(val)
		{
			;
		}
		Node<K,V>* _left;
		Node<K,V>* _right;
		K _key;
		V _value;
	};

	template<class K,class V>
	class BST
	{
	public:
		typedef Node<K,V> node;
		node* 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 cur;
				}
			}
			return nullptr;
		}
		void _Inorder(node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			_Inorder(root->_left);
			cout << root->_key << " " << root->_value << endl;
			_Inorder(root->_right);

		}
		void Inorder()
		{
			if (_root == nullptr)
			{
				cout << "Tree NULL" << endl;
				return;
			}
			_Inorder(_root);
			cout << endl;
		}
		bool Insert(const K& key,const V&val)
		{
			if (_root == nullptr)
			{
				_root = new node(key,val);
				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;
				}
			}
			cur = new node(key,val);
			if (key < parent->_key)
			{
				parent->_left = cur;
			}
			else
			{
				parent->_right = cur;
			}
			return true;
		}
		bool erase(const K& val)
		{    //删除节点要先找到将要删除的节点
			node* parent = nullptr;
			node* cur = _root;
			if (_root == nullptr)
			{
				//cout << "Tree NULL" << endl;
				return false;
			}
			while (cur)
			{
				if (cur->_key > val)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_key < val)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					//找到节点开始删除 
					//分为3种大情况 cur 左为空 右为空 或者左右都为空
					//1.左为空
					if (cur->_left == nullptr)
					{    //如果删除的是根节点单独处理
						if (cur == _root)
						{
							_root = cur->_right;
							delete cur;
							return true;
						}
						else
						{
							//判断cur是parent的右孩子还是右孩子
							if (parent->_left == cur)
							{
								parent->_left = cur->_right;
								delete cur;
								return true;
							}
							else
							{
								parent->_right = cur->_right;
								delete cur;
								return true;
							}
						}
					}
					//2.右为空
					else if (cur->_right == nullptr)
					{   //1.cur为根节点单独判断
						if (cur == _root)
						{
							_root = cur->_left;
							delete cur;
							return true;
						}
						else
						{
							if (parent->_left == cur)
							{
								parent->_left = cur->_left;
								delete cur;
								return true;
							}
							else
							{
								parent->_right = cur->_left;
								delete cur;
								return true;
							}
						}
					}
					//3.左右都不为空
					else
					{
						//找当前节点的左树的最右节点或者右数的最左节点进行替换
							/*注意不能这样写,
							如果cur->_left就是左树的的最右节点就会有问题
							node pmaxleft=nullptr;
							* node maxleft=cur->_left;
							*/
						node* cur_parent = cur;
						node* left_max = cur->_left;
						while (left_max->_right)
						{
							cur_parent = left_max;
							left_max = left_max->_right;
						}
						cur->_key = left_max->_key;
						if (cur_parent->_left == left_max)
						{
							cur_parent->_left = left_max->_left;
						}
						else
						{
							cur_parent->_right = left_max->_left;
						}
						delete left_max;
						return true;
					}
				}
			}
			return false;
		}
	
		void Destroy(node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			Destroy(root->_left);
			Destroy(root->_right);
			delete root;
		}
		~BST()
		{
			Destroy(_root);
			_root = nullptr;
		}
		BST(const BST<K,V>& t)
		{
			_root = Copy(t._root);
		}
		BST<K,V> operator=(const BST<K,V> t)
		{
			swap(_root, t._root);
			return *this;
		}
		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;
		}
		BST()
			:_root(nullptr)
		{

		}
	private:
		node* _root;
	};


}

在K模型的基础上加一个模板参数,在插入的时候除了插入key之外还插入了val,其他方面大致不变,Copy函数在new节点的时多需要对val值也初始化一下,还有这个node节点类的构造函数稍微修改一下即可。

2.代码演示

#include"BinarySearchTree.h"
#include<vector>
#include<stdlib.h>
#include <time.h>
void t1()
{
	BST<int> tree;
	vector<int>tem(8, 0);
	srand((unsigned int)time(NULL));
	for (int i = 1; i <= 8; i++)
	{
		int val = rand() % 20;
		//tree.Insert(val);
		tree.InsertR(val);
		tem.push_back(val);
	}
	tree.Inorder();
	for (int i = 0; i < 8; i++)
	{
		bool r1 = tree.Find(tem[i]);
		bool r2 = tree.Find(tem[i] + 1);
		cout << r1 << " " << r2 << " ";
	}
	cout << endl;
	for (int i = 0; i < tem.size(); i++)
	{
		tree.erase(tem.back());
		tree.Inorder();
		tem.pop_back();
	}
	tree.Inorder();
	cout << "t1" << endl;
	
}
void t2()
{
	BST<int> tree;
	vector<int>tem(8, 0);
	srand((unsigned int)time(NULL));
	for (int i = 1; i <= 8; i++)
	{
		int val = rand() % 20;
		tree.InsertR(val);
		tem.push_back(val);
	}
	tree.Inorder();
	for (int i = 0; i < 8; i++)
	{
		bool r1 = tree.FindR(tem[i]);
		bool r2 = tree.FindR(tem[i] + 1);
		cout << r1 << " " << r2 << " ";
	}
	cout << endl;
	for (int i = 0; i < tem.size(); i++)
	{
		tree.EraseR(tem.back());
		tree.Inorder();
		tem.pop_back();
	}
	tree.Inorder();
	cout << "t2" << endl;
}
int main()
{
	t1();
	t2();
	/*BST<int>t1;
	t1.Insert(1);
	t1.Insert(4);
	t1.Insert(6);
	t1.Insert(7);
	t1.Insert(1);
	BST<int>t2(t1);
	t2.Inorder();*/
	key_val::BST<string, string>t;
	t.Insert("sort", "排序");
	t.Insert("red", "红色");
	t.Insert("big", "大");
	t.Insert("end", "结束");
	t.Insert("left", "左边");
	t.Insert("right", "右边");
	string s1;
	while (cin >> s1)
	{
		auto ret= t.Find(s1);
		if (ret)
		{
			cout << ret->_value << endl;
		}
		else
		{
			cout << "无此单词" << endl;
		}
	}
	key_val::BST<string, int>t2;
	string array[] = { "苹果","橘子","香蕉","苹果","梨","橙子","哈密瓜","橘子" };
	for (string e : array)
	{
		auto ret = t2.Find(e);
		if (ret == nullptr)
		{
			t2.Insert(e, 1);
		}
		else
		{
			ret->_value++;
		}
	}
	t2.Inorder();
	return 0;
}

在这里插入图片描述

这里简单演示了一下这个水果出现的次数和简单的单词互译。

4.二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树.

在这里插入图片描述

根据插入节点的大小关系,二叉搜索树可能出现不平衡的状态,这样二叉搜索树就会退化成单链表的形式。因此最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其单次查找次数为:logN。最差情况下,二叉搜索树退化为单支树(或者类似单支),其单次查找次数为:N.如果退化成单支树,二叉搜索树的性能就失去了,因此出现了AVL树以及红黑树,它们是平衡的二叉搜索树不会出现上述退化成单链表的情况,查找搜索效率很高。后续我将介绍这两种数据结构。

以上内容如有问题,欢迎指正!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

本地Linux服务器安装宝塔面板,并公网远程登录【内网穿透】

文章目录 前言1. 安装宝塔2. 安装cpolar内网穿透3. 远程访问宝塔4. 固定http地址5. 配置二级子域名6. 测试访问二级子域名 前言 宝塔面板作为建站运维工具&#xff0c;它支持一键LAMP/LNMP/集群/监控/网站/FTP/数据库/JAVA等100多项服务器管理功能&#xff0c;可提高运维效率。…

人工智能导论:模型与算法,附录实验三:线性回归模型

本人研一&#xff0c;简单记录下上课的大作业&#xff0c;如果你不会自己写&#xff0c;相信你有缘能找到这篇博客 线性回归模型 1.实验内容 图像是一种非常常见的信息载体&#xff0c;但是在图像的获取、传输、存储过程中可能由于各种原因使得图像受到噪声的影响。如何去除…

程序员如何通过创作图文项目实现自己的代码价值

本文阅读适合人群&#xff1a; 1.程序员 2.图文赛道创业者 项目背景&#xff1a; 毫无疑问&#xff0c;这是一个优质内容的时代&#xff0c;从某种意义上说&#xff0c;优质内容Money。优质内容的形式包含很多&#xff0c;在头条的定义中包括&#xff1a;纯文字&#xff08;…

存储资源调优技术——SmartTier智能分级技术、SmartQoS智能服务质量控制技术

目录 SmartTier智能分级技术 基本概述 工作原理 实现SmartThier数据迁移的三个阶段 应用场景 SmartQoS智能服务质量控制 基本概念 两种关键技术 应用场景 SmartTier智能分级技术 基本概述 自动将不同活跃的数据和不同特点的存储介质动态匹配&#xff0c;提高性能。 NL…

ETL工具 - Kettle 转换算子介绍

一、Kettle 转换算子 上篇文章对 Kettle 中的输入输出算子进行了介绍&#xff0c;本篇文章继续对转换算子进行讲解。 下面是上篇文章的地址&#xff1a; ETL工具 - Kettle 输入输出算子介绍 转换是ETL里面的T&#xff08;Transform&#xff09;&#xff0c;主要做数据转换&am…

开放式基金净值估算数据 API 数据接口

开放式基金净值估算数据 API 数据接口 全量基金数据&#xff0c;实时数据&#xff0c;所有基金数据。 1. 产品功能 返回实时开放式基金净值估值可定义所有基金估值数据&#xff1b;多个基金属性值返回&#xff1b;多维指标&#xff0c;一次查询毫秒级返回&#xff1b;数据持续…

领域驱动设计事件驱动框架命令查询责任分离测试驱动开发

领域驱动设计: DDD 事件驱动框架: Event Driven Architecture 命令查询责任分离: CQRS(Command Query Responsibility Segregation) 测试驱动开发: TDD 先睹为快&#xff1a;架构图 入口&#xff1a;entrypoint 入口是系统外部客户访问系统内部的端口。常见的入口如http, …

算法刷题|647.回文子串、516.最长回文子序列

回文子串 题目&#xff1a;给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子字符串 是字符串中的由连续字符组成的一个序列。 具有不同开始位置或结束位置的子串&#xff0c;即使是由相同的字符…

【redis】redis分布式锁(三)自动续期

系列文章目录 【redis】redis分布式锁&#xff08;一&#xff09;手写分布式锁1.0~6.0 【redis】redis分布式锁&#xff08;二&#xff09;可重入锁设计模式 文章目录 系列文章目录前言一、CAPredis集群 APZooKeeper集群 CP 数据一致性Eureak集群 APnacos集群 AP 二、lua脚本脚…

1 认识仿真工具Packet Tracer【实验】【计算机网络】

1 认识仿真工具Packet Tracer【实验】【计算机网络】 前言推荐1 认识仿真工具Packet Tracer1.1账号注册与Packet Tracer软件下载1.1.1 下载1.1.2 安装 1.2 Packet Tracer界面简介1.2.1 总述1.2.2 详细 1.3网络拓扑构建与设备模块添加1.3.1如何往工作区中添加设备1.3.2添加连线1…

【计算机网络】1.1——因特网概述

因特网概述&#xff08;了解&#xff09; 网络、互联网和因特网 网络由若干结点和连接这些结点的链路组成 多个网络还可以通过路由器互连起来&#xff0c;互联网是"网络的网络“ internet 和 Internet internet&#xff08;互联网或互连网&#xff09;是通用名词 泛指…

Java 基础入门篇(四)——— 方法的重载与参数传递机制

文章目录 一、方法的定义二、方法的参数传递机制 ★2.1 基本类型的参数传递2.2 引用类型的参数传递 三、方法重载 一、方法的定义 方法的作用&#xff1a;封装一段代码的语法结构&#xff0c;可以被重复调用&#xff0c;以此提高代码的复用性&#xff0c;提高开发效率&#xf…

操作系统之线程

线程 一、产生原因 最开始的并发&#xff0c;只能更具切换进程的方式去交替执行进程来宏观上并发&#xff0c;但是切换进程需要切换执行进程的环境&#xff0c;有很大的系统开销&#xff0c;所以产生了线程&#xff0c;让其可以不用频繁的切换进程&#xff0c;使得线程成为CPU…

【论文笔记 fintune 】羊驼

1.论文 这些开发由两个关键组件提供动力&#xff1a;大型预训练语言模型&#xff08;LM&#xff09;和人工编写的指令数据。 1.1 背景 目前的训练太吃数据了&#xff0c;需要使用一些替代方法减少对数据的依赖有微调过的数据集效果远好于没有微调 用什么数据集微调呢&#x…

Collaborative Metric Learning(www 2017)

Background 传统基于MF的模型&#xff0c;将特征向量的点积作为预测结果&#xff0c;这存在一个很大的问题&#xff0c;即dot product 不满足三角不等式&#xff09;&#xff0c;这会导致学到的特征向量次优&#xff08;难以捕捉用户细粒度的偏好&#xff09;。本文提出了一种…

【Java笔试强训 20】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 一、选择题 二、编程题 &#x1f525;字符串反…

第十八章 协程

我们知道脚本都是继承自MonoBehaviour类&#xff0c;而其中的Update方法里面放置了大部分的游戏逻辑处理代码。Update方法是游戏循环的每一帧都去执行&#xff0c;这就要求我们的代码“无时无刻”不在处理所有的可能发生的情况&#xff0c;并做出相应的处理。如果我们想要完成“…

Vue之render函数

概述 render函数从名字上看就可以看出&#xff0c;它是一个用于渲染的函数&#xff0c;在Vue中我们要将我们写的界面展示到屏幕上时&#xff0c;使用的方式都是组件中的template:标签下编写内容后&#xff0c;由Vue将我们编写的界面渲染到屏幕上。而这个render函数就是比templ…

B. Divide Candies(数学 + 思维)

Problem - B - Codeforces Arkady和他的朋友们喜欢在一个n n的棋盘上玩跳棋。这个棋盘的行和列从1到n编号。 他的朋友们最近赢了一场比赛&#xff0c;所以Actady想用一些糖果来取悦他们。记得一则古老寓言(但不记得寓意)》&#xff0c;Arlady想给他的朋友们每个格子一个糖果组…

SpringMVC学习总结(一)SpringMVC简介入门案例

SpringMVC学习总结&#xff08;一&#xff09;SpringMVC简介/入门案例 一、SpringMVC简介 &#xff08;一&#xff09;什么是MVC MVC是一种软件架构的思想&#xff0c;将软件按照模型、视图、控制器来划分。 M&#xff1a;Model&#xff0c;模型层&#xff0c;指工程中的Ja…