【数据结构高阶】二叉搜索树

接下来我们来开始使用C++来详细讲解数据结构的一些高阶的知识点

本期讲解的是二叉搜索树,对于初阶二叉树有所遗忘的同学可以看到这里:

【精选】【数据结构初阶】链式二叉树的解析及一些基本操作

讲解二叉搜索树主要是为了后面的map和set做铺垫,废话不多说我们直接上干货:


目录

一、二叉搜索树的概念

二、模拟实现二叉搜索树

2.1 插入数据

2.1.1 插入数据的递归实现

2.2 遍历数据

2.3 查找数据

2.4 删除数据

2.4.1 删除数据的递归实现

2.5 二叉搜索树的析构

2.6 二叉搜索树的拷贝构造

2.7 赋值重载

2.8 模拟实现二叉搜索树的全部代码

三、二叉搜索树的应用

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


一、二叉搜索树的概念

二叉搜索树又称二叉排序树(BST, Binary Search Tree),它可以是一棵空树,或者是具有以下性质的二叉树:

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

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

它的左右子树也分别为二叉搜索树

 例如:

我们可以发现的一点:无论是什么样的二叉搜索树,使用中序遍历,遍历出的值都是升序排列的

二、模拟实现二叉搜索树

下面又到了我们最激动人心的代码实现环节,本次代码实现我们还是要基于链式二叉树的实现:

template<class K>
struct BSTreeNode//节点
{
	BSTreeNode<K>* _lchild;
	BSTreeNode<K>* _rchild;
	K _key;

	BSTreeNode(const K& key)
		:_lchild(nullptr),
		_rchild(nullptr),
		_key(key)
	{}
};

2.1 插入数据

我们可以根据二叉搜索树的规律来向其中插入数据,但是插入数据时需要注意一点:要记录插入节点的上一个父节点,将插入的节点连接上二叉树:

template<class K>
class BSTree 
{
	typedef BSTreeNode<K> Node;
public:
	bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			_root->_key = key;
			return true;
		}
		Node* cur = _root;//使用cur遍历二叉树找到合适的插入位置
		Node* parent = cur;//记录cur的父节点
		while (cur)
		{
			parent = cur;
			if (key < cur->_key)
			{
				cur = cur->_lchild;
			}
			else if (key > cur->_key)
			{
				cur = cur->_rchild;
			}
			else
			{
				return false;//这里创建的二叉搜索树不允许出现值的冗余
			}
		}
		cur = new Node(key);//创建
		//将创建的节点链接到二叉树上
		if (key < parent->_key)
		{
			parent->_lchild = cur;
		}
		else
		{
			parent->_rchild = cur;
		}
		return true;
	}

private:
	Node* _root = nullptr;//根节点
};

2.1.1 插入数据的递归实现

递归的效率并没有循环高,那为什么要说一下插入数据的非递归实现呢

主要是非递归的数据插入的传值方法值得一说:

template<class K>
class BSTree 
{
	typedef BSTreeNode<K> Node;
public:
	bool InsertR(const K& key)//插入数据(递归)
	{
		return _InsertR(_root, key);
	}

	bool _InsertR(Node*& root,const K& key)//这里使用指针的引用用来直接修改其父节点指针的指向
	{
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}
		if (root->_key > key)
		{
			_InsertR(root->_lchild, key);
		}
		else if (root->_key < key)
		{
			_InsertR(root->_rchild, key);
		}
		else
		{
			return false;
		}
	}
private:
	Node* _root = nullptr;//根节点
};

我们可以看到在递归时,传入的形参类型为Node*&,这样可以直接在其函数内部习惯其父节点孩子指针的指向

那为什么要写两个插入函数呢?因为如果我们直接使用_InsertR函数,无法直接使用对象对_InsertR函数进行传参

2.2 遍历数据

因为二叉搜索树的性质,这里我们采用中序遍历: 

template<class K>
class BSTree 
{
	typedef BSTreeNode<K> Node;
public:
	bool Insert(const K& key)插入数据
	{
        ....
	}

	void InOrder()//中序遍历
	{
		_InOrder(_root);
		cout << endl;
	}

