c++高阶数据结构:二叉搜索树详解

151 篇文章 1 订阅
129 篇文章 1 订阅
本文详细介绍了二叉搜索树的概念,包括其性质、节点结构以及实现方法,如迭代和递归的查找、插入和删除操作。还探讨了如何将其转化为KV模型应用于单词查找和频率统计等场景。
摘要由CSDN通过智能技术生成

1.二叉搜索树的概念

1.它有两种存在形式,首先就是空树,其次就是下面的几种表现形式

2.若它的左子树不为空,则左子树的所有值小于根节点的值

3.若它的右子树不为空,则右子树的所有值大于根节点的值

4.它的左右子树都是二叉搜索树

2.二叉搜索树的实现

        1.节点

1.结合二叉树的知识,二叉搜索树分别写两个类,一个是节点的类,一个是整个二叉树的类

template<class T>
struct BSTNode
{
	BSTNode(const T& val = T())
		:_val (val)
		,_left(nullptr)
		,_right(nullptr)
	{}
	BSTNode<T>* _left;
	BSTNode<T>* _right;
	T _val;
};

2.注意事项:无论何时调用类模板,都要把模板参数给出

template<class T>
class BSTree
{
	typedef BSTNode<T> Node;
private:
	Node* _node;
};

3.为了方便调用节点模板,先把它typedef为node

        2.构造函数
BSTree()
:_node(nullptr)
{}
        3.拷贝构造函数
BSTree(const BSTree<T>& b)
{
	_root = Copy(b._node);
}
Node* Copy(Node *b)
{
	if (b == nullptr)
		return nullptr;
	Node* p = new Node(b->_val);
	p->_left = Copy(b->_left);
	p->_right = Copy(b->_right);
	return p;
}

1.为了方便用递归,我们用别的函数实现拷贝,再用拷贝构造函数调用那个函数

2.实现使用前序遍历

        4.赋值操作符重载
BSTree<T>& operator=(BSTree<T>b)
{
	swap(b._node, _node);
	return *this;
}

1.临时拷贝一份对象再与根交换,采用现代写法

        5.析构函数
~BSTree()
{
	destory(_node);
}
void destory(Node* node)
{
	if (node == nullptr)
		return;
	if (node->_left)
		destory(node->_left);
	if (node->_right)
		destory(node->_right);
	delete node;
}

1.同样的道理,另写一个函数使用后序遍历进行析构,再用析构函数调用这个函数

        6.中序遍历二叉树
	void _InOrder(Node* node)
	{
		if(node->_left)
		_InOrder(node->_left);
		cout << node->_val << endl;
		if(node->_right)
		_InOrder(node->_right);
	}
	void InOrder()
	{
		_InOrder(_node);
	}

1.中序遍历可以得到二叉树的升序和降序排列

3.查找(时间复杂度O(longn))

        1.迭代实现
Node* Find(const T& val)
{
	Node* node = _node;
	while (node)
	{
		if (node->_val == val)
			return node;
		else if (node->_val > val)
			node = node->_left;
		else
			node = node->_right;
	}
	return nullptr;
}

1.当前节点值小于目标值朝右找,当前节点值大于目标值朝左找

2.找到则返回节点的指针,没找到返回nullptr

        2.递归实现
bool RFind(const T& val)
{
	return _RFind(_node, val);
}
bool _RFind(Node*node,const T& val)
{
	if (node == nullptr)
		return false;
	if (val == node->_val)
		return true;
	return _RFind(node->_left, val) || _RFind(node->_left, val);
 
}

1.返回指针比较困难,所以直接返回真假

4.插入

        1.迭代
bool Insert(const T& val)
{
	if (_node == nullptr)
	{
		_node = new Node(val);
	}
	else
	{
		Node* parent = nullptr;
		Node* child = _node;
		Node* ptr = new Node(val);
		while (child)
		{
			parent = child;
			if (_node->_val > val)
			{
				child = child->_left;
			}
			else if (_node->_val < val)
			{
				 child = child->_right;
			}
			else
				return false;
		}
		if (parent->_val > val)
			parent->_left = ptr;
		if (parent->_val < val)
			parent->_right = ptr;
	}
	return true;
}

1.传入的是引用,当发现根节点为空时直接修改根节点而不需要二级指针

2.定义父亲节点和孩子节点,在循环中找到那个对应的父类(要插入的数据就是他的孩子)

3.判断是放到父节点的左节点还是右节点

4.如果已经存在返回false,如果插入成功返回true

        2.递归
bool RInsert(const T& val)
{
	_RInsert(_node, val);
}
bool _RInsert(Node*& p, const T& val)
{
	if (p == nullptr)
	{
		p = new Node(val);
		return true;
	}
	if (p->_val > val)
		return RInsert(p->_left, val);
	else if (p->_val < val)
		return RInsert(p->_right, val);
	else
		return false;
}

1.还是一个函数调用另一个函数

2.传的是引用,所以不需要父亲节点,直接修改即可

3.插入成功返回true,插入失败返回false

5.删除(难点)

        1.迭代
