C++的二叉搜索树

目录

基本概念

二叉搜索树的实现 

插入结点

查找结点

删除结点

删除结点左为空

删除结点右为空

基于特殊情况的优化

删除结点左右不为空

基于特殊情况的优化

完整代码 

二叉搜索树的实际应用

K和KV模型

改造二叉搜索树为为KV模型


基本概念

1、二叉搜索树又称二叉排序树

2、左子树上的所有值均小于根节点,右子树上的所有值均大于根节点

3、一个结点的左右子树均为二叉搜索树

二叉搜索树的实现 

插入结点

bool Insert(const T& key)
{
    //头结点为空就造一个头结点
	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;//成功插入返回true
}

查找结点

bool Find(const T& key)
{
	Node* cur = root;//从头结点开始查找
	while (cur)
	{
		if (cur->key< key)
		{
			cur = cur->right;
		}
		else if (cur->key> key)
		{
			cur = cur->left;
		}
		else
		{
			return true;//遍历找到了cur->key== key就返回true
		}
	}
	return false;//没找到就返回false
}

删除结点

删除结点左为空

//删除结点的左节点为空
if (cur->left == nullptr)
{
	if(cur == parent->left)
	{
		parent->left = cur->right;
	}
	else
	{
		parent->right = cur->left;
	}
	delete cur;
}

删除结点右为空

//删除结点的右节点为空
else if (cur->right == nullptr)
{
	if (cur == parent->left)
	{
		parent->left = cur->left;
	}
	else
	{
		parent->right = cur->left;
	}
	delete cur;
}

基于特殊情况的优化

//删除结点的左节点为空
if (cur->left == nullptr)
{
	//删除结点是根节点
	if (cur == root)
	{
		root = cur->right;
	}
	else
	{
		if(cur == parent->left)
		{
			parent->left = cur->right;
		}
		else
		{
			parent->right = cur->left;
		}
	}
	delete cur;
}

//删除结点的右节点为空
else if (cur->right == nullptr)
{
	//删除结点是根节点
	if (cur == root)
	{			
        root = cur->left;
		
	}
	else
	{
		if (cur == parent->left)
		{
			parent->left = cur->left;
		}
		else
		{
			parent->right = cur->left;
		}
	}
	delete cur;
}

删除结点左右不为空

图画反了 

//删除结点的左右结点均不为空
else
{
	//查找右子树的最左结点替代删除
	Node* rightMinParent = null;//记录交换结点的父亲结点
	Node* rightMin = cur->right;//记录交换节点

	//遍历寻找删除结点的右子树的最左结点
	while (rightMin->left)
	{
		rightMinParent = rightMin;
		rightMin = rightMin->left;
	}
				
	swap(cur->key, rightMin->key);
	rightMinParent->left = rightMin->right;//防止交换结点点还有右子树(交换结点不可能有左子树,
    //因为交换结点就是删除结点的右子树的最左结点,如果它还有左子树那么最左结点就不是它)
	delete rightMin;//rightMin负责找到交换结点,找到并交换后它没用了可以直接删除
}

基于特殊情况的优化

//删除结点的左右结点均不为空
else
{
	//查找右子树的最左结点替代删除
	Node* rightMinParent = cur;//如果要删除的是根节点,(即使不删除根节点,一旦进入循环则parent也会直接发生变化)
	Node* rightMin = cur->right;//记录交换节点
	//遍历寻找
	while (rightMin->left)
	{
		rightMinParent = rightMin;
		rightMin = rightMin->left;
	}
				
	swap(cur->value, rightMin->key);
    //rightMin是parent的左,就令parent的左指向rightMin的右
	if (rightMinParent->left == rightMin)//不删除根节点时都会满足该条件
		rightMinParent->left = rightMin->right;
    //rightMin是parent的右,就令parent的左指向rightMin的右
	else
		rightMinParent->right = rightMin->right;//处理删除根节点的特殊情况
	delete rightMin;
}