	void _InOrder(Node* root)
	{
		if (root == NULL)//如果是空树就直接结束
		{
			return;
		}
		_InOrder(root->_lchild);//先递归遍历其左子树
		cout << root->_key << " ";//再遍历其根节点
		_InOrder(root->_rchild);//最后递归遍历其右子树
	}

private:
	Node* _root = nullptr;//根节点
};

那为什么要写两个中序遍历函数呢?因为如果我们直接使用_InOrder函数,无法直接使用对象对_InOrder函数进行传参

2.3 查找数据

template<class K>
class BSTree 
{
	typedef BSTreeNode<K> Node;
public:
	bool Insert(const K& key)//插入数据
	{
        ......
	}

	void InOrder()//中序遍历
	{
        ......
	}

	void _InOrder(Node* root)
	{
        ......
	}

	bool Find(const K& key)//查找数据
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)
			{
				cur = cur->_rchild;
			}
			else if (cur->_key > key)
			{
				cur = cur->_lchild;
			}
			else
			{
				return true;
			}
		}
		return false;
	}

private:
	Node* _root = nullptr;//根节点
};

2.4 删除数据

对于在二叉搜索树中删除数据,我们就要好好说道说道了

下面我们有这样的一个二叉搜索树:

现在我们要删除其叶子节点,这很容易,直接删除完,再将其父节点对应的孩子指针置空即可

那我们要删只有一个孩子节点的数据呢?例如14和10

这个稍微麻烦一点,删除完该节点后将其孩子节点托付给其父节点即可:

那要删带有两个孩子节点的数据呢?例如3、6、8:

对于这种情况我们可以选择其节点下的左子树的最大节点(左子树的最右节点),或者右子树的最小节点(右子树的最左节点)来替代要删除的节点:

综上所述,要删除的结点可能分下面四种情况:

a. 要删除的结点无孩子结点

b. 要删除的结点只有左孩子结点 

c. 要删除的结点只有右孩子结点

d. 要删除的结点有左、右孩子结点

看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:

情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除

情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除

情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题--替换法删除

思路我们有了,下面用代码来实现一下:

template<class K>
class BSTree 
{
	typedef BSTreeNode<K> Node;
public:
	bool Insert(const K& key)//插入数据
	{
        ...
	}

	void InOrder()//中序遍历
	{
        ...
	}

	void _InOrder(Node* root)
	{
        ...
	}

	bool Find(const K& key)//查找数据
	{
        ...
	}

	bool Erase(const K& key)
	{
		Node* cur = _root;//使用cur遍历二叉树找到要删除元素的位置
		Node* parent = cur;//记录cur的父节点
		while (cur)//寻找需要删除的节点
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_rchild;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_lchild;
			}
			else//找到了,开始删除
			{
				if (cur->_lchild == cur->_rchild && cur->_lchild == nullptr)//删除的节点为叶子节点
				{
					if (parent == cur)//删除的是根节点
					{
						_root = nullptr;//更新根节点
					}
					//将其父节点指向的自身的指针置空
					else if (parent->_lchild == cur)
					{
						parent->_lchild = nullptr;
					}
					else
					{
						parent->_rchild = nullptr;
					}
					//释放节点空间
					delete cur;
					cur = nullptr;
				}
				else if (cur->_rchild == nullptr)//删除的节点右孩子为空,左孩子不为空
				{
					if (parent == cur)//删除的是根节点
					{
						_root = cur->_lchild;//更新根节点
					}
					//将删除节点的左孩子交给其父节点
					else if (parent->_lchild == cur)
					{
						parent->_lchild = cur->_lchild;
					}
					else
					{
						parent->_rchild = cur->_lchild;
					}
					//释放节点空间
					delete cur;
					cur = nullptr;
				}
				else if (cur->_lchild == nullptr)//删除的节点左孩子为空,右孩子不为空
				{
					if (parent == cur)//删除的是根节点
					{
						_root = cur->_rchild;//更新根节点
					}
					//将删除节点的右孩子交给其父节点
					else if (parent->_lchild == cur)
					{
						parent->_lchild = cur->_rchild;
					}
					else
					{
						parent->_rchild = cur->_rchild;
					}
					//释放节点空间
					delete cur;
					cur = nullptr;
				}
				else//删除的节点左右孩子都不为空,要找到删除节点的左子树的最大节点或右子树的最小节点可将其替换
				{
					//这里找要删除节点的左子树的最大节点(右子树的最小节点也可)
					Node* maxleft = cur->_lchild; // 记录左子树的最大节点
					Node* pmaxleft = cur;//记录左子树的最大节点的父节点
					while (maxleft->_rchild)//查找左子树的最大节点
					{
						pmaxleft = maxleft;
						maxleft = maxleft->_rchild;
					}
					//将找到的节点替换要删除的节点
					cur->_key = maxleft->_key;
					if (pmaxleft->_lchild == maxleft)
					{
						pmaxleft->_lchild = maxleft->_lchild;
					}
					else
					{
						pmaxleft->_rchild = maxleft->_lchild;
					}
					//释放节点空间
					delete maxleft;
					maxleft = nullptr;
				}
				return true;
			}
		}
		return false;//没找到要删除的节点
	}
