C++初阶学习————二叉树进阶(二叉搜索树)


前面的文章介绍过二叉树的基础概念以及完全二叉树的应用等等,本篇将介绍二叉搜索树

二叉搜索树的概念

二叉搜索树是一颗排序树,或者是一颗空树如图:

在这里插入图片描述
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树,并且它可以去重

二叉搜索树的操作

基本框架

template<class T>
struct BSTreeNode
{
	BSTreeNode<T>* _left;
	BSTreeNode<T>* _right;
	T _key;
	
	BSTreeNode(const T& key)
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
	{}
};
template<class T>
class BSTree
{
	typedef BSTreeNode<T> Node;
public:
	BSTree()
		:_root(nullptr)
	{}
private:
	Node* _root;
};

二叉搜索树的插入

例如插入6和10
在这里插入图片描述

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

	Node* cur = _root;
	Node* parent = cur;
	while (cur)
	{
		if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
			return false;
	}
	cur = new Node(key);
	if (parent->_key > key)
		parent->_left = cur;
	else
		parent->_right = cur;

	return true;
}

注意:会有树为空的情况需要判断

二叉搜索树的查找

查找的代码和插入非常类似,大于根去右边,小于根去左边,最多查找高度次
代码如下:

//Node* find(const T& key)
bool find(const T& key)
{
	if (_root == nullptr)
		return false;
	//return nullptr;

	Node* cur = _root;
	while (cur)
	{
		if (cur->_key > key)
			cur = cur->_left;
		else if (cur->_key < key)
			cur = cur->_right;
		else
			return true;
		//return cur;
	}
	return false;
	//return nullptr;
}

二叉搜索树的删除

这里的删除相对于插入和查找来说是比较难的,最重要的是不能改变二叉搜索树的结构,所以这里主要分为两种情况考虑

  1. 删除只有一个子节点或没有子节点
  2. 删除左右子节点都存在的

先找到要删除的节点,代码如下:

bool erase(const T& key)
{
	if (_root == nullptr)
		return false;

	Node* cur = _root;
	Node* parent = cur;
	while (cur)
	{
		if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else		
		{
			删除部分的代码........
		}
	}
	return false;	
}

1. 删除只有一个子节点或没有子节点

(1)删除叶子节点(删除9举例)
在这里插入图片描述

(2)删除只存在一个子节点的节点(删除8举例)
在这里插入图片描述

情况1的删除代码:

else
{
	if (cur->_left == nullptr)
	{
		if (cur == parent->_left)
			parent->_left = cur->_right;
		else
			parent->_right = cur->_right;
	}
	else if (cur->_right == nullptr)
	{
		if (cur == parent->_left)
			parent->_left = cur->_left;
		else
			parent->_right = cur->_left;
	}
	else
	{情况2部分的代码.......}
	delete cur;
}

除上述代码删除的示例外,另外三种情况
在这里插入图片描述
所以这里需要仔细处理链接,否则会破坏搜索树的结构

2.删除左右节点都存在的节点

(1)删除根节点
在这里插入图片描述
在这里插入图片描述

(2)删除其他存在左右子节点的节点
在这里插入图片描述
在这里插入图片描述
找到的右子树最小节点一定没有左节点,且它的父节点一定不为空;所以在交换或覆盖完数值后,剩下的步骤和情况一一样,并且这里不用单独判断删除的节点存在哪边的子节点(右子树的最小节点一定没有左节点,所以只可能存在右节点)
在这里插入图片描述
情况2的删除代码:

else
{
	Node* min_parent = cur;
	Node* min = cur->_right;

	while (min->_left)
	{
		min_parent = min;
		min = min->_left;
	}
	cur->_key = min->_key;

	if (min == min_parent->_left)
		min_parent->_left = min->_right;
	else
		min_parent->_right = min->_right;

	swap(min,cur);
}

在这里插入图片描述
以上就是删除主要步骤,但还有个问题就是要考虑只有一个节点,需要操作根节点。

if (_root->_key == key )
{
	if (_root->_left == nullptr)
		_root = _root->_right;
	else
		_root = _root->_left;
}

总体代码

bool erase(const T& key)
{
	if (_root == nullptr)
		return false;

	Node* cur = _root;
	Node* parent = cur;
	while (cur)
	{
		if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			if (_root->_key == key )
			{
				if (_root->_left == nullptr)
					_root = _root->_right;
				else
					_root = _root->_left;
			}
			else if (cur->_left == nullptr)
			{
				if (cur == parent->_left)
					parent->_left = cur->_right;
				else
					parent->_right = cur->_right;
			}
			else if (cur->_right == nullptr)
			{
				if (cur == parent->_left)
					parent->_left = cur->_left;
				else
					parent->_right = cur->_left;
			}
			else
			{
				Node* min_parent = cur;
				Node* min = cur->_right;

				while (min->_left)
				{
					min_parent = min;
					min = min->_left;
				}
				
				
				//把min摘出来,交换后删除cur
				if (min == min_parent->_left)
					min_parent->_left = min->_right;
				else
					min_parent->_right = min->_right;
				//目的是删除cur,所以先赋值,再交换
				cur->_key = min->_key;
				swap(min,cur);
			}
			delete cur;
			return true;
		}
	}
	return false;
}

整体代码