完整代码 

#pragma once
#include <iostream>
using namespace std;

template<class T>
struct  BSTreeNode
{
	BSTreeNode<T>* left;
	BSTreeNode<T>* right;
	T value;

	BSTreeNode(const T& key)
		:left(nullptr)
		,right(nullptr)
		,value(key)
	{}
};


template<class T>
class  BSTree
{
	typedef BSTreeNode<T> Node;
public:
bool Insert(const T& key)
{
	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;//成功插入返回true
}

bool Find(const T& 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 T& key)
{
	Node* parent = nullptr;
	Node* cur = root;
	while (cur)
	{
		if (cur->key< key)
		{
			cur = cur->right;
		}
		else if (cur->key> key)
		{
			cur = cur->left;
		}
		//遍历寻找到要删除的值
		else
		{
			//删除结点的左节点为空
			if (cur->left == nullptr)
			{
				//删除结点是根节点
				if (cur == root)
				{
					root = cur->right;
				}
				else
				{
					if(cur == parent->left)
					{
					parent->left = cur->right;
					}
					else
					{
						parent->right = cur->left;
					}
				}
				delete cur;
			}

			//删除结点的右节点为空
			else if (cur->right == nullptr)
			{
				//删除结点是根节点
				if (cur == root)
				{
					root = cur->left;
				}
				else
				{
					if (cur == parent->left)
					{
						parent->left = cur->left;
					}
					else
					{
						parent->right = cur->left;
					}
				}
				delete cur;
			}
			//删除结点的左右结点均不为空
			else
			{
				//查找右子树的最左结点替代删除
				Node* rightMinParent = cur;
				Node* rightMin = cur->right;
				//遍历寻找
				while (rightMin->left)
				{
					rightMinParent = rightMin;
					rightMin = rightMin->left;
				}
				
				swap(cur->key, rightMin->key);
				if (rightMinParent->left == rightMin)
					rightMinParent->left = rightMin->right;
				else
					rightMinParent->right = rightMin->right;
				delete rightMin;
			}
			return true;
		}
	}
	return false;
}

public:
	//套一层(友元、套一层、get三种方式获取类内的数据)
	void InOrder()
	{
		_InOrder(root);
		cout << endl;
	}

private:

	//循环遍历
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		} 

		_InOrder(root->left);
		cout << root->key<< " ";
		_InOrder(root->right);
	}

private:
	Node* root = nullptr;
};

void test()
{
	int a[] = { 8,3,1,10,6,4,7,14,13 };
	BSTree<int> t1;
	//循环插入
	for (auto e : a)
	{
		t1.Insert(e);
	}

	//中序遍历
	t1.InOrder();

	//删除结点
	t1.erase(8);

	//中序遍历
	t1.InOrder();
}

  • 时间复杂度:O(n)或 O(logn)
  • O(n)

  • O(logn)

二叉搜索树的实际应用

K和KV模型

K模型:只有key作为关键码,结构中只需存储key即可,key就是要搜索的值(以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误)

KV模型:每一个关键码key都有与之对应的值value,即<key,value>键值对(英汉词典中的中英文之间的对应关系<word,chinese>,通过中文可以快速找到对应的中文,统计单词的出现次数,统计成功后,给定某一个单词就能快速找到其出现的次数<word,count>)

查找的方式:

  • 二分查找
  • 二叉树搜索查找 -> AVL树和红黑树
  • 哈希查找
  • 跳表查找
  • 多叉搜索树查找:B树系列

改造二叉搜索树为为KV模型

//KV模型
namespace key_value
{
template<class K,class V>
struct  BSTreeNode
{
	BSTreeNode<K,V>* left;
	BSTreeNode<K,V>* right;
	K key;
	V _value;

	BSTreeNode(const K& key,const V& value)
		:left(nullptr)
		,right(nullptr)
		,key(key)
		,_value(value)
	{}
};


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;//成功插入返回true
}