private:
	Node* _root = nullptr;//根节点
};

2.4.1 删除数据的递归实现

template<class K>
class BSTree 
{
	typedef BSTreeNode<K> Node;
public:
	bool EraseR(const K& key)//递归删除数据
	{
		return _EraseR(_root, key);
	}

	bool _EraseR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			return false;
		}
		else if (root->_key < key)
		{
			_EraseR(root->_rchild, key);
		}
		else if (root->_key > key)
		{
			_EraseR(root->_lchild, key);
		}
		else
		{
			Node* del = root;
			if (root->_lchild == root->_rchild && root->_lchild == nullptr)//删除的节点为叶子节点
			{
				//释放节点空间
				delete root;
				//将其父节点指向的自身的指针置空
				root = nullptr;
			}
			else if (root->_rchild == nullptr)//删除的节点右孩子为空,左孩子不为空
			{
				//将删除节点的左孩子交给其父节点
				root = root->_lchild;
				//释放节点空间
				delete del;
				del = nullptr;
			}
			else if (root->_lchild == nullptr)//删除的节点左孩子为空,右孩子不为空
			{
				//将删除节点的右孩子交给其父节点
				root = root->_rchild;
				//释放节点空间
				delete del;
				del = nullptr;
			}
			else//删除的节点左右孩子都不为空,要找到删除节点的左子树的最大节点或右子树的最小节点将其替换
			{
				//这里找要删除节点的右子树的最小节点(左子树的最大节点也可)
				Node* minright = del->_rchild; // 记录右子树的最小节点
				while (minright->_lchild)//查找右子树的最小节点
				{
					minright = minright->_lchild;
				}
				root->_key = minright->_key;//将找到的节点替换要删除的节点
				return _EraseR(root->_rchild, root->_key);//继续递归删除其右子树中用来替换的节点
			}
			return true;
		}
	}

private:
	Node* _root = nullptr;//根节点
};

2.5 二叉搜索树的析构

class BSTree 
{
	typedef BSTreeNode<K> Node;

public:
	~BSTree()//析构
	{
		Destory(_root);
	}
	void Destory(Node*& root)//销毁二叉搜索树
	{
		if (root == nullptr)
			return;
		Destory(root->_lchild);
		Destory(root->_rchild);
		delete root;
		root = nullptr;
	}
private:
	Node* _root = nullptr;//根节点
};

2.6 二叉搜索树的拷贝构造

template<class K>
class BSTree 
{
	typedef BSTreeNode<K> Node;

public:

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

	Node* Copy(const Node* node)//拷贝
	{
		if (node == nullptr)
		{
			return nullptr;
		}
		Node* newnode = new Node(node->_key);
		newnode->_lchild = Copy(node->_lchild);
		newnode->_rchild = Copy(node->_rchild);
		return newnode;
	}

private:
	Node* _root = nullptr;//根节点
};

2.7 赋值重载

template<class K>
class BSTree 
{
	typedef BSTreeNode<K> Node;
public:
    BSTree& operator=(BSTree<K> t)//赋值重载
	{
		std::swap(_root, t._root);
		return *this;
	}
private:
	Node* _root = nullptr;//根节点
};

2.8 模拟实现二叉搜索树的全部代码

#include<iostream>
using namespace std;

