深入了解二叉搜索树:原理、操作与应用

2024-10-09 16:35:40 浏览数 (1)

二叉搜索树

形如上图的二叉树就是二叉搜索树,接下来我们来具体阐述一下什么是二叉搜索树。

二叉搜索树的概念:满足左子树的值小于根节点,右子树的值大于根节点的值,这样的树就是二叉搜索树

二叉搜索树的性质: 1.二叉搜索树的中序遍历呈现单调递增的性质。 2.二叉搜索树的每个节点的值都具有唯一性。

二叉搜索树的操作

对于普通的二叉树来说,增删查改没有意义,但是对于二叉搜索树来说增删查改便有了意义,接下来,我们来研究二叉搜索树的增删查改等等一系列操作。 首先我们先定义一个二叉搜索树,二叉搜索树的定义和二叉树的定义相同:

代码语言:javascript复制
class TreeNode
{
public:
private:
	int _val;
	TreeNode* _left;
	TreeNode* _right;
};
class BST
{
public:
private:
	TreeNode* _root;
};

上面就是二叉搜索树的定义

接下来我们来写一下二叉搜索树的接口,,我们先将其列在BST

代码语言:javascript复制
class BST
{
public:
	//初始化
	BST();
	//查找
	bool Search(int val);
	bool SearchNode(TreeNode* Node, int val);
	//插入
	void Insert(int val);
	TreeNode* InsertNode(TreeNode* Node, int val);
	//删除
	void Delete(int val);
	TreeNode* AssistDelete(TreeNode* Node, int val);
	TreeNode* FindPrev(TreeNode* root, TreeNode* Node);
	//查找最大值或者最小值
	int GetMin();
	TreeNode* AssistGetMin(TreeNode*Node);
	int GetMax();
	TreeNode* AssistGetMax(TreeNode* Node);
	//后序遍历
	void PostOrder();
	void AssistPostOrder(TreeNode* Node);
	//中序遍历
	void InOrder();
	void AssistInOrder(TreeNode* Node);
	//前序遍历
	void PrevOrder();
	void AssistPrevOrder(TreeNode* Node);
private:
	TreeNode* _root;
};

接下我们一个一个来探讨

1.查找操作

代码语言:javascript复制
//查找
bool Search(int val);
bool SearchNode(TreeNode* Node, int val);

找到了返回true,没找到返回false 对于普通的二叉树来说我们需要遍历整个树对其进行查找,而且可能二叉树中还有重复值的节点,但是对于二叉搜索树就没有这种麻烦,当我们要搜索一个数时,只需要将这个数和根节点的值进行比较,如果比根节点的数大就递归到右边,如果比根节点的数小就递归左边,不需要整个树都递归。

我们来看看具体代码:

代码语言:javascript复制
bool BST::Search(int val)
{
	return SearchNode(_root, val);
}
//辅助函数
bool BST::SearchNode(TreeNode* Node, int val)
{
	//找到了nullptr就说明没找到,直接返回false
	if (Node == nullptr)
	{
		return false;
	}
	//判断val的位置,对左子树或者右子树进行递归
	if (Node->_val == val)
	{
		return true;
	}
	else if (val < Node->_val)
	{
		return SearchNode(Node->_left, val);
	}
	else
	{
		return SearchNode(Node->_right, val);
	}
}

2.插入操作

对于普通二叉树来说,插入操作没有意义,因为每一个地方都可以插入,并没有实际意义,但是对于二叉搜索树来说就有意义了,对于二叉搜索树来说,插入操作具有唯一性,可以维护二叉搜索树的排序的性质。

代码语言:javascript复制
//插入
void Insert(int val);
TreeNode* InsertNode(TreeNode* Node, int val);

查到插入位置然后返回,InsertNode是一个辅助函数。 具体代码:

代码语言:javascript复制
void BST::Insert(int val)
{
	_root = InsertNode(_root, val);
}
TreeNode* BST::InsertNode(TreeNode* Node, int val)
{
	//当Node==nullptr的时候说明找到了插入的位置
	if (Node == nullptr)
	{
		创造一个新的节点,用val进行初始化
		return new TreeNode(val);//创建一个节点
	}
	//判断递归的范围
	if (Node->_val < val)
	{
		Node->_right = InsertNode(Node->_right, val);
	}
	else if (Node->_val > val)
	{
		Node->_left = InsertNode(Node->_left, val);
	}
	//最后返回Node
	return Node;
}

3.查找最大值或者最小值

代码语言:javascript复制
//查找最大值或者最小值
int GetMin();
TreeNode* AssistGetMin(TreeNode*Node);
int GetMax();
TreeNode* AssistGetMax(TreeNode* Node);

由于二叉搜索树的特殊性质,左子树的值一定比右子树小,说明最大值一定在右子树的最右边的一个节点上,而最小值一定在左子树的最左边的一个节点上。

我们来看看代码:

代码语言:javascript复制
//查找最小值
int BST::GetMin()
{
	//根节点为空的时候,说明没有,直接返回INT_MAX
	if (_root == nullptr)
	{
		return INT_MAX;
	}
	//找到最小值的节点
	TreeNode* min = AssistGetMin(_root);
	//返回最小值
	return min->_val;
}
TreeNode* BST::AssistGetMin(TreeNode* Node)
{
	//直接递归左子树,当左子树指向的左边是空的时候直接返回当前节点
	if (Node->_left == nullptr)
	{
		return Node;
	}
	//递归左子树
	return AssistGetMin(Node->_left);
}
//查找最大值
int BST::GetMax()
{
	//同上
	if (_root == nullptr)
	{
		return INT_MAX;
	}
	//找到最大值对应的节点
	TreeNode* max = AssistGetMax(_root);
	//返回最大值
	return max->_val;
}
TreeNode* BST::AssistGetMax(TreeNode* Node)
{
	//判断边界条件
	if (Node->_right == nullptr)
	{
		return Node;
	}
	//递归右子树
	return AssistGetMax(Node->_right);
}