Node* 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 cur;
		}
	}
	return cur;
}

bool erase(const K& key)
{
	Node* parent = nullptr;
	Node* cur = root;
	while (cur)
	{
		if (cur->key < key)
		{
			cur = cur->right;
		}
		else if (cur->key > key)
		{
			cur = cur->left;
		}
		//遍历寻找到要删除的值
		else
		{
			//删除结点的左节点为空
			if (cur->left == nullptr)
			{
				//删除结点是根节点
				if (cur == root)
				{
					root = cur->right;
				}
				else
				{
					if (cur == parent->left)
					{
						parent->left = cur->right;
					}
					else
					{
						parent->right = cur->left;
					}
				}
				delete cur;
			}

			//删除结点的右节点为空
			else if (cur->right == nullptr)
			{
				//删除结点是根节点
				if (cur == root)
				{
					root = cur->left;
				}
				else
				{
					if (cur == parent->left)
					{
						parent->left = cur->left;
					}
					else
					{
						parent->right = cur->left;
					}
				}
				delete cur;
			}
			//删除结点的左右结点均不为空
			else
			{
				//查找右子树的最左结点替代删除
				Node* rightMinParent = cur;
				Node* rightMin = cur->right;
				//遍历寻找
				while (rightMin->left)
				{
					rightMinParent = rightMin;
					rightMin = rightMin->left;
				}

				swap(cur->key, rightMin->key);
				if (rightMinParent->left == rightMin)
					rightMinParent->left = rightMin->right;
				else
					rightMinParent->right = rightMin->right;
				delete rightMin;
			}
			return true;
		}
	}
	return false;
}

public:
	//套一层(友元、套一层、get三种方式获取类内的数据)
	void InOrder()
	{
		_InOrder(root);
		cout << endl;
	}

private:

	//循环遍历
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->left);
		cout << root->key << ":" <<root->_value<<endl;
		_InOrder(root->right);
	}

private:
	Node* root = nullptr;
};

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

		string str;
		while (cin >> str)
		{
			BSTreeNode<string, string>* ret = dict.Find(str);//Find函数的返回值变为了结点的指针
			if (ret)
			{
				cout << ret->_value << endl;
			}
			else
			{
				cout << "无此单词,请重新输入" << endl;
			}
		}
	}

	void test1()
	{
		//统计次数
		string arr[] = { "苹果","西瓜","香蕉","西瓜","香蕉" ,"西瓜","香蕉" ,"西瓜","草莓" };
		BSTree<string, int> countTree;
		for (const auto& str : arr)
		{
			auto ret = countTree.Find(str);
			if (ret == nullptr)
			{
				countTree.Insert(str, 1);
			}
			else
			{
				ret->_value++;
			}
		}
		countTree.InOrder();
	
	}
}

~over~

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2024-04-30 17:30:02       19 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-30 17:30:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-30 17:30:02       20 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-30 17:30:02       20 阅读

热门阅读

  1. 【JDBC】数据库连接池

    2024-04-30 17:30:02       12 阅读
  2. Unity Android(十) 适配Android14系统

    2024-04-30 17:30:02       12 阅读
  3. C# 请求第三方API

    2024-04-30 17:30:02       12 阅读
  4. 区块链技术的应用场景和优势

    2024-04-30 17:30:02       13 阅读
  5. C语言题目

    2024-04-30 17:30:02       11 阅读
  6. 动态规划:0/1背包问题

    2024-04-30 17:30:02       14 阅读
  7. MySql 前缀索引

    2024-04-30 17:30:02       15 阅读
  8. leetcode热题HOT 287. 寻找重复数

    2024-04-30 17:30:02       34 阅读
  9. 联系Odoo partner邮件怎么写

    2024-04-30 17:30:02       37 阅读
  10. 题目 2662: 李白打酒加强版

    2024-04-30 17:30:02       15 阅读