template<class K>
struct BSTreeNode//节点
{
	BSTreeNode<K>* _lchild;
	BSTreeNode<K>* _rchild;
	K _key;

	BSTreeNode(const K& key)
		:_lchild(nullptr),
		_rchild(nullptr),
		_key(key)
	{}
};

template<class K>
class BSTree 
{
	typedef BSTreeNode<K> Node;

public:
	BSTree() = default;//强制生成默认拷贝函数

	BSTree(const BSTree<K>& t)//拷贝构造函数
	{
		_root = Copy(t._root);
	}

	~BSTree()//析构
	{
		Destory(_root);
	}

	bool Insert(const K& key)//插入数据
	{
		if (_root == nullptr)//如果根节点为空就直接插入
		{
			_root = new Node(key);
			_root->_key = key;
			return true;
		}
		Node* cur = _root;//使用cur遍历二叉树找到合适的插入位置
		Node* parent = cur;//记录cur的父节点
		while (cur)
		{
			parent = cur;
			if (key < cur->_key)
			{
				cur = cur->_lchild;
			}
			else if (key > cur->_key)
			{
				cur = cur->_rchild;
			}
			else
			{
				return false;//这里创建的二叉搜索树不允许出现值的冗余
			}
		}
		cur = new Node(key);//创建
		//将创建的节点链接到二叉树上
		if (key < parent->_key)
		{
			parent->_lchild = cur;
		}
		else
		{
			parent->_rchild = cur;
		}
		return true;
	}

	bool InsertR(const K& key)//插入数据(递归)
	{
		return _InsertR(_root, key);
	}

	void InOrder()//中序遍历
	{
		_InOrder(_root);
		cout << endl;
	}

	bool Find(const K& key)//查找数据
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)
			{
				cur = cur->_rchild;
			}
			else if (cur->_key > key)
			{
				cur = cur->_lchild;
			}
			else
			{
				return true;
			}
		}
		return false;
	}

	bool Erase(const K& key)
	{
		Node* cur = _root;//使用cur遍历二叉树找到要删除元素的位置
		Node* parent = cur;//记录cur的父节点
		while (cur)//寻找需要删除的节点
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_rchild;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_lchild;
			}
			else//找到了,开始删除
			{
				if (cur->_lchild == cur->_rchild && cur->_lchild == nullptr)//删除的节点为叶子节点
				{
					if (parent == cur)//删除的是根节点
					{
						_root = nullptr;//更新根节点
					}
					//将其父节点指向的自身的指针置空
					else if (parent->_lchild == cur)
					{
						parent->_lchild = nullptr;
					}
					else
					{
						parent->_rchild = nullptr;
					}
					//释放节点空间
					delete cur;
					cur = nullptr;
				}
				else if (cur->_rchild == nullptr)//删除的节点右孩子为空,左孩子不为空
				{
					if (parent == cur)//删除的是根节点
					{
						_root = cur->_lchild;//更新根节点
					}
					//将删除节点的左孩子交给其父节点
					else if (parent->_lchild == cur)
					{
						parent->_lchild = cur->_lchild;
					}
					else
					{
						parent->_rchild = cur->_lchild;
					}
					//释放节点空间
					delete cur;
					cur = nullptr;
				}
				else if (cur->_lchild == nullptr)//删除的节点左孩子为空,右孩子不为空
				{
					if (parent == cur)//删除的是根节点
					{
						_root = cur->_rchild;//更新根节点
					}
					//将删除节点的右孩子交给其父节点
					else if (parent->_lchild == cur)
					{
						parent->_lchild = cur->_rchild;
					}
					else
					{
						parent->_rchild = cur->_rchild;
					}
					//释放节点空间
					delete cur;
					cur = nullptr;
				}
				else//删除的节点左右孩子都不为空,要找到删除节点的左子树的最大节点或右子树的最小节点可将其替换
				{
					//这里找要删除节点的左子树的最大节点(右子树的最小节点也可)
					Node* maxleft = cur->_lchild; // 记录左子树的最大节点
					Node* pmaxleft = cur;//记录左子树的最大节点的父节点
					while (maxleft->_rchild)//查找左子树的最大节点
					{
						pmaxleft = maxleft;
						maxleft = maxleft->_rchild;
					}
					//将找到的节点替换要删除的节点
					cur->_key = maxleft->_key;
					if (pmaxleft->_lchild == maxleft)
					{
						pmaxleft->_lchild = maxleft->_lchild;
					}
					else
					{
						pmaxleft->_rchild = maxleft->_lchild;
					}
					//释放节点空间
					delete maxleft;
					maxleft = nullptr;
				}
				return true;
			}
		}
		return false;//没找到要删除的节点
	}


	bool EraseR(const K& key)//递归删除数据
	{
		return _EraseR(_root, key);
	}

	BSTree& operator=(BSTree<K> t)//赋值重载
	{
		std::swap(_root, t._root);
		return *this;
	}

