二叉搜索树的实现(迭代方式)

二叉搜索树(Binary Search Tree,BST)是一种基于二叉树的数据结构,具有以下特点:

  1. 对于树中的每个节点,其左子树中的所有节点的值都小于该节点的值,而右子树中的所有节点的值都大于该节点的值。
  2. 左子树和右子树也都是二叉搜索树。

这个特性使得二叉搜索树能够高效地支持插入、删除和查找操作。

插入操作:
要在二叉搜索树中插入一个新节点,需要按照以下步骤进行:

  1. 如果树为空,则将新节点作为根节点。
  2. 如果树不为空,则从根节点开始比较新节点的值与当前节点的值。
  3. 如果新节点的值小于当前节点的值,继续在当前节点的左子树中插入新节点。
  4. 如果新节点的值大于当前节点的值,继续在当前节点的右子树中插入新节点。
  5. 重复步骤3和4,直到找到一个空位置插入新节点。

删除操作:
要在二叉搜索树中删除一个节点,需要按照以下步骤进行:

  1. 首先,找到要删除的节点。如果节点不存在,则删除操作失败。
  2. 如果要删除的节点没有子节点,直接删除即可。
  3. 如果要删除的节点只有一个子节点,将其子节点替代要删除的节点。
  4. 如果要删除的节点有两个子节点,需要找到其右子树中的最小节点(或左子树中的最大节点),将其值替换到要删除的节点上,然后删除这个最小(或最大)节点。

针对第4点,左子树的最右节点和右子树的最左节点,只有这两个节点能起到承上启下的作用,比所有的左子树节点都大,比所有的右子树节点都小;去替换目标节点后,这颗二叉树才能保持为搜索二叉树。

查找操作:
要在二叉搜索树中查找一个特定的值,需要按照以下步骤进行:

  1. 从根节点开始,将要查找的值与当前节点的值进行比较。
  2. 如果要查找的值等于当前节点的值,则找到了目标节点,返回该节点。
  3. 如果要查找的值小于当前节点的值,继续在当前节点的左子树中查找。
  4. 如果要查找的值大于当前节点的值,继续在当前节点的右子树中查找。
  5. 重复步骤3和4,直到找到目标节点或者遍历到叶子节点为止。

二叉搜索树的时间复杂度取决于树的平衡性。在最坏情况下,树可能变成链表,导致插入、删除和查找操作的时间复杂度为 O(n)。但是,如果树保持平衡,例如平衡二叉搜索树(AVL树、红黑树等),则这些操作的时间复杂度可以保持在 O(log n)。

以下是BST的代码实现:

定义BST的节点结构BSTreeNode,包含左子树指针_left、右子树指针_right和键值_key

#include<iostream>
using namespace std;

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

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

定义BSTree类,其中的Find函数用于查找指定的键值。从根节点开始,根据当前节点的键值与目标键值的大小关系,沿着左子树或右子树逐步查找,直到找到目标键值或遍历完整个树。

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	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 Erase(const K& key)
	{
		if (_root == nullptr)
			return false;

		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			//不能在这更新parent,否则删除节点时parent和cur指向相同
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else//删除操作
			{
				//只有一个孩子,托孤法				
				if (cur->_right == nullptr)//只有一个左孩子(+无孩子)
				{
					if (parent == nullptr)
					{
						_root = cur->_left;
						delete cur;
						return true;
					}

					if (cur == parent->_left)
						parent->_left = cur->_left;
					else
						parent->_right = cur->_left;
					delete cur;
				}
				else if (cur->_left == nullptr)//只有一个右孩子
				{
					if (parent == nullptr)
					{
						_root = cur->_right;
						delete cur;
						return true;
					}

					if (cur == parent->_left)
						parent->_left = cur->_right;
					else
						parent->_right = cur->_right;
					delete cur;
				}
				else //左右都有孩子
				{
					//有两个孩子,替换删除法;左子树的最大(最右)节点或右子数的最小(最左)节点
					//找到右子树的最左节点
					Node* subLeft = cur->_right;
					Node* subParent = cur;
					while (subLeft->_left)
					{
						subParent = subLeft;
						subLeft = subLeft->_left;
					}
					swap(subLeft->_key, cur->_key);
					//经过交换后,现在只需删除subLeft,已经转化为只有一个孩子的情况,使用托孤法
					if (subParent->_left == subLeft)
						subParent->_left = subLeft->_right;
					else
						subParent->_right = subLeft->_right;

					delete subLeft;
				}
				return true;
			}
		}
		return false;
	}

Erase函数用于删除指定的键值对应的节点。删除操作分为四种情况:

  1. 节点只有一个左孩子(包括无孩子),使用"托孤法"将其父节点指向其左孩子。
  2. 节点只有一个右孩子,同样使用"托孤法"将其父节点指向其右孩子。
  3. 节点既有左孩子又有右孩子,采用"替换删除法",找到右子树的最左节点(或左子树的最右节点),将其键值与当前节点键值交换,然后删除这个最左节点。
  4. 无孩子可以随便合并到只有一个孩子的情况,代码是兼容的。

	bool insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}
		else
		{
			Node* parent = nullptr;
			Node* cur = _root;
			while (cur)
			{	
				//parent = 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);
			parent->_key > cur->_key ? parent->_left = cur : parent->_right = cur;
		}
	}

