C++ 实现二叉搜索树

一.二叉搜索树的基本概念

    二叉搜索树,也叫二叉排序树,是一种具有特殊性质的二叉树。

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

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

  • 任意节点的子树也都是二叉搜索树。

如下面这颗二叉树:

 我们可以看出这个二叉树符合上面的规则。

并且二叉搜索树还有一个重要的特点:

二叉搜索树的中序遍历,如果排序的是数字的话,那么他的中序遍历结果一定是个有序的数组,上面这个树的中序遍历为[1 15 16 17 18 20 23 24 27 28 30].

 二.key模型和key-value模型

概念:

  key的模型是指每个节点只有一个键值,用于确定节点在树中的位置。节点的键值必须满足二叉搜索树的性质,即左子节点的键值小于父节点的键值,右子节点的键值大于父节点的键值。这种模型比较简单,但是不能存储额外的信息。
  key/value模型是指每个节点有一个键值和一个数据值,键值用于确定节点在树中的位置,数据值用于存储节点的附加信息。节点的键值仍然必须满足二叉搜索树的性质,但是数据值可以是任意类型或对象。这种模型比较灵活,可以实现一些高级功能,比如映射或字典。

  一般而言,我们在程序中使用的都是key-value模型的二叉搜索树,因为key模型的没什么实际作用。

   假设你想要存储图书的话,在key/value模型中就可以让图书姓名当作键值,数量当作数据值,以此来详细统计一下图书。

   但是我们先由易到难,先来讲解一下key模型的二叉搜索树。

三.key模型的二叉搜索树操作

  3.1 节点

  无论是哪种模型,我们都需要定义一个内部类来表示节点,根据树的基本含义我们可以得知,除了key模型所需要的一个元素之外,我们还需要左右两个指针来表示每个节点的左孩子右孩子。

  故得出:

	template<typename  K>
	class BSTreeNode  //二叉搜索树的节点类
	{
	public:
		BSTreeNode(const K& key = K())
			:_left(nullptr)
			,_right(nullptr)
			,_key(key)
		{}
		BSTreeNode<K>* _left;
		BSTreeNode<K>* _right;
		K _key;
	};

  3.2 二叉树的表示

 在整个二叉树的类中,我们只要保证有一个头节点就好了,因为他会指向自己的左右孩子。

故得出:

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node; //typedef一下节省代码量
private:
	Node* _root = nullptr;
};

3.3 二叉搜索树的插入

3.3.1 思想

  二叉搜索树的插入其实只需要注意一点,那就是,插入后,仍然保持二叉搜索树的性质

  那么我们就要与原有的树上的值进行比较,到了合适的位置后再进行插入。 

  比如说这张图:

如果我们要插入的值为 19 的话,需要进行如下几段比较

因此,平衡二叉树的插入实际上就是一个 比较大小 直到寻找到空位置 再进行插入的过程 。

但是,如果插入的元素在数中已经有了该怎么办呢?

平衡二叉树的key模型基本上是拒绝重复元素的插入的。(难办就别办了)

3.3.2 代码实现 

bool Insert(const K& key)
		{
			//特殊情况,如果头节点为nullptr,直接插入
			if (_root == nullptr) 
			{
				_root = new Node(key);
				return true;
			}

			//记录父节点,以方便链接 
			Node* parent = nullptr;
			//真正的遍历比较节点
			Node* cur = _root;
			//进行比较
			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);
			//再次进行比较 判断插入的节点是在左边还是右边
			if (parent->_key < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}
			return true;

		}

3.4 二叉搜索树的查找

3.4.1 简介

  可以说,二叉搜索树在查找上面的进步是很大,他的平均查找速度为O(logN),最坏查找情况为O(N)。(N为树的高度)

  为什么呢,其实类似于二分查找,它每次在查找的时候都只会前往左右区间,也就是直接剔除一半的值,最后便得出平均查找速度。

  最坏查找是因为有特殊情况,(一般是有序)使二叉树退化成单子树,从而大幅降低了查找效率。

如:

 3.4.2 思想

 其实查找也是个比较大小的思想,通过一次一次的比较大小,最终得出结果。

如:

我们要查找  18 

 3.4.3 代码实现

	Node* Find(const K& key) //我们选择返回节点指针
		{
			//通过父子指针进行查找与大小比较
			Node* parent = nullptr;
			Node* cur = _root;
			//进行大小比较
			while (cur)
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					//与插入不同,查找到返回节点
					return cur;
				}
			}
			//没有查找到 返回空
			return nullptr;
		}

3.5 二叉搜索树的删除

3.5.1 思想

二叉搜索树的删除和插入一样,都需要保持一个原则,插入后,仍然保持二叉搜索树的性质

 二叉搜索树的删除无非就四种情况:

1.删除没有孩子节点的父节点

2.删除没有左孩子,但有右孩子的父节点。

3.删除没有有孩子,但有左孩子的节父点。

