C++: 二叉搜索树及实现

目录

一、二叉搜索树的概念

二、二叉搜索树的操作

2.1插入

2.2删除

1.有左子树,无右子树

2.有右子树,无左子树

3.有左子树和右子树

三、二叉搜索树的实现

要点


前言:为了学习map和set,需要先学二叉搜索树作为铺垫。

一、二叉搜索树的概念

二叉搜索树是一棵二叉树,又称为二叉排序树,它有以下特性:

1.若左子树不为空,则左子树的节点值比根要小

2.若右子树不为空,则右子树的节点值比根要大

3.整棵树都遵循以上规则

需要注意,二叉树也可以是空树。

二、二叉搜索树的操作

2.1插入

插入的思路是,先用递归将树搜索一遍,用key和树的根的节点比大小,比它大往右走,比它小往左走,直到走到空,那么就在空的位置插入。

2.2删除

删除的思路比较复杂。删除情况分三种,分别是所要删除的节点情况,若删除的是key节点:

1.有左子树,无右子树

这里就将key节点的前一个节点,指向它的后一个节点,然后释放key节点。

2.有右子树,无左子树

和上面类似。

3.有左子树和右子树

这里比较复杂,需要将key节点与它的左子树中的最大的一个,或者右子树中最小的一个交换,然后再删除key。

右子树中最小的一个,是第一个右节点的最左的节点。若没有最左节点,那就只能和右节点交换。

交换以后,就可以删除key节点也就是4了。

最后一种无左子树也无右子树可以放在1和2中讨论,归为一种。操作比较简单,此处不再赘述。

三、二叉搜索树的实现

要点

1.为了体现封装,我们只留接口放在类的public中,把具体实现的细节放在private中,这样也方便代码日后维护。同时由于使用递归,所以需要取root的引用,参数就要给root,而在类外是没有办法获取作为私有的root的,因此只能把实现的细节写在private里。

2.搜索二叉树可以用来匹配,比如说当字典使用和门禁系统,这里可以再放入一个类模板。

3.插入的时候要新建一个节点再插入,这里会改变root的地址,所以传引用会更好,不然就要传二级指针了,这个很麻烦,而且在C++中是避免的。

4.删除的时候,要先找到这个节点,再删除。删除时,一共涉及三个指针的操作。建议先理清思路再写代码。

cpp 复制代码
namespace ting
{
	template<class K, class V>
	struct BSTreeNode
	{
		BSTreeNode(const K& content, const V& value)
			:_content(content)
			,_left(nullptr)
			,_right(nullptr)
			,_value(value)
		{

		}
		K  _content;
		BSTreeNode<K,V>* _left;
		BSTreeNode<K,V>* _right;

		V _value;
	};
	template<class K, class V>
	class BSTree
	{
		typedef BSTreeNode<K, V> Node;
	public:

		bool Insert(const K& key, const V& value)
		{
			return _Insert(_root, key, value);
		}
		Node* Find(const K& key)
		{
			return _Find(_root, key);
		}

		bool Erase(const K& key)
		{
			return _Erase(_root, key);
		}
		
		void InOrder()
		{
			_InOrder(_root);
		}
		
	private:
		Node* _root = nullptr;

		Node* _Find(Node* root, const K& key)
		{
			while (root != nullptr)
			{
				if (root->_content < key)
				{
					root = root->_right;
				}
				else if (root->_content > key)
				{
					root = root->_left;
				}
				else if (root->_content == key)
				{
					return root;
				}
			}
			return nullptr;
		}

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


		bool _Insert(Node*& root, const K& key, const V& value)
		{
			if (root == nullptr)
			{
				/*root->_content = key;
				root->_value = value;
				return true;*/
				root = new Node(key, value);//这里要给root建一个节点
				return true;
			}
			
			if (root->_content < key)
			{
				return _Insert(root->_right, key, value);
			}
			else if (root->_content > key)
			{
				return _Insert(root->_left, key, value);
			}
			else
			{
				return false;
			}
		}

		bool _Erase(Node*& root, const K& key)
		{
			if (root == nullptr)
				return false;
			
			if (root->_content < key)
			{
				return _Erase(root->_right, key);
			}
			else if (root->_content > key)
			{
				return _Erase(root->_left, key);
			}
			else
			{
				Node* del = root;
				if (root->_left == nullptr)
				{
					root = root->_right;
				}
				else if (root->_right == nullptr)
				{
					root = root->_left;
				}
				else
				{
					Node* prev = root;
					Node* minNode = root->_right;
					while (minNode->_left != nullptr)
					{
						prev = minNode;
						minNode = minNode->_left;
					}

					root->_content = minNode->_content;
					root->_value = minNode->_value;

					if (prev->_left == minNode)
					{
						prev->_left = minNode->_right;
					}
					else
					{
						prev->_right = minNode->_right;
					}

					del = minNode;
				}
				delete del;
				return true;		
			}

		}
	};

	void TestBSTree()
	{
		BSTree<string, string> dict;
		dict.Insert("insert", "插入");
		dict.Insert("erase", "删除");
		dict.Insert("left", "左边");
		dict.Insert("string", "字符串");

		string str;
		while (cin >> str)
		{
			auto ret = dict.Find(str);
			if (ret)
			{
				cout << str << ":" << ret->_value << endl;
			}
			else
			{
				cout << "单词拼写错误" << endl;
			}
		}

		string strs[] = { "苹果", "西瓜", "苹果", "樱桃", "苹果", "樱桃", "苹果", "樱桃", "苹果" };
		// 统计水果出现的次
		BSTree<string, int> countTree;
		for (auto str : strs)
		{
			auto ret = countTree.Find(str);
			if (ret == NULL)
			{
				countTree.Insert(str, 1);
			}
			else
			{
				ret->_value++;
			}
		}
		countTree.InOrder();
	}
}
相关推荐
小林熬夜学编程4 分钟前
【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展
linux·运维·服务器·c语言·网络·c++·http
倔强的石头10615 分钟前
【C++指南】类和对象(九):内部类
开发语言·c++
A懿轩A1 小时前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
机器视觉知识推荐、就业指导1 小时前
C++设计模式:享元模式 (附文字处理系统中的字符对象案例)
c++
半盏茶香1 小时前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
Ronin3052 小时前
11.vector的介绍及模拟实现
开发语言·c++
✿ ༺ ོIT技术༻2 小时前
C++11:新特性&右值引用&移动语义
linux·数据结构·c++
字节高级特工2 小时前
【C++】深入剖析默认成员函数3:拷贝构造函数
c语言·c++
唐诺9 小时前
几种广泛使用的 C++ 编译器
c++·编译器
冷眼看人间恩怨10 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget