数据结构高阶–二叉搜索树(原理+实现)

二叉搜索树

概念

二叉搜索树又称为二叉排序树,因为这棵树的中序遍历是有序的。二叉搜索树总结起来有以下几个性质:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于于根节点的值
  • 它的左右子树都是二叉搜索树
  • 这棵树中没有重复的元素

举个例子:

数据结构高阶--二叉搜索树(原理+实现)

二叉搜索树的实现

基本框架

template <class K,class V> struct  BST_Node { 	BST_Node<K,V>* left; 	BST_Node<K,V>* right; 	K key; 	V value; 	//构造函数 	BST_Node(const K& key, const V& value):left(nullptr),right(nullptr),key(key),value(value) 	{} }; template <class K,class V> class BST_Tree { 	typedef BST_Node<K,V> Node; public: private: 	Node* root = nullptr; }; 

要实现的接口

bool Insert(const K& key,const V& value);//二叉搜索树的插入 void InOrder();//打印功能,采用中序遍历(递归) Node* Find(const K& key);//二叉搜索树的查找 bool Erase(const K& key);//二叉搜索树的删除 

二叉搜索树的插入

插入分为下面几个步骤:

  • 先判断树是否为空,为空就让要插入的这个节点作为根节点,然后结束
  • 确定要插入节点的位置
  • 用一个cur记录当前节点,parent记录父节点
  • 要插入节点的值如果比当前节点的值小,cur就往左走,如果比当前节点的值大,就往右子树走,如果等于就返回false,表面这棵树中有这个数据,不需要插入
//二叉搜索树的插入 	bool Insert(const K& key,const V& value) 	{ 		//没有节点的时候就是根节点 		if (root == nullptr) 		{ 			root = new Node(key,value); 			return true; 		} 		//用一个父节点记录cur的上一个节点 		Node* parent = nullptr; 		Node* cur = root; 		while (cur) 		{ 			parent = cur; 			//小于往左走 			if (key < cur->key) 			{ 				cur = cur->left; 			} 			else if (key > cur->key) 			{ 				cur = cur->right; 			} 			else 				return false; 		} 		cur = new Node(key,value); 		//判断应该插在父节点的左边还是右边 		if (cur->key < parent->key) 		{ 			parent->left = cur; 		} 		else 		{ 			parent->right = cur; 		} 		return true; 	} 

打印二叉搜索树(中序遍历)

//中序遍历(递归) 	void InOrder() 	{ 		_InOrder(root); 		cout << endl; 	} 	void _InOrder(Node* root) 	{ 		if (root == NULL) 		{ 			return; 		} 		else 		{ 			_InOrder(root->left); 			cout << root->key << ":" << root->value << endl; 			_InOrder(root->right); 		} 	} 

二叉搜索树的查找

查找的步骤如下:(和插入的步骤有些类似)

  • 如果查找值key比当前节点的值小,就往左子树走
  • 如果查找值key比当前节点的值大,就往右子树走
  • 如果查找值key和当前节点的值相等,就返回当前节点的指针
//二叉搜索树的查找 	Node* Find(const K& key) 	{ 		if (root == nullptr) 		{ 			return nullptr; 		} 		Node* cur = root;//遍历结点 		while (cur) 		{ 			//小于往左走 			if (cur->key > key) 			{ 				cur = cur->left; 			} 			else if (cur->key < key) 			{ 				cur = cur->right; 			} 			else 			{ 				return cur; 			} 		} 		return nullptr; 	} 

二叉搜索树的删除(难)

分四种情况:我们一个一个来讨论

以下面这颗树为例:

数据结构高阶--二叉搜索树(原理+实现)

情景一:

数据结构高阶--二叉搜索树(原理+实现)

情景二:

数据结构高阶--二叉搜索树(原理+实现)

还要分析一种特殊的情况,就是此时2没有父亲节点,也就是自己为根时,看下面如何操作

数据结构高阶--二叉搜索树(原理+实现)

情景三:

数据结构高阶--二叉搜索树(原理+实现)

该节点如果为根节点,就让自己的右孩子变成根节点

数据结构高阶--二叉搜索树(原理+实现)

情景四:

数据结构高阶--二叉搜索树(原理+实现)

总结: 一共有四种情况,但是情况1可以归为情况3,因为它也是左为空,所以整体处理下来是三种情况

//二叉搜索树的删除 bool Erase(const K& key) { 	//树为空,删除失败 	if (root == nullptr) 	{ 		return false; 	} 	//parent始终是cur的父亲节点 	//cur就是要找的删除的当前节点 	Node* parent = nullptr; 	Node* cur = root; 	while (cur) 	{ 		//小于往左边走 		if (key < cur->key) 		{ 			parent = cur; 			cur = cur->left; 		} 		else if (key > cur->key) 		{ 			parent = cur; 			cur = cur->right; 		} 		else 		{ 			// 找到了,开始删除 			// 1.左右子树都为空,直接删除,可以归类为左为空 			// 2.左右子树只有一边为空,左为空,父亲指向我的右,右为空,父亲指向我的左   			// 3.左右子树都不为空,取左子树最大的节点或右子树最小的节点和要删除的节点交换,然后再删除  			//当前情况是情景三,删除的节点它的左为空,右未知 			if (cur->left == nullptr) 			{ 				// 要删除节点为根节点时,直接把右子树的根节点赋值给——root 				// 根节点的话会导致parent为nullptr 				if (root == cur) 				{ 					root = root->right; 				} 				else 				{ 					//左为空,父亲指向我的右 					//判断cur在父亲的左还是右 					if (parent->left == cur) 					{ 						parent->left = cur->right; 					} 					else 					{ 						parent->right = cur->right; 					} 				} 				delete cur; 				cur = nullptr; 			} 			//当前情况是情景二,删除节点它的右为空,左未知 			else if (cur->right == nullptr) 			{ 				if (root ==cur ) 				{ 					root = root->left; 				} 				else 				{ 					//右为空,父亲指向我的左 					//判断cur在父亲的左还是右 					if (parent->left == cur) 					{ 						parent->left = cur->left; 					} 					else 					{ 						parent->right = cur->left; 					} 				} 				delete cur; 				cur = nullptr; 			} 			//只剩下情景四 			else 			{ 				//找右子树中最小的节点,当前cur就是要删除的节点 				Node* rightMinParent = cur; 				Node* rightMin = cur->right;//去右子树找最小的节点 				while (rightMin->left) 				{ 					rightMinParent = rightMin; 					rightMin = rightMin->left; 				} 				//替代删除 				cur->key = rightMin->key; 				//转化成了情景三,左孩子为空 				if (rightMinParent->left == rightMin) 					rightMinParent->left = rightMin->right; 				else 					rightMinParent->right = rightMin->right;  				delete rightMin; 				rightMin = nullptr; 			} 			return true; 		} 	} 	return false; } 

完整代码以及测试

#define _CRT_SECURE_NO_WARNINGS #include<iostream> //引入头文件 #include<string>//C++中的字符串 using namespace std; //标准命名空间 template <class K > struct  BST_Node { 	BST_Node<K>* left; 	BST_Node<K>* right; 	K key; 	//构造函数 	BST_Node(const K& key):left(nullptr),right(nullptr),key(key) 	{} }; template <class K> class BST_Tree { 	typedef BST_Node<K> Node; public: 	//二叉搜索树的插入 	bool Insert(const K& key) 	{ 		//没有节点的时候就是根节点 		if (root == nullptr) 		{ 			root = new Node(key); 			return true; 		} 		//用一个父节点记录cur的上一个节点 		Node* parent = nullptr; 		Node* cur = root; 		while (cur) 		{ 			parent = cur; 			//小于往左走 			if (key < cur->key) 			{ 				cur = cur->left; 			} 			else if (key > cur->key) 			{ 				cur = cur->right; 			} 			else 				return false; 		} 		cur = new Node(key); 		//判断应该插在父节点的左边还是右边 		if (cur->key < parent->key) 		{ 			parent->left = cur; 		} 		else 		{ 			parent->right = cur; 		} 		return true; 	} 	//中序遍历(递归) 	void InOrder() 	{ 		_InOrder(root); 		cout << endl; 	} 	void _InOrder(Node* root) 	{ 		if (root == NULL) 		{ 			return; 		} 		else 		{ 			_InOrder(root->left); 			cout << root->key <<" "; 			_InOrder(root->right); 		} 	} 	//二叉搜索树的查找 	Node* Find(const K& key) 	{ 		if (root == nullptr) 		{ 			return nullptr; 		} 		Node* cur = root;//遍历结点 		while (cur) 		{ 			//小于往左走 			if (cur->key > key) 			{ 				cur = cur->left; 			} 			else if (cur->key < key) 			{ 				cur = cur->right; 			} 			else 			{ 				return cur; 			} 		} 		return nullptr; 	} 	//二叉搜索树的删除 	bool Erase(const K& key) 	{ 		//树为空,删除失败 		if (root == nullptr) 		{ 			return false; 		} 		//parent始终是cur的父亲节点 		//cur就是要找的删除的当前节点 		Node* parent = nullptr; 		Node* cur = root; 		while (cur) 		{ 			//小于往左边走 			if (key < cur->key) 			{ 				parent = cur; 				cur = cur->left; 			} 			else if (key > cur->key) 			{ 				parent = cur; 				cur = cur->right; 			} 			else 			{ 				// 找到了,开始删除 				// 1.左右子树都为空,直接删除,可以归类为左为空 				// 2.左右子树只有一边为空,左为空,父亲指向我的右,右为空,父亲指向我的左   				// 3.左右子树都不为空,取左子树最大的节点或右子树最小的节点和要删除的节点交换,然后再删除  				//当前情况是情景三,删除的节点它的左为空,右未知 				if (cur->left == nullptr) 				{ 					// 要删除节点为根节点时,直接把右子树的根节点赋值给——root 					// 根节点的话会导致parent为nullptr 					if (root == cur) 					{ 						root = root->right; 					} 					else 					{ 						//左为空,父亲指向我的右 						//判断cur在父亲的左还是右 						if (parent->left == cur) 						{ 							parent->left = cur->right; 						} 						else 						{ 							parent->right = cur->right; 						}  					} 					delete cur; 					cur = nullptr;  				} 				//当前情况是情景二,删除节点它的右为空,左未知 				else if (cur->right == nullptr) 				{ 					if (root ==cur ) 					{ 						root = root->left; 					} 					else 					{ 						//右为空,父亲指向我的左 						//判断cur在父亲的左还是右 						if (parent->left == cur) 						{ 							parent->left = cur->left; 						} 						else 						{ 							parent->right = cur->left; 						} 					} 					delete cur; 					cur = nullptr; 				} 				//只剩下情景四 				else 				{ 					//找右子树中最小的节点,当前cur就是要删除的节点 					Node* rightMinParent = cur; 					Node* rightMin = cur->right;//去右子树找最小的节点 					while (rightMin->left) 					{ 						rightMinParent = rightMin; 						rightMin = rightMin->left; 					} 					//替代删除 					cur->key = rightMin->key; 					//转化成了情景三,左孩子为空 					if (rightMinParent->left == rightMin) 						rightMinParent->left = rightMin->right; 					else 						rightMinParent->right = rightMin->right;  					delete rightMin; 					rightMin = nullptr; 				} 				return true; 			} 		} 		return false; 	} private: 	Node* root = nullptr; }; void TestBSTree() { 	BST_Tree<int> bt; 	int arr[] = { 5,3,4,1,7,8,2,6,0,9 }; 	for (auto e : arr) 	{ 		cout << "插入 " << e << " 后:"; 		bt.Insert(e); 		bt.InOrder(); 	}  	cout << "------------------------------" << endl; 	for (auto e : arr) 	{ 		cout << "删除 " << e << " 后:"; 		bt.Erase(e); 		bt.InOrder(); 	}  } int main() { 	TestBSTree(); 	system("pause"); 	return EXIT_SUCCESS; } 

二叉搜索树的应用

二叉搜索树有两种模型:

  • K模型: K模型只有key值,节点只存储key值。这里主要应用就是查找判断某个元素在不在。
  • KV模型: KV模型每个key值都对应着一个value,主要应用就是通过key找value。(我们平时查找单词就是通过中文找英文,或者通过英文找中文)

上面的测试代码是KV模型改成了K模型,接下来我们来看看KV模型的作用

#define _CRT_SECURE_NO_WARNINGS #include<iostream> //引入头文件 #include<string>//C++中的字符串 using namespace std; //标准命名空间 template <class K,class V> struct  BST_Node { 	BST_Node<K,V>* left; 	BST_Node<K,V>* right; 	K key; 	V value; 	//构造函数 	BST_Node(const K& key, const V& value):left(nullptr),right(nullptr),key(key),value(value) 	{} }; template <class K,class V> class BST_Tree { 	typedef BST_Node<K,V> Node; public: 	//二叉搜索树的插入 	bool Insert(const K& key,const V& value) 	{ 		//没有节点的时候就是根节点 		if (root == nullptr) 		{ 			root = new Node(key,value); 			return true; 		} 		//用一个父节点记录cur的上一个节点 		Node* parent = nullptr; 		Node* cur = root; 		while (cur) 		{ 			parent = cur; 			//小于往左走 			if (key < cur->key) 			{ 				cur = cur->left; 			} 			else if (key > cur->key) 			{ 				cur = cur->right; 			} 			else 				return false; 		} 		cur = new Node(key,value); 		//判断应该插在父节点的左边还是右边 		if (cur->key < parent->key) 		{ 			parent->left = cur; 		} 		else 		{ 			parent->right = cur; 		} 		return true; 	} 	//中序遍历(递归) 	void InOrder() 	{ 		_InOrder(root); 		cout << endl; 	} 	void _InOrder(Node* root) 	{ 		if (root == NULL) 		{ 			return; 		} 		else 		{ 			_InOrder(root->left); 			cout << root->key << ":" << root->value << endl; 			_InOrder(root->right); 		} 	} 	//二叉搜索树的查找 	Node* Find(const K& key) 	{ 		if (root == nullptr) 		{ 			return nullptr; 		} 		Node* cur = root;//遍历结点 		while (cur) 		{ 			//小于往左走 			if (cur->key > key) 			{ 				cur = cur->left; 			} 			else if (cur->key < key) 			{ 				cur = cur->right; 			} 			else 			{ 				return cur; 			} 		} 		return nullptr; 	} 	//二叉搜索树的删除 	bool Erase(const K& key) 	{ 		//树为空,删除失败 		if (root == nullptr) 		{ 			return false; 		} 		//parent始终是cur的父亲节点 		//cur就是要找的删除的当前节点 		Node* parent = nullptr; 		Node* cur = root; 		while (cur) 		{ 			//小于往左边走 			if (key < cur->key) 			{ 				parent = cur; 				cur = cur->left; 			} 			else if (key > cur->key) 			{ 				parent = cur; 				cur = cur->right; 			} 			else 			{ 				// 找到了,开始删除 				// 1.左右子树都为空,直接删除,可以归类为左为空 				// 2.左右子树只有一边为空,左为空,父亲指向我的右,右为空,父亲指向我的左   				// 3.左右子树都不为空,取左子树最大的节点或右子树最小的节点和要删除的节点交换,然后再删除  				//当前情况是情景三,删除的节点它的左为空,右未知 				if (cur->left == nullptr) 				{ 					// 要删除节点为根节点时,直接把右子树的根节点赋值给——root 					// 根节点的话会导致parent为nullptr 					if (root == cur) 					{ 						root = root->right; 					} 					else 					{ 						//左为空,父亲指向我的右 						//判断cur在父亲的左还是右 						if (parent->left == cur) 						{ 							parent->left = cur->right; 						} 						else 						{ 							parent->right = cur->right; 						}  					} 					delete cur; 					cur = nullptr;  				} 				//当前情况是情景二,删除节点它的右为空,左未知 				else if (cur->right == nullptr) 				{ 					if (root ==cur ) 					{ 						root = root->left; 					} 					else 					{ 						//右为空,父亲指向我的左 						//判断cur在父亲的左还是右 						if (parent->left == cur) 						{ 							parent->left = cur->left; 						} 						else 						{ 							parent->right = cur->left; 						} 					} 					delete cur; 					cur = nullptr; 				} 				//只剩下情景四 				else 				{ 					//找右子树中最小的节点,当前cur就是要删除的节点 					Node* rightMinParent = cur; 					Node* rightMin = cur->right;//去右子树找最小的节点 					while (rightMin->left) 					{ 						rightMinParent = rightMin; 						rightMin = rightMin->left; 					} 					//替代删除 					cur->key = rightMin->key; 					//转化成了情景三,左孩子为空 					if (rightMinParent->left == rightMin) 						rightMinParent->left = rightMin->right; 					else 						rightMinParent->right = rightMin->right;  					delete rightMin; 					rightMin = nullptr; 				} 				return true; 			} 		} 		return false; 	} private: 	Node* root = nullptr; }; int main() { 	system("pause"); 	return EXIT_SUCCESS; } 

实例1

英汉字典

void TestBSTree_KV1() { 	// 创建一个简易的字典 	BST_Tree<string, string> dict;  	dict.Insert("苹果", "apple"); 	dict.Insert("香蕉", "banana"); 	dict.Insert("橘子", "orange"); 	dict.Insert("葡萄", "grape"); 	dict.Insert("apple", "苹果"); 	dict.Insert("banana", "香蕉"); 	dict.Insert("orange", "橘子"); 	dict.Insert("grape", "葡萄");  	string str; 	while (cin >> str) 	{ 		BST_Node<string, string>* ret = dict.Find(str); 		if (ret) 		{ 			cout << ret->value << endl; 		} 		else 		{ 			cout << "本字典无此词" << endl; 		} 	} } 

实例2

统计树

void TestBSTree_KV2() { 	// 统计水果个数 	BST_Tree<string, int> countTree;  	string strArr[] = { "香蕉","水蜜桃","西瓜","苹果","香蕉" ,"西瓜","香蕉" ,"苹果","西瓜","苹果","苹果","香蕉" ,"水蜜桃" };  	for (auto e : strArr) 	{ 		BST_Node<string, int>* ret = countTree.Find(e); 		if (ret == nullptr) 		{ 			// 第一次插入 			countTree.Insert(e, 1); 		} 		else 		{ 			ret->value++; 		} 	}  	countTree.InOrder(); } 

发表评论

评论已关闭。

相关文章