4.删除有两个孩子的父节点

还是经典老图,稍微改一下,我们分别要删除16 15 28 24这四个节点,分别对应四种情况,我们来看一下。

 

 

 

其实第一种情况可以归类为2.3种情况 

 3.5.2 代码实现

	bool Erase(const K& key)
		{

			//父指针记录 cur指针查找
			Node* parent = nullptr;
			Node* cur = _root;
			//while循环遍历查找
			while (cur)
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					//查找到
					//开始删除
					//一共有三种情况

					//待删除节点左指针为nullptr
					if (cur->_left == nullptr)  //第一二种 情况合起来
					{ 
						//处理特殊情况 如果要删除的是头指针
						if (_root == cur)
						{
							_root = cur->_right;
						}
						else
						{
							//如果是节点左右都为nullptr 父节点的指针就指向nullptr
							if (parent->_left == cur)
							{
								parent->_left = cur->_right;
							}
							else
							{
								parent->_left = cur->_right;
							}
						}
						delete cur;
						cur = nullptr;
					}
					//待删除节点右指针为nullptr
					else if (cur->_right == nullptr)
					{
						//处理特殊情况 如果要删除的是头指针
						if (_root == cur)
						{
							_root = cur->_left;
						}
						else
						{
							if (parent->_right == cur)
							{
								parent->_right = cur->_left;
							}
							else
							{
								parent->_right = cur->_left;
							}
						}
						delete cur;
						cur = nullptr;
					}

					//待删除节点左右孩子都有
					else
					{
						//查找左子树的最大值 其实就是查找cur左子树的最右节点
						/*Node* maxParent = cur;
						Node* max = cur->_left;
						while (max->_right)
						{

							maxParent = max;
							max =max->_left;
						}*/

						Node* minParent = cur;
						Node* min = cur->_right;
						//查找右子树的最小值 其实就是查找cur右子树的最左节点
						while (min->_left)
						{

							minParent = min;
							min = min->_left;
						}
						//将找到的值和cur交换
						swap(cur->_key, min->_key);

						//如果查找到的最大值或者最小值 还有另外的节点 需要链接
						if (minParent->_left == min)
							minParent->_left = min->_right;
						else
							minParent->_right = min->_right;


						delete min;
						min = nullptr;
					}
					return true;
				}
			}
			//没有删除返回false
			return false;
		}

3.6 中序遍历

没啥可说的 普通二叉树的遍历罢了,除了中序遍历可能是一个有序数组外这一个性质外。

void _InOrder(Node* root)
{
			if (root == nullptr)
			{
				return;
			}
			_InOrder(root->_left);
			cout << root->_key << endl;
			_InOrder(root->_right);

}

3.7 总代码

template<typename  K>
	class BSTreeNode  //二叉搜索树的节点类
	{
	public:
		BSTreeNode(const K& key = K())
			:_left(nullptr)
			,_right(nullptr)
			,_key(key)
		{}
		BSTreeNode<K>* _left;
		BSTreeNode<K>* _right;
		K _key;
	};
	template<class K>
	class BSTree
	{
		typedef BSTreeNode<K> Node; //typedef一下节省代码量
	public:
		bool Insert(const K& key)
		{
			//特殊情况,如果头节点为nullptr,直接插入
			if (_root == nullptr) 
			{
				_root = new Node(key);
				return true;
			}

			//记录父节点,以方便链接 
			Node* parent = nullptr;
			//真正的遍历比较节点
			Node* cur = _root;
			//进行比较
			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);
			//再次进行比较 判断插入的节点是在左边还是右边
			if (parent->_key < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}
			return true;

		}
		Node* Find(const K& key) //我们选择返回节点指针
		{
			//通过父子指针进行查找与大小比较
			Node* parent = nullptr;
			Node* cur = _root;
			//进行大小比较
			while (cur)
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					//与插入不同,查找到返回节点
					return cur;
				}
			}
			//没有查找到 返回空
			return nullptr;
		}
		bool Erase(const K& key)
		{

			//父指针记录 cur指针查找
			Node* parent = nullptr;
			Node* cur = _root;
			//while循环遍历查找
			while (cur)
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					//查找到
					//开始删除
					//一共有三种情况

					//待删除节点左指针为nullptr
					if (cur->_left == nullptr)  //第一二种 情况合起来
					{ 
						//处理特殊情况 如果要删除的是头指针
						if (_root == cur)
						{
							_root = cur->_right;
						}
						else
						{
							//如果是节点左右都为nullptr 父节点的指针就指向nullptr
							if (parent->_left == cur)
							{
								parent->_left = cur->_right;
							}
							else
							{
								parent->_left = cur->_right;
							}
						}
						delete cur;
						cur = nullptr;
					}
					//待删除节点右指针为nullptr
					else if (cur->_right == nullptr)
					{
						//处理特殊情况 如果要删除的是头指针
						if (_root == cur)
						{
							_root = cur->_left;
						}
						else
						{
							if (parent->_right == cur)
							{
								parent->_right = cur->_left;
							}
							else
							{
								parent->_right = cur->_left;
							}
						}
						delete cur;
						cur = nullptr;
					}

					//待删除节点左右孩子都有
					else
					{
						//查找左子树的最大值 其实就是查找cur左子树的最右节点
						/*Node* maxParent = cur;
						Node* max = cur->_left;
						while (max->_right)
						{

							maxParent = max;
							max =max->_left;
						}*/

						Node* minParent = cur;
						Node* min = cur->_right;
						//查找右子树的最小值 其实就是查找cur右子树的最左节点
						while (min->_left)
						{

							minParent = min;
							min = min->_left;
						}
						//将找到的值和cur交换
						swap(cur->_key, min->_key);

						//如果查找到的最大值或者最小值 还有另外的节点 需要链接
						if (minParent->_left == min)
							minParent->_left = min->_right;
						else
							minParent->_right = min->_right;


						delete min;
						min = nullptr;
					}
					return true;
				}
			}
			//没有删除返回false
			return false;
		}

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}//为了预防数据被改,我们改成privat
	private:
		void _InOrder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			_InOrder(root->_left);
			cout << root->_key << endl;
			_InOrder(root->_right);

		}
		Node* _root = nullptr;
	};