insert函数用于插入一个新节点。若树为空,直接创建根节点;否则,从根节点开始,根据当前节点的键值与待插入键值的大小关系,沿着左子树或右子树逐步查找插入位置,直到找到一个空位置,然后创建新节点并插入。

	void midOrder()
	{
		_midOrder(_root);
	}
private:
	Node * _root = nullptr;

	void _midOrder(Node* node)
	{
		if (node == nullptr)
			return;

		_midOrder(node->_left);
		cout << node->_key << " ";
		_midOrder(node->_right);
	}
};

midOrder函数用于中序遍历树。由于根节点指针_root是私有的,无法在类外直接使用,因此提供了一个公有的无参遍历函数,在类内部调用私有的递归遍历函数_midOrder

int main()
{
	int a[] = { 8,3,1,6,4,7,14,13 };
	BSTree<int> bst;
	for (int x : a)
	{
		bst.insert(x);
	}
	
	//测试:遍历删除
	for (int x : a)
	{
		bst.midOrder();
		cout << endl;
		bst.Erase(x);
		bst.midOrder();
		cout << endl;
		cout << endl;
	}

	cout << "全部删除成功" << endl;
	system("pause");
	return 0;
}

在主函数中,首先创建了一个BSTree对象bst,然后依次插入数组a中的元素。接下来进行遍历删除的测试,每次删除一个元素后,都会输出当前树的中序遍历结果。最后输出"全部删除成功",并暂停程序的执行。

这段代码实现了BST的基本功能,包括查找、插入和删除操作,并且通过遍历删除的测试验证了代码的正确性。

这是插入后建立的二叉树的逻辑关系:

完整代码如下:

#include<iostream>
using namespace std;

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:
	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 Erase(const K& key)
	{
		if (_root == nullptr)
			return false;

		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			//不能在这更新parent,否则删除节点时parent和cur指向相同
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else//删除操作
			{
				//只有一个孩子,托孤法				
				if (cur->_right == nullptr)//只有一个左孩子(+无孩子)
				{
					if (parent == nullptr)
					{
						_root = cur->_left;
						delete cur;
						return true;
					}

					if (cur == parent->_left)
						parent->_left = cur->_left;
					else
						parent->_right = cur->_left;
					delete cur;
				}
				else if (cur->_left == nullptr)//只有一个右孩子
				{
					if (parent == nullptr)
					{
						_root = cur->_right;
						delete cur;
						return true;
					}

					if (cur == parent->_left)
						parent->_left = cur->_right;
					else
						parent->_right = cur->_right;
					delete cur;
				}
				else //左右都有孩子
				{
					//有两个孩子,替换删除法;左子树的最大(最右)节点或右子数的最小(最左)节点
					//找到右子树的最左节点
					Node* subLeft = cur->_right;
					Node* subParent = cur;
					while (subLeft->_left)
					{
						subParent = subLeft;
						subLeft = subLeft->_left;
					}
					swap(subLeft->_key, cur->_key);
					//经过交换后,现在只需删除subLeft,已经转化为只有一个孩子的情况,使用托孤法
					if (subParent->_left == subLeft)
						subParent->_left = subLeft->_right;
					else
						subParent->_right = subLeft->_right;

					delete subLeft;
				}
				return true;
			}
		}
		return false;
	}

	//插入节点,要保证插入的值不同,一旦发现相同的,返回false
	bool insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}
		else
		{
			Node* parent = nullptr;
			Node* cur = _root;
			while (cur)
			{	
				//parent = 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);
			parent->_key > cur->_key ? parent->_left = cur : parent->_right = cur;
		}
	}

	//遍历树需要传根节点指针,但是树的指针是私有的
	//解决方法:套一层壳,给用户提供无参的遍历函数,在类内部调用传参遍历函数
	void midOrder()
	{
		_midOrder(_root);
	}
private:
	Node * _root = nullptr;

	void _midOrder(Node* node)
	{
		if (node == nullptr)
			return;

		_midOrder(node->_left);
		cout << node->_key << " ";
		_midOrder(node->_right);
	}
};


int main()
{
	int a[] = { 8,3,1,6,4,7,14,13 };
	BSTree<int> bst;
	for (int x : a)
	{
		bst.insert(x);
	}
	
	//测试:遍历删除
	for (int x : a)
	{
		bst.midOrder();
		cout << endl;
		bst.Erase(x);
		bst.midOrder();
		cout << endl;
		cout << endl;
	}

	cout << "全部删除成功" << endl;
	system("pause");
	return 0;
}

以上是BST的实现是以迭代方式(循环),下一篇博客会展示以递归方式实现BST的增、删、查。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值