循环写法

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

		Node* cur = _root;
		Node* parent = cur;
		while (cur)
		{
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
				return false;
		}
		cur = new Node(key);
		if (parent->_key > key)
			parent->_left = cur;
		else
			parent->_right = cur;

		return true;
	}


	bool find(const T& key)
	{
		if (_root == nullptr)
			return false;
		//return nullptr;

		Node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)
				cur = cur->_left;
			else if (cur->_key < key)
				cur = cur->_right;
			else
				return true;
			//return cur;
		}
		return false;
		//return nullptr;
	}


	bool erase(const T& key)
	{
		if (_root == nullptr)
			return false;

		Node* cur = _root;
		Node* parent = cur;
		while (cur)
		{
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				if (_root->_key == key )
				{
					if (_root->_left == nullptr)
						_root = _root->_right;
					else
						_root = _root->_left;
				}
				else if (cur->_left == nullptr)
				{
					if (cur == parent->_left)
						parent->_left = cur->_right;
					else
						parent->_right = cur->_right;
				}
				else if (cur->_right == nullptr)
				{
					if (cur == parent->_left)
						parent->_left = cur->_left;
					else
						parent->_right = cur->_left;
				}
				else
				{
					Node* min_parent = cur;
					Node* min = cur->_right;

					while (min->_left)
					{
						min_parent = min;
						min = min->_left;
					}
					cur->_key = min->_key;

					if (min == min_parent->_left)
						min_parent->_left = min->_right;
					else
						min_parent->_right = min->_right;

					swap(min,cur);
				}
				delete cur;
				return true;
			}
		}
		return false;
	}

	//打印搜索树
	void print_BSTree()
	{
		_print_BSTree(_root);
		cout << endl;
	}
	void _print_BSTree(Node* root)
	{
		if (root == nullptr)
			return;

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

递归写法

1.插入

bool insertR(const T& key)
{
	return insertR(_root,key)
}
bool _insertR(Node*& root,const T& key)
{
	if(root == nullptrd)
	{
		root = new Node(key);
		return true;
	}
	if(root->_key > key)
		return insertR(root->_left,key);
	else if(root->_key < key)
		return insertR(root->_right,key);
	else
		return false;
}

注意,这里可以成功链接上,主要是运用了引用,递的是左右指针的引用
在这里插入图片描述

2.查找

Node* findR(const T& key)
{
	if()
}
bool _insertR(Node*& root, const T& key)
{
	if (root == nullptr)
	{
		root = new Node(key);
		return true;
	}
	if (root->_key > key)
		return _insertR(root->_left, key);
	else if (root->_key < key)
		return _insertR(root->_right, key);
	else
		return false;
}

3.删除

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

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

	if (root->_key > key)
		_EraseR(root->_left, key);
	else if (root->_key < key)
		_EraseR(root->_right, key);
	else
	{
		Node* era = root;
		if (root->_left == nullptr)
			root = root->_right;
		else if (root->_right == nullptr)
			root = root->_left;
		else
		{
			Node* min = root->_right;
			Node* min_parent = root;
			while (min->_left)
			{
				min_parent = min;
				min = min->_left;
			}
			swap(min->_key, root->_key);
			return _EraseR(root->_right, key);
		}
		delete era;
		return true;
	}
}

这里讲一下删除的思路:
情况一时,操作和递归的插入基本一样,通过使用引用就可直接令要删除节点的父节点与子节点直接相连,也不用再去判断删除节点是父节点的左边还是右边

情况二时,先找到右子树最小节点,再与要删除的节点交换数值,最后递归传递的节点是,要删除节点的左子树(情况二再交换完数值后就可以按照情况一去处理了)

在这里插入图片描述

注意:要提前记录一下找到的要删除的节点,中间过程会改变root的值,所以最后删除的是提前记录好的节点

总体代码

	bool insertR(const T& key)
	{
		return _insertR(_root,key);
	}
	Node* findR(const T& key)
	{
		return _findR(_root,key);
	}

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



private:
	bool _insertR(Node*& root, const T& key)
	{
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}
		if (root->_key > key)
			return _insertR(root->_left, key);
		else if (root->_key < key)
			return _insertR(root->_right, key);
		else
			return false;
	}

	Node* _findR(Node* root, const T& key)
	{
		if (root == nullptr)
			return nullptr;

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

	}

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

		if (root->_key > key)
			_EraseR(root->_left, key);
		else if (root->_key < key)
			_EraseR(root->_right, key);
		else
		{
			Node* era = root;
			if (root->_left == nullptr)
				root = root->_right;
			else if (root->_right == nullptr)
				root = root->_left;
			else
			{
				Node* min = root->_right;
				Node* min_parent = root;
				while (min->_left)
				{
					min_parent = min;
					min = min->_left;
				}
				swap(min->_key, root->_key);
				return _EraseR(root->_right, key);
			}
			delete era;
			return true;
		}
	}

二叉搜索树的应用

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

c++提供了这种键值对儿的结构,叫做pair

pair<string,int> p1("one",1);
p1.first就是key
p1.second就是value
make_pair("one",p1); 可以构造一个pair

在这里插入图片描述

二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。

但是会有极端情况:当插入的值顺着一个方向一直插入(即插入的值一直是最小或最大或者接近这种情况),那么二叉树就变成了单树,这时的查找效率就是极低了,即结点越深,则比较次数越多。

  • 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
  • 最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N
    在这里插入图片描述
    当变为单树就失去意义了(查找元素相当于在顺序表中搜索元素,效率低下)。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值