c++二插搜索树

news2025/1/16 2:35:58

1二插搜索树的概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

​ 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
​ 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
​ 它的左右子树也分别为二叉搜索树

image-20220513124614728

2二插搜索树的实现

#pragma once
#include <iostream>
using std::cout;
template <class K>
struct BSTreeNode
{
	K _key;
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;

	BSTreeNode(const K& key)
		:_key(key)
		, _left(nullptr)
		, _right(nullptr)
	{}
};

template <class K>
class BSTree
{
public:
	typedef BSTreeNode<K> Node;
	BSTree()
		:_root(nullptr)
	{}
	~BSTree()
	{
		Destroy(_root);
		_root = nullptr;
	}
	BSTree(const BSTree<K>& t)
	{
		_root = CopyTree(t._root);
	}
	BSTree<K>& operator=(BSTree<K> t)
	{
		std::swap(_root, t._root);
		return *this;
	}

	bool Insert(const K& key) 
	{
		//今天我们实现的二叉搜索树不允许插入同一个值
		if (_root == nullptr)
		{
			//第一次插入数据
			_root = new Node(key);
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (cur->_key > key) 
			{
				cur = cur->_left;
			}
			else if (cur->_key < key) 
			{
				cur = cur->_right;
			}
			else//树中存在相同的值
				return false;
		}
		cur = new Node(key);
		//判断我们插入的节点是在parent的左还是右
		if (parent->_key > key)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		return true;
	}
	bool Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else
			{
				return true;
			}
		}
		return false;
	}

	bool Erase(const K& key)
	{
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				//找到了 处理删除的细节
				//被删除的节点右四种情况
				//1.左右孩子都为空(叶子节点) 2. 左为空右不为空  3.右为空左不为空  4.左右都不为空
				if (cur->_left == nullptr)
				{
					//这里我们可以处理情况1和2,让父亲指向cur的右
					if (parent->_left == cur)
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
					delete cur;
					return true;
				}
				else if (cur->_right == nullptr)
				{
					//这里我们可以处理情况3,让父亲指向cur的左
					if (parent->_left == cur)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
					delete cur;
					return true;
				}
				else
				{
					//情况4,左右都不为空,这种情况我们可以将被删节点和其右子树的最左节点(或左子树的最右节点互换位置)
					//互换之后删除节点,不会破坏搜索树的结构
					//这里我们以右子树的最左节点互换为例
					Node* minRight = cur->_right;
					Node* minParent = cur;
					while (minRight->_left)
					{
						minParent = minRight;
						minRight = minRight->_left;
					}
					std::swap(cur->_key, minRight->_key);

					//当然我们也可以选择直接覆盖被删节点
					//cur->_key = minRight->_key;

					//然后删除minRight节点即可
					//注意这里一定不能再次递归调用Erase(key),因为互换位置之后搜索树结构已经被破坏
					if (minParent->_left == minRight)
					{
						minParent->_left = minRight->_right;
					}
					else
					{
						minParent->_right = minRight->_right;
					}
					delete minRight;
					return true;
				}
			}

		}
		//没找到
		return false;
	}

	bool EraseR(const K& key)
	{
		return _EraseR(_root, key);
	}
	bool FindR(const K& key)
	{
		return _FindR(_root, key);
	}

	void InOrder()
	{
		_InOrder(_root);
	}