protected:
	Node* Copy(const Node* node)//拷贝
	{
		if (node == nullptr)
		{
			return nullptr;
		}
		Node* newnode = new Node(node->_key);
		newnode->_lchild = Copy(node->_lchild);
		newnode->_rchild = Copy(node->_rchild);
		return newnode;
	}

	void Destory(Node*& root)//销毁二叉搜索树
	{
		if (root == nullptr)
			return;
		Destory(root->_lchild);
		Destory(root->_rchild);
		delete root;
		root = nullptr;
	}

	bool _InsertR(Node*& root,const K& key)//这里使用指针的引用用来直接修改其父节点指针的指向
	{
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}
		if (root->_key > key)
		{
			_InsertR(root->_lchild, key);
		}
		else if (root->_key < key)
		{
			_InsertR(root->_rchild, key);
		}
		else
		{
			return false;
		}
	}

	void _InOrder(Node* root)
	{
		if (root == NULL)//如果是空树就直接结束
		{
			return;
		}
		_InOrder(root->_lchild);//先递归遍历其左子树
		cout << root->_key << " ";//再遍历其根节点
		_InOrder(root->_rchild);//最后递归遍历其右子树
	}

	bool _EraseR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			return false;
		}
		else if (root->_key < key)
		{
			_EraseR(root->_rchild, key);
		}
		else if (root->_key > key)
		{
			_EraseR(root->_lchild, key);
		}
		else
		{
			Node* del = root;
			if (root->_lchild == root->_rchild && root->_lchild == nullptr)//删除的节点为叶子节点
			{
				//释放节点空间
				delete root;
				//将其父节点指向的自身的指针置空
				root = nullptr;
			}
			else if (root->_rchild == nullptr)//删除的节点右孩子为空,左孩子不为空
			{
				//将删除节点的左孩子交给其父节点
				root = root->_lchild;
				//释放节点空间
				delete del;
				del = nullptr;
			}
			else if (root->_lchild == nullptr)//删除的节点左孩子为空,右孩子不为空
			{
				//将删除节点的右孩子交给其父节点
				root = root->_rchild;
				//释放节点空间
				delete del;
				del = nullptr;
			}
			else//删除的节点左右孩子都不为空,要找到删除节点的左子树的最大节点或右子树的最小节点将其替换
			{
				//这里找要删除节点的右子树的最小节点(左子树的最大节点也可)
				Node* minright = del->_rchild; // 记录右子树的最小节点
				while (minright->_lchild)//查找右子树的最小节点
				{
					minright = minright->_lchild;
				}
				root->_key = minright->_key;//将找到的节点替换要删除的节点
				return _EraseR(root->_rchild, root->_key);//继续递归删除其右子树中用来替换的节点
			}
			return true;
		}
	}

private:
	Node* _root = nullptr;//根节点
};

三、二叉搜索树的应用

K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。

比如:给一个单词word,判断该单词是否拼写正确,具体方式如下: 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树,在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

KV模型:每一个关键码key,都有与之对应的值Value,即的键值对。

该种方式在现实生活中十分常见: 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文就构成一种键值对; 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是就构成一种键值对

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

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二 叉搜索树的深度的函数,即结点越深,则比较次数越多。

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

所以最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:O(㏒⑵N)

最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:O(N)

那如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插 入关键码,二叉搜索树的性能都能达到最优?

那么我们后面就要学习的AVL树和红黑树了。


本期博客到这里就结束了,代码量较大,还请各位仔细分析呀

我们下期见~

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

1e-12

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

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

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

打赏作者

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

抵扣说明:

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

余额充值