bool Erase(const T& val)
{
	if (_node == nullptr)
		return true;
	Node* cur = _node;
	Node* parent_node = nullptr;
	while (cur)
	{
		if (cur->_val == val)
			break;
		else if (cur->_val > val)
		{
			parent_node = cur;
			cur = cur->_left;
		}
		else
		{
			parent_node = cur;
			cur = cur->_right;
		}
	}
	if (cur == nullptr)
		return false;
	if (cur->_left == nullptr)
	{
		if (parent_node == nullptr)
			_node = _node->_right;
		 else if (parent_node->_left == cur)
			parent_node->_left = cur->_right;
		 else if (parent_node->_right == cur)
			parent_node->_right = cur->_right;
		delete cur;
	}
	else if (cur->_right == nullptr)
	{
		if (parent_node == nullptr)
			_node = _node->_left;
		else if (parent_node->_left == cur)
			parent_node->_left = cur->_left;
		else
			parent_node->_right = cur->_left;
		delete cur;
	}
	else
	{
		Node* leftmax = cur->_left;
		Node* parent_leftmax = cur;
		while (leftmax->_right)
		{
			parent_leftmax = leftmax;
			leftmax = leftmax->_right;
		}
		cur->_val = leftmax->_val;
		if (parent_leftmax->_left)
			parent_leftmax->_left = leftmax->_left;
		else
			parent_leftmax->_right = leftmax->_left;
		delete leftmax;
	}
}

1.首先查找元素是否在二叉搜索树中,如果不在则返回false,否则要删除的节点分为下面四种情况

        1.要删除的节点无孩子节点

        2.要删除的节点只有左孩子节点

        3.要删除的节点只有右孩子节点

        4.要删除的节点有左右孩子节点

2.实际情况中删除节点有4中情况,但情况1可以与2或3合并起来,真正的删除过程如下

        1.情况2:删除该节点且使被删除节点的父节点指向被删除节点的左孩子节点

        2.情况3:删除该节点且使被删除节点的父节点指向被删除节点的右孩子节点

        3.情况4:在它的左子树中寻找最大的节点a(右树最小也可以),将它的值填补到被删除的节点中,再来处理节点a,再来处理节点a(替换法删除)

        4.再此基础上,还要对每种情况下删除节点是否为根节点进行讨论

        

        2.递归
	bool _RErase(Node*&node,const T&val)
	{
		if (node == nullptr)
			return false;
			if (node->_val > val)
				return _RErase(node->_left, val);
			if (node->_val < val)
				return _RErase(node->_right, val);
			else
			{
				if (node->_left == nullptr)
				{
					Node* right = node->_right;
					delete node;
					node = right;
				}
				else if (node->_right == nullptr)
				{
					Node* left = node->_left;
					delete node;
					node =left;
				}
				else
				{
					Node* p = node->_left;
					while (p->_right)
					{
						p = p->_right;
					}
				
					swap(node->_val , p->_val);
					return  _RErase(node->_left,val);
				}
				return true;
			}
	}
	bool RErase(const T& val)
	{
		return _RErase(_node,val);
	}

1.由于使用了引用,对于左子树和右子树为空不再需要父节点的帮助

2.若左右子树都不为空,则交换letfmax和node数值后,通过递归消除leftmax

3.最后递归传参不要直接传根节点,而是node->left,因为之前的交换打乱了二叉树的结构,只能确定node->_left这棵树还是结构正确的

6.二叉搜索树应用

        1.k模型

1.K 模型即只有 key 作为关键码,结构中 只需要存储Key 即可,关键码即为需要搜索到 的值 。

比如: 给一个单词 word ,判断该单词是否拼写正确 ,具体方式如下:

        1.以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树

        2.在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

        2.KV模型

1.每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对 。该种方式在现实生活中非常常见:

        1.比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英 文单词与其对应的中文<word, chinese>就构成一种键值对;

        2.比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出 现次数就是<word, count>就构成一种键值对

// 改造二叉搜索树为KV结构
template<class K, class V>
struct BSTNode
 {
 BSTNode(const K& key = K(), const V& value = V())
   : _pLeft(nullptr) , _pRight(nullptr), _key(key), _Value(value)
 {}
 BSTNode<T>* _pLeft;
 BSTNode<T>* _pRight;
 K _key;
    V _value
 };
template<class K, class V>
class BSTree
 {
 typedef BSTNode<K, V> Node;
 typedef Node* PNode;
public:
 BSTree(): _pRoot(nullptr){}
 PNode Find(const K& key);
 bool Insert(const K& key, const V& value)
 bool Erase(const K& key)
private:
 PNode _pRoot;
 };
void TestBSTree3()
{
 // 输入单词,查找单词对应的中文翻译
 BSTree<string, string> dict;
 dict.Insert("string", "字符串");
 dict.Insert("tree", "树");
 dict.Insert("left", "左边、剩余");
 dict.Insert("right", "右边");
 dict.Insert("sort", "排序");
 // 插入词库中所有单词
 string str;
 while (cin>>str)
 {
 BSTreeNode<string, string>* ret = dict.Find(str);
 if (ret == nullptr)
 {
 cout << "单词拼写错误,词库中没有这个单词:" <<str <<endl;
 }
 else
 {
 cout << str << "中文翻译:" << ret->_value << endl;
 }
 }
}
void TestBSTree4()
{
 // 统计水果出现的次数
 string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", 
"苹果", "香蕉", "苹果", "香蕉" };
 BSTree<string, int> countTree;
 for (const auto& str : arr)
 {
 // 先查找水果在不在搜索树中
 // 1、不在,说明水果第一次出现,则插入<水果, 1>
 // 2、在,则查找到的节点中水果对应的次数++
 //BSTreeNode<string, int>* ret = countTree.Find(str);
 auto ret = countTree.Find(str);
 if (ret == NULL)
 {
 countTree.Insert(str, 1);
 }
 else
 {
 ret->_value++;
 }
 }
 countTree.InOrder();
}

以上为改进二叉搜索树KV结构,以上所有函数的测试原码笔者正在抓紧制作中,欢迎以后来看看哦

                                                        

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值