当前位置: 首页 > news >正文

【数据结构】二叉搜索树

 引入

当我们用有序数组储存数据时,查找某个数据可以用折半查找,时间复杂度为LogN;但是当我们要插入或删除数据时需要一步一步挪动数据,消耗非常大。为此引入了二叉搜索树这个数据结构。

二叉搜索树的规则

二叉搜索树又称二叉排序树,只要它不是空树,就满足一下规则:

1.若左子树不为空,则左子树上的所有节点值都小于等于根节点的值

2.若右子树不为空,则右子树上的所有节点值都大于等于根节点的值

3.它的左右子树也分别为二叉搜索树

4.二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看应用场景。

二叉搜索树的性能

最优情况下,二叉搜索树为完全二叉树,高度为log2N。

最差情况下,二叉树搜索树为单只树,高度为N。

所以综合来看,二叉搜索树的增删查改时间复杂度为:O(N)

这样的效率和数组相比优势不明显,无法满足我们的需求,后面将会学习 AVL和红黑树来解决效率问题。这两种二叉搜索树的增删查改的时间复杂度为:O(logN)。二分查找也能达到这个时间复杂度;为什么还要用二叉搜索树呢?这里就要提到能用二分查找的前提条件了:1.要保证有序2.要支持下标随机访问。而且用二分的结构大多在插入删除时需要挪动数据,消耗非常大。

二叉搜索树的插入

1.插入前树为空:
直接新增一个节点,赋值给root指针。

2.插入前树不为空:

插入值和当前节点值进行比较,比当前节点值小则与左子树值比较,为空时插入;比当前节点值大则与右子树值比较,为空时插入。

当插入值与当前节点值相等时,自由安排往左往右都可以;但是要注意逻辑连贯性,第一次往左了下次也得往左。

bool Insert(const K& 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;
}

二叉搜索树的查找

1.从根节点开始比较,查找x,比查找值大则往右走,反之往左走。

2.若最后找到空节点则未找到。

3.如果未支持插入相等值,找到第一个就返回

4.如果支持插入相等的值,那么可能存在多个要查找的x,返回中序的第一个x。

如果要查找3,找到第一个3后,继续向下查找,直至找到空节点,返回最后一个找到的值为3的节点,为中序的第一个节点。

代码实现:

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;}
}

二叉搜索树的删除

要删除二叉搜索树中的元素,首先要找到查找二叉搜索树中的元素,如果找不到,即不存在,返回false。因为一个节点有两个子节点,排列组合就有四种情况。

1.要删除的节点左右孩子都为空

这种情况最简单,直接删除就可以(把该节点的父节点指向此节点的路径指向空),不会影响整棵树的访问顺序。

2.节点左孩子为空,右孩子不为空

把父节点指向此节点的右孩子,直接删除此节点。

3.节点右孩子为空,左孩子不为空

把父节点指向左节点,直接删除该节点。

4.左右均不为空

这种情况最难处理,此节点的两个孩子要想办法安放。在讲堆和top-k问题时【数据结构】 -- 堆 (堆排序)(TOP-K问题)_高度为 k的二叉树最大的结点数为( )。-CSDN博客,我们插入一个元素,可以插入到末尾,然后再向上调整。其实这里有点类似这个的逆过程。

按照中序遍历,访问此节点前访问的元素时左子树中最大的元素,访问此节点后访问的第一个元素是右子树中最小的元素。用他俩中任意一个来替换此节点,中序遍历的结果都一样(具体用哪一个替换看自己喜好)。替换之后,此节点左右都为空,满足情况1,直接删除就好了。