private:
	void Destroy(Node* root)
	{
		if (root == nullptr)
			return;
		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
	}
	Node* CopyTree(Node* root)
	{
		if (root == nullptr)
		{
			return nullptr;
		}
		Node* copyNode = new Node(root->_key);
		copyNode->_left = CopyTree(root->_left);
		copyNode->_right = CopyTree(root->_right);
		return copyNode;
	}
	bool _EraseR(Node*& root, const K& key)
	{
		if (root == nullptr)//不存在
			return false;
		if (root->_key > key)
		{
			_EraseR(root->_left, key);
		}
		else if (root->_key < key)
		{
			_EraseR(root->_right, key);
		}
		else
		{
			//找到了该被删除的值
			Node* del = root;
			if (root->_left == nullptr)
			{
				//我们传参的时候传的是Node*& root,因此root不仅指向被删除的值
				//root还恰好是root的父亲节点指向root的那个指针
				//我们只需改变root的指向即可
				root = root->_right;
			}
			else if (root->_right == nullptr)
			{
				root = root->_left;
			}
			else
			{
				Node* minRight = root->_right;
				while (minRight->_left)
				{
					minRight = minRight->_left;
				}
				std::swap(minRight->_key, root->_key);
				return _EraseR(root->_right, key);//这次递归调用最终一定会走到左为空的情况
			}
			delete del;
			return true;
		}

	}
	bool _FindR(Node* root, const K& key)
	{
		if (root == nullptr)
			return false;
		if (root->_key > key)
		{
			_FindR(root->_left, key);
		}
		else if (root->_key < key)
		{
			_FindR(root->_right, key);
		}
		else
			return true;
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}


private:
	Node* _root;
};

3.二插搜索树的应用

  1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
    比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
    以单词集合中的每个单词作为key,构建一棵二叉搜索树
    在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
  2. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word,count>就构成一种键值对。
    比如:实现一个简单的英汉词典dict,可以通过英文找到与其对应的中文,具体实现方式如下:
    <单词,中文含义>为键值对构造二叉搜索树,注意:二叉搜索树需要比较,键值对比较时只比较Key查询英文单词时,只需给出英文单词,就可快速找到与其对应的key

现在我们将上面的搜索二叉树改成kv模型

#pragma once
#include <iostream>
using std::cout;
template <class K, class V>
struct BSTreeNode
{
	K _key;
	V _val;
	BSTreeNode<K, V>* _left;
	BSTreeNode<K, V>* _right;

	BSTreeNode(const K& key, const V& val)
		:_key(key)
		,_val(val)
		, _left(nullptr)
		, _right(nullptr)
	{}
};

template <class K, class V>
class BSTree
{
public:
	typedef BSTreeNode<K, V> Node;
	BSTree()
		:_root(nullptr)
	{}
	~BSTree()
	{
		Destroy(_root);
		_root = nullptr;
	}
	BSTree(const BSTree<K, V>& t)
	{
		_root = CopyTree(t._root);
	}
	BSTree<K, V>& operator=(BSTree<K, V> t)
	{
		std::swap(_root, t._root);
		return *this;
	}

	bool Insert(const K& key, const V& val)
	{
		//今天我们实现的二叉搜索树不允许插入同一个值
		if (_root == nullptr)
		{
			//第一次插入数据
			_root = new Node(key, val);
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else//树中存在相同的值
				return false;
		}
		cur = new Node(key, val);
		//判断我们插入的节点是在parent的左还是右
		if (parent->_key > key)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		return true;
	}
	Node* Find(const K& key)
	{
		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)
	{
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{

			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				//找到了 处理删除的细节
				//被删除的节点右四种情况
				//1.左右孩子都为空(叶子节点) 2. 左为空右不为空  3.右为空左不为空  4.左右都不为空
				if (cur->_left == nullptr)
				{
					//这里我们可以处理情况1和2,让父亲指向cur的右
					if (parent->_left == cur)
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
					delete cur;
					return true;
				}
				else if (cur->_right == nullptr)
				{
					//这里我们可以处理情况3,让父亲指向cur的左
					if (parent->_left == cur)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
					delete cur;
					return true;
				}
				else
				{
					//情况4,左右都不为空,这种情况我们可以将被删节点和其右子树的最左节点(或左子树的最右节点互换位置)
					//互换之后删除节点,不会破坏搜索树的结构
					//这里我们以右子树的最左节点互换为例
					Node* minRight = cur->_right;
					Node* minParent = cur;
					while (minRight->_left)
					{
						minParent = minRight;
						minRight = minRight->_left;
					}
					std::swap(cur->_key, minRight->_key);

					//当然我们也可以选择直接覆盖被删节点
					//cur->_key = minRight->_key;

					//然后删除minRight节点即可
					//注意这里一定不能再次递归调用Erase(key),因为互换位置之后搜索树结构已经被破坏
					if (minParent->_left == minRight)
					{
						minParent->_left = minRight->_right;
					}
					else
					{
						minParent->_right = minRight->_right;
					}
					delete minRight;
					return true;
				}
			}

		}
		//没找到
		return false;
	}

	bool EraseR(const K& key)
	{
		return _EraseR(_root, key);
	}
	Node* FindR(const K& key)
	{
		return _FindR(_root, key);
	}

	void InOrder()
	{
		_InOrder(_root);
	}