四 .Key-Value模型的搜索二叉树

  如果说key模型的值只能表示在不在的话,那么key-value模型就可以表示通过一个值查找另一个值。

  key value模型 与上述实现的二叉搜索树实现功能差不多,只是增加了一个模板参数value。

代码为:

template<class K, class V>
class BSTreeNode
{
public:
	BSTreeNode(const K& key=K(),const V& val=V())
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
		,_val(val)
	{
	}
	BSTreeNode<K, V>* _left;
	BSTreeNode<K, V>* _right;
	K _key;
	V _val;
};
template<class K, class V>
class BSTree
{
	typedef BSTreeNode<K, V> Node; //这里我们重命名一下,节省代码
public:
	bool Insert(const K& key, const V& value)
	{
		if (_root == nullptr)
		{
			_root = new Node(key, value);
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		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, value);
		if (parent->_key < key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		return true;

	}
	Node* Find(const K& key)
	{
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}
	bool Erase(const K& key)
	{
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				//开始删除
				//一共有三种情况
				if (cur->_left == nullptr)
				{
					if (_root == cur)
					{
						_root = cur->_right;
					}
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_right;
						}
						else
						{
							parent->_left = cur->_right;
						}
					}
					delete cur;
					cur = nullptr;
				}
				else if (cur->_right == nullptr)
				{
					if (_root == cur)
					{
						_root = cur->_left;
					}
					else
					{
						if (parent->_right == cur)
						{
							parent->_right = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
					}
					delete cur;
					cur = nullptr;
				}
				else
				{

					Node* minParent = cur;
					Node* min = cur->_right;

					while (min->_left)
					{
						
						minParent = min;
						min = min->_left;
					}
					swap(cur->_key, min->_key);
	
					if (minParent->_left == min)
						minParent->_left = min->_right;
					else 
						minParent->_right = min->_right;
					delete min;
					min = nullptr;
				}
				return true;
			}
		}
		return false;
	}
	
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << ":" << root->_val<< endl;
		_InOrder(root->_right);

	}
	Node* _root = nullptr;
};

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
要创建一个二叉排序树(Binary Search Tree,BST)的话,你需要实现以下几个关键步骤: 1. 首先,你需要定义二叉树节点的结构。每个节点应该包含一个数据元素,以及指向左子节点和右子节点的指针。 ```c typedef struct Node { int data; struct Node* left; struct Node* right; } Node; ``` 2. 创建一个函数来插入节点到二叉排序树中。这个函数应该接收一个指向根节点的指针和待插入的数据。如果根节点为空,表示树为空,你可以直接将新节点作为根节点。否则,你需要按照 BST 的规则找到合适的位置插入新节点。 ```c void insert(Node** root, int data) { if (*root == NULL) { *root = createNode(data); return; } if (data < (*root)->data) { insert(&(*root)->left, data); } else { insert(&(*root)->right, data); } } ``` 3. 创建一个函数来创建新的节点。 ```c Node* createNode(int data) { Node* newNode = (Node*)malloc(sizeof(Node)); newNode->data = data; newNode->left = NULL; newNode->right = NULL; return newNode; } ``` 4. 最后,你可以编写一个函数来遍历并打印二叉排序树中的所有节点,以验证是否正确创建了二叉排序树。 ```c void inorderTraversal(Node* root) { if (root != NULL) { inorderTraversal(root->left); printf("%d ", root->data); inorderTraversal(root->right); } } ``` 这样,你就可以使用上述代码来创建和操作二叉排序树了。记得在程序结束后释放节点的内存空间,以避免内存泄漏。 希望这能帮到你!如有疑问,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值