bool Erase(const K& key){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//cur->_key == key{//左右至少有一个为空的情况,直接改变指向if (cur->_left == nullptr){if (parent == nullptr){_root = cur->_right;}else{if (parent->_left == cur)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;return true;}else if (cur->_right == nullptr){if (parent == nullptr){_root = cur->_left;}else{if (parent->_left == cur)parent->_left = cur->_left;elseparent->_right = cur->_left;}delete cur;return true;}else {//两个子树都有孩子的情况,用右子树最小或左子树最大来替换// ⼀定要把cur给rightMinP,否会报错。//注意右子树的第一个值就是最小值的情况Node* rightMinP = cur;Node* rightMin = cur->_right;while (rightMin->_left)//找出最左边的,就是右子树最小的{rightMinP = rightMin;rightMin = rightMin->_left;}cur->_key = rightMin->_key;//把右子树中最小值替换到cur->_keyif (rightMinP->_left == rightMin)rightMinP->_left = rightMin->_right;else//右子树的第一个值就是最小值的情况rightMinP->_right = rightMin->_right;delete rightMin;return true;}}}return false;}

二叉搜索树key和key/value使用场景

key搜索场景

一个节点只储存key这一个数据,这样实现的二叉搜索树只能实现增删查,不支持改动。

key/value搜索场景

每一个key,都有与之对应的值value,value可以是任意类型对象。树的结构中纪要储存key,也要储存value。这种二叉搜索树,还是按照key值来建立和搜索,key的值同样不能改变,但是value的值是可以改变的。就像按学号生成一个班级的二叉树,一个节点代表一个同学,就可以用这个节点的value记录该同学的成绩,每次考试成绩是可以改变的,value就更新。

代码实现:


#include<iostream>using namespace std;
#include<string>template < class K, class V>struct BSTNode{// pair<K, V> _kv;K _key;V _value;BSTNode<K, V>* _left;BSTNode<K, V>* _right;BSTNode(const K & key, const V & value):_key(key), _value(value), _left(nullptr), _right(nullptr){}};template<class K, class V>class BSTree{typedef BSTNode<K, V> Node;public:BSTree() = default;BSTree(const BSTree<K, V>& t){_root = Copy(t._root);}BSTree<K, V>& operator=(BSTree<K, V> t){swap(_root, t._root);return *this;}~BSTree(){Destroy(_root);_root = nullptr;}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;}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 nullptr;}bool Erase(const K& key){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{if (cur->_left == nullptr){if (parent == nullptr){_root = cur->_right;}else{if (parent->_left == cur)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;return true;}else if (cur->_right == nullptr){if (parent == nullptr){_root = cur->_left;}else{if (parent->_left == cur)parent->_left = cur->_left;elseparent->_right = cur->_left;}delete cur;return true;}else{Node* rightMinP = cur;Node* rightMin = cur->_right;while (rightMin->_left){rightMinP = rightMin;rightMin = rightMin->_left;}cur->_key = rightMin->_key;if (rightMinP->_left == rightMin)rightMinP->_left = rightMin->_right;elserightMinP->_right = rightMin->_right;delete rightMin;return true;}}}return false;}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);}void Destroy(Node* root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;}Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key, root->_value);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;}
private:Node* _root = nullptr;
};
//int main()
//{
//	BSTree<string, string> dict;
//	//BSTree<string, string> copy = dict;
//	dict.Insert("left", "左边");
//	dict.Insert("right", "右边");
//	dict.Insert("insert", "插⼊");
//	dict.Insert("string", "字符串");
//	string str;
//	while (cin >> str)
//	{
//		auto ret = dict.Find(str);
//		if (ret)
//		{
//			cout << "->" << ret->_value << endl;
//		}
//		else
//		{
//			cout << "无此单词,请重新输⼊" << endl;
//		}
//	}
//	return 0;
//}int main()
{string arr[] = { "蔡徐坤","陈立农","宋亚轩","范丞丞","黄明昊" };BSTree<string, int> countTree;for (const auto& str : arr){// 先查找idol在不在搜索树中// 1、不在,说明idol第⼀次出现,则插⼊<idol, 1>// 2、在,则查找到的结点中idol对应的次数++//BSTreeNode<string, int>* ret = countTree.Find(str);auto ret = countTree.Find(str);if (ret == NULL)countTree.Insert(str, 1);else{ret->_value++;}countTree.InOrder();return 0;}
}

http://www.xdnf.cn/news/27199.html

相关文章:

  • SQL注入相关知识
  • 深度解析接口:构建代码规范与实现多态的基石
  • docker转移镜像
  • db中查询关于null的sql该怎么写
  • 测试模板1
  • Linux—I/O复用---select、poll、epoll
  • 学习笔记十八——Rust 封装
  • mysql8.0.17以下驱动导致mybatis blob映射String乱码问题分析与解决
  • 实现AWS Lambda函数安全地请求企业内部API返回数据
  • 嵌入式单片机开发 - 嵌入式系统中 Flash(闪存)与 RAM(随机存储器)
  • 《JVM考古现场(二十三):归零者·重启奇点的终极奥义》
  • 【Java面试系列】Spring Boot微服务架构下的分布式事务处理与性能优化 - 2025-04-19详解 - 3-5年Java开发必备知识
  • JVM 系列:JVM 内存结构深度解析
  • 基础数学知识-线性代数
  • 蓝桥杯之递归二
  • 洛谷题目:P8624 [蓝桥杯 2015 省 AB] 垒骰子 题解 (本题简)
  • 纯FPGA实现AD9361控制的思路和实现 UART实现AXI_MASTER
  • 实现Azure Synapse Analytics安全地请求企业内部API返回数据
  • @EnableAsync+@Async源码学习笔记之二
  • @EnableAsync+@Async源码学习笔记之三
  • 系统思考:危机中的转型机遇
  • STM32单片机入门学习——第43节: [12-3] 读写备份寄存器实时时钟
  • STM32 外部中断EXTI
  • 爬虫入门与requests库的使用——python爬虫
  • XCVU13P-2FHGA2104I Xilinx Virtex UltraScale+ FPGA
  • 额外篇 非递归之美:归并排序与快速排序的创新实现
  • 解决 IntelliJ IDEA 项目启动时端口冲突问题
  • Linux网络编程——基于ET模式下的Reactor
  • 使用 Vite 快速搭建现代化 React 开发环境
  • 考公:数字推理