private:
	void Destroy(Node* root)
	{
		if (root == nullptr)
			return;
		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
	}
	Node* CopyTree(Node* root)
	{
		if (root == nullptr)
		{
			return nullptr;
		}
		Node* copyNode = new Node(root->_key, root->_val);
		copyNode->_left = CopyTree(root->_left);
		copyNode->_right = CopyTree(root->_right);
		return copyNode;
	}
	bool _EraseR(Node*& root, const K& key)
	{
		if (root == nullptr)//不存在
			return false;
		if (root->_key > key)
		{
			_EraseR(root->_left, key);
		}
		else if (root->_key < key)
		{
			_EraseR(root->_right, key);
		}
		else
		{
			//找到了该被删除的值
			Node* del = root;
			if (root->_left == nullptr)
			{
				//我们传参的时候传的是Node*& root,因此root不仅指向被删除的值
				//root还恰好是root的父亲节点指向root的那个指针
				//我们只需改变root的指向即可
				root = root->_right;
			}
			else if (root->_right == nullptr)
			{
				root = root->_left;
			}
			else
			{
				Node* minRight = root->_right;
				while (minRight->_left)
				{
					minRight = minRight->_left;
				}
				std::swap(minRight->_key, root->_key);
				return _EraseR(root->_right, key);//这次递归调用最终一定会走到左为空的情况
			}
			delete del;
			return true;
		}

	}
	Node* _FindR(Node* root, const K& key)
	{
		if (root == nullptr)
			return nullptr;
		if (root->_key > key)
		{
			_FindR(root->_left, key);
		}
		else if (root->_key < key)
		{
			_FindR(root->_right, key);
		}
		else
			return root;
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}


private:
	Node* _root;
};

现在我们实现一个简易字典试试

#include "BinarySearchTree of KV.hpp"
#include <string>
using namespace std;
int main()
{
	BSTree<string, string> dict;
	dict.Insert("hello", "你好");
	dict.Insert("apple", "苹果");
	dict.Insert("Chinese", "中文");
	dict.Insert("insert", "插入");
	
	string str;
	while (cin >> str)
	{
		auto ret = dict.FindR(str);
		if (ret)
		{
			cout << "对应的中文" << ":" << ret->_val << endl;
		}
		else
			cout << "不存在,请重新输入" << endl;
	}

	return 0;
}

下面我们在利用kv模型实现一下统计字符串出现的次数

#include "KVBinarySearchTree.h"

int main()
{
	string arr[] = { "苹果", "香蕉", "橘子", "苹果", "香蕉", "苹果" };
	BSTree<string, int> cnt;
	for (const auto& str : arr)
	{

		auto ret = cnt.Find(str);
		if (ret == nullptr)
		{
			cnt.Insert(str, 1);
		}
		else
		{
			ret->_val++;
		}
	}
	cnt.InOrder();
	return 0;
}

image-20220518005632616

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/178535.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mongodb shell

连接指定数据库 .\mongosh.exe localhost:27017/test不连接数据库 .\mongosh.exe --nodb然后连接数据库 conn new Mongo("localhost:27017") /// mongodb://localhost:27017/?directConnectiontrue&serverSelectionTimeoutMS2000 db conn.getDB("test&q…