4.删除操作

代码语言:javascript复制
//删除
void Delete(int val);
TreeNode* AssistDelete(TreeNode* Node, int val);
TreeNode* FindPrev(TreeNode* root, TreeNode* Node);

对于删除操作存在三种情况: 1. 删除的当前节点的左节点和右节点都是nullpt 2. 删除的当前节点的的左节点或者右节点当中存在一个节点,有一个节点是nullpt 3. 删除的当前节点的左节点和右节点都不为nullptr

对于删除操作我们还需要一个函数就是能找到前一个节点的函数,当我们删除了一个节点之后,我们需要找到前一个节点将它与后一个节点连接起来,还需要一个函数就是我们刚刚写的找最大值的函数或者找最小值的函数,这里我们就用找最小值的函数,对于删除的当前节点的左节点和右节点都不为空的时候,我们需要有一个节点来重新构造一下这个二叉搜索树,这里有个技巧,就是我们需要的节点就是当前节点的左子树的最大值的节点或者是右子树的最小值的节点充当本节点。

代码展示:

代码语言:javascript复制
void BST::Delete(int val)
{
	_root = AssistDelete(_root, val);
}
//找到前一个节点
TreeNode* BST::FindPrev(TreeNode* root, TreeNode* Node)
{
	if (root->_left == Node || root->_right == Node)
	{
		return root;
	}
	if (root->_val < Node->_val)
	{
		FindPrev(root->_right, Node);
	}
	if (root->_val > Node->_val)
	{
		FindPrev(root->_left, Node);
	}
}
//删除操作
TreeNode* BST::AssistDelete(TreeNode* node, int val)
{
	//当node==nullptr说明没有满足条件的删除节点,直接返回nullpr
	if (node == nullptr)
	{
		return node;
	}
	// 找到要删除的节点
	if (val < node->_val)
	{
		node->_left = AssistDelete(node->_left, val);
	}
	else if (val > node->_val)
	{
		node->_right = AssistDelete(node->_right, val);
	}
	//找到了之后进入else进行删除操作
	else
	{
		// 要删除的节点有一个或没有孩子
		if (node->_left == nullptr)
		{
			TreeNode* temp = node->_right;
			delete node;
			return temp;
		}
		else if (node->_right == nullptr)
		{
			TreeNode* temp = node->_left;
			delete node;
			return temp;
		}

		// 要删除的节点有两个孩子
		// 找到要删除节点的后继节点(右子树中的最小节点)
		TreeNode* temp = AssistGetMin(node->_right);
		// 将后继节点的值复制到要删除的节点中
		node->_val = temp->_val;
		// 递归删除后继节点
		node->_right = AssistDelete(node->_right, temp->_val);
	}
	//返回
	return node;
}

5.前序中序后序遍历

代码语言:javascript复制
	//后序遍历
	void PostOrder();
	void AssistPostOrder(TreeNode* Node);
	//中序遍历
	void InOrder();
	void AssistInOrder(TreeNode* Node);
	//前序遍历
	void PrevOrder();
	void AssistPrevOrder(TreeNode* Node);

和普通二叉树相同

代码语言:javascript复制
void BST::PostOrder()
{
	AssistPostOrder(_root);
}
void BST::AssistPostOrder(TreeNode* Node)
{
	if (Node == nullptr)
	{
		return;
	}
	AssistPostOrder(Node->_left);
	AssistPostOrder(Node->_right);
	cout << Node->_val << ' ';
}
void BST::InOrder()
{
	AssistInOrder(_root);
}
void BST::AssistInOrder(TreeNode* Node)
{
	if (Node == nullptr)
	{
		return;
	}
	AssistInOrder(Node->_left);
	cout << Node->_val << ' ';
	AssistInOrder(Node->_right);
}
void BST::PrevOrder()
{
	AssistPrevOrder(_root);
}
void BST::AssistPrevOrder(TreeNode* Node)
{
	if (Node == nullptr)
	{
		return;
	}
	cout << Node->_val << ' ';
	AssistPrevOrder(Node->_left);
	AssistPrevOrder(Node->_right);
}

总结

总的来说,二叉搜索树(BST)作为一种重要的数据结构,在计算机科学领域发挥着重要作用。通过其排序性质和高效的搜索、插入和删除操作,二叉搜索树成为了解决各种问题的有力工具。

在本博客中,我们深入探讨了二叉搜索树的概念、性质和操作。我们了解到,二叉搜索树具有自平衡的能力,能够在平均情况下保持较低的时间复杂度。同时,我们也注意到了在极端情况下,二叉搜索树可能会退化为链表,导致操作的时间复杂度上升。因此,在实际应用中,需要考虑树的平衡性,以保证其性能。

通过本博客,希望读者能够深入理解二叉搜索树的工作原理,并且能够运用这一数据结构解决实际问题。同时,也希望读者能够进一步探索二叉搜索树的相关内容,如平衡二叉搜索树(如AVL树和红黑树)以及其他高级数据结构,从而拓展自己的知识领域。

最后,二叉搜索树是计算机科学中的基础之一,深入了解它将有助于我们更好地理解和应用数据结构与算法,提高编程能力,并解决更复杂的计算问题。

0 人点赞