Git学习笔记(黑马)

目录 一、获取本地仓库 二、为常用指令配置别名 三、基础操作指令 四、分支 五、Git远程仓库&#xff08; 码云Gitee&#xff09; &#xff08;一&#xff09;配置SSH公钥 &#xff08;二&#xff09;Gitee设置账户公钥 六、操作远程仓库 &#xff08;一&#xff09;添…

【数据结构】详谈复杂度

目录 1.前言 2.什么是复杂度 3.如何计算时间复杂度 1.引例 2.二分查找 3.常见的复杂度 4.如何计算空间复杂度 5.关于递归 6.总结 1.前言 我们在做一些算法题时&#xff0c;经常会发现题目会对时间复杂度或者空间复杂度有所要求&#xff0c;如果你不知道什么是复杂度时&am…

SQL--DDL

目录 一、数据库的相关概念 二、MySQL数据库 1. 关系型数据库&#xff08;RDBMS&#xff09; 2. 数据数据库 3. MySQL客户端连接的两种方式 方式一&#xff1a;使用MySQL提供的客户端命令行工具 方式二&#xff1a;使用系统自带的命令行工具执行指令 三、SQL SQL的…

【C++】深浅拷贝

最近一些老铁一直问我深浅拷贝的问题&#xff0c;今天我们就来介绍一下深浅拷贝在说深浅拷贝构造之前&#xff0c;我们先介绍一下拷贝构造函数的应用场景&#xff1a;使用另一个同类型的对象来初始化新创建的对象。浅拷贝我们在学类和对象时了解到了类的6大默认函数&#xff0c…

给定一个数组arr,代表每个人的能力值。再给定一个非负数k,如果两个人能力差值正好为k,那么可以凑在一起比赛 一局比赛只有两个人,返回最多可以同时有多少场比赛

目录题目描述题目解析代码实现对数器题目描述 给定一个数组arr&#xff0c;代表每个人的能力值。再给定一个非负数k&#xff0c;如果两个人能力差值正好为k&#xff0c;那么可以凑在一起比赛一局比赛只有两个人&#xff0c;返回最多可以同时有多少场比赛 比如&#xff1a; [3&a…

MyBatis的入门

1、Mybatis的简介和特性 2、环境配置及其注意事项 2.1、注意事项 本文示例&#xff0c;开发环境 IDE&#xff1a;idea 2019.2 构建工具&#xff1a;maven 3.8.6 MySQL版本&#xff1a;MySQL 8 MyBatis版本&#xff1a;MyBatis 3.5.7 MySQL不同版本的注意事项&#xff1a;…

Allegro如何自动做差分对内等长操作指导

Allegro如何自动做差分对内等长操作指导 在做PCB设计的时候,需要给差分做对内等长,如果差分对比较多,Allegro支持自动做差分对内等长,如下图 具体操作如下 选择Route选择Auto-interactive Phase Tu

【UE4】将pmx导入到ue4中(obj-zip-mixamo绑骨)|模之屋模型导入UE4(较详细)

前言&#xff1a;我用fbx导入mixamo会报错&#xff0c;所以想用obj格式试试。 fbx导入↓ 效果预览&#xff1a; 目录 1.下载模型 2. 为blender安装插件 3.打开blender ​编辑 要删掉默认生成的方块&#xff01;&#xff01;&#xff01; 4.帮老婆找衣服环节&#xff01;&…

CSS定位属性详解

一、简介 1.文档流 在介绍postion之前&#xff0c;有必要先了解下文档流。 简单说就是元素按照其在 HTML 中的位置顺序决定排布的过程。HTML的布局机制就是用文档流模型的&#xff0c;即块元素&#xff08;block&#xff09;独占一行&#xff0c;内联元素&#xff08;inline…

【唐诗学习】三、盛唐诗歌的老大哥

三、盛唐诗歌的老大哥 1. 李白的伯乐——贺知章 在聊盛唐诗人之前&#xff0c;我们要先了解一位出生在初唐的大诗人&#xff1a;贺知章 盛唐诗歌虽然是中国文学的巅峰&#xff0c;但它不是蹿天猴&#xff0c;这个顶点不是“噌”一下就上的&#xff0c;需要有个老大哥把初唐诗…

Blender BMesh数据结构解密

BMesh 是一种非流形边界表示。 它旨在取代当前有限的 EditMesh 结构&#xff0c;解决 EditMesh 的许多设计限制和维护问题。 它与径向边结构相当。 推荐&#xff1a;使用 NSDT场景设计器 快速搭建 可编程 3D场景。 1、BMesh实体 在最基本的层面上&#xff0c;BMesh 将拓扑存储…

机器学习(一)——基础概念

小谈&#xff1a;一直想整理机器学习的相关笔记&#xff0c;但是一直在推脱&#xff0c;今天发现知识快忘却了&#xff08;虽然学的也不是那么深&#xff09;&#xff0c;但还是浅浅整理一下吧&#xff0c;便于以后重新学习。 最近换到新版编辑器写文章了&#xff0c;有的操作挺…

Eclipse导入python项目

导入python项目&#xff1a;https://blog.csdn.net/weixin_38917807/article/details/83046956想要导入的项目名称&#xff1a;“template-matching-ocr”路径&#xff1a;“D:\DeepLearning\cv\第九章&#xff1a;项目实战-信用卡数字识别\template-matching-ocr”方法一&…

这20个Pandas函数可以完成80%的数据科学工作

Pandas 是数据科学社区中使用最广泛的库之一&#xff0c;它是一个强大的工具&#xff0c;可以进行数据操作、清理和分析。本文将提供最常用的 Pandas 函数以及如何实际使用它们的样例。我们将涵盖从基本数据操作到高级数据分析技术的所有内容&#xff0c;到本文结束时&#xff…

【Linux】Linux调试器——gdb使用

前言 学习完 gcc/g 后我们已经能够在 Linux 下进行C/C编程了&#xff0c;但是既然涉及到了编程在怎么能没有调试呢&#xff1f;于是我们想更近一步的话就要学习gdb的使用了。由于Linux的服务器端没有图形化操作界面&#xff0c;用gdb进行调试你可能不太习惯&#xff0c;但这是必…

凌玮科技将在创业板上市:预计募资净额约8亿元,曾踩雷民生理财

近日&#xff0c;广州凌玮科技股份有限公司&#xff08;下称“凌玮科技”&#xff0c;SZ:301373&#xff09;开启申购&#xff0c;并于2023年1月19日披露了首次公开发行股票并在创业板上市网下发行初步配售结果等。本次冲刺上市&#xff0c;凌玮科技的发行价为33.73元/股&#…

【owt-server】webrtc agent

owt server 5.0 代码。m88版本。首选关注js层,作为owner对内部模块的调用 分为三大模块:rtc conn ,rtc framejs 服务以及js 服务都有的微服务框架代码 : addon中初始化全部底层组件 // Copyright (C) <2019> Intel Corporation // // SPDX-License-Identifier: Apach…

ricequant量化的基础是什么?

ricequant量化的基础包括了开仓、买入、止盈、止损方法等等方面&#xff0c;但是在股票量化中要想执行开仓、买入、止损等必须要借助一些股票交易接口来进行数据的存储和获取&#xff0c;其中就包括了通达信接口跟程序化交易接口的结合量化开展&#xff0c;同时注意l2数据接口获…

JAVA中static、final、static final的区别

1 问题当我们在使用java写类的属性时总会用到很多的类型去修饰它&#xff0c;比如字符串String&#xff0c;整数型int&#xff0c;但是我们偶尔也会遇到 static、final、static final&#xff0c;那么他们的区别是什么呢&#xff1f;2 方法finalfinal可以修饰属性、方法、类&am…