二叉搜索树

article/2025/8/25 1:24:07

在这里插入图片描述

目录

  • 二叉搜索树
    • 二叉搜索树概念
  • 增删查改接口
    • 插入
    • 递归插入
    • 查找
    • 递归查找
    • 删除
    • 递归删除
  • 成员函数
    • 拷贝构造
    • 拷贝赋值
    • 析构
  • 二叉搜索树的应用
  • 二叉搜索树的性能分析

二叉搜索树

二叉搜索树概念

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

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树
    在这里插入图片描述

增删查改接口

插入

//增删查改
bool insert(const T& val)
{Node* node =  new Node(val);if (!_root) {_root = node;}else {Node* cur = _root;Node* parent = nullptr;while (cur) {//插入的结点val值比根大if (cur->_key < val) {parent = cur;cur = cur->_right;}//插入的结点val值比根小else if (cur->_key > val) {parent = cur;cur = cur->_left;}else {//插入失败return  false;}}// 判断插入的值是在根的左边还是根的右边if (parent->_key > val)  parent->_left = node;else parent->_right = node;}return true;
}

递归插入

bool InsertR(const T& val)
{return _InsertR(_root, val);
}bool _InsertR(Node*& root, const T& val)
{//直到走到null位置处,也正是插入结点的适当位置if (!root){root = new Node(val);return true;}//如果比根小那么就去左子树找插入位置if (root->_key > val) {return _InsertR(root->_left, val);}//如果比根大,那么就去右子树找插入位置else if (root->_key < val) {return _InsertR(root->_right, val);}else {//二叉搜索树并不会存在重复的值,如果val既不大于也不小于那就是相等了return false;}
}

查找

//查找
Node* Find(const T& val)
{Node* cur = _root;while (cur){//比根大if (cur->_key < val){cur = cur->_right;}//比根小else if (cur->_key > val){cur = cur->_left;}else{//返回查找到的结点return cur;}}return nullptr;
}

递归查找

Node* _FindR(Node* root, const T& val) 
{//直到走到null还没有找到就返回nullif (!root) return nullptr;//比根大if (root->_key < val)return  _FindR(root->_right, val);//比根小else if (root->_key > val)return _FindR(root->_left, val);//找到了elsereturn root;
}
//递归版本Node* FindR(const T&val) 
{return _FindR(_root,val);
}

删除

搜索树的删除需要考虑三种情况

删除一个值等于key的结点,分情况分析:
在这里插入图片描述
1、要删除的是6、9、0…,这个几个结点的特征是属于叶子结点,而删除叶子结点只需要将结点的父亲指向孩子的左或者孩子的右都行。

在这里插入图片描述

2、要删除的结点是8、1…,这几个结点的特征时属于只有一个孩子的结点,删除的方式是通过父亲去接管孩子的孩子,再把孩子给删除

3、要删除的结点是5、7,这两个结点的特征是拥有两个孩子,并不好处理,也不满足1、2的特征

如果能够通过一个解决办法直接将特征3的复杂度降低为特征1的复杂度就会变得很好处理。

解决办法:替换法删除,去左右子树中找一个能够替换自己位置的结点,替换自己删除

这里可以通过搜索树的性质来决定:
1、左子树最大值得结点就是左子树最右边的结点:4
2、右子树最小值的结点就是右子树最左的结点:6

通过这两个结点去替换要删除的结点

先看删除val值为5的结点,通过替换法,将问题复杂度直接降低为特征1

在这里插入图片描述

先看删除val值为7的结点,通过替换法,将问题复杂度直接降低为特征2

在这里插入图片描述

而在这里的特征2的处理方式同样也能解决特征1的问题,所以在替换法删除结点的时候都采用特征2的处理方式

//删除
bool Erase(const T& val)
{Node* parent = nullptr;Node* cur = _root;while (cur) {//如果val值的结点小于根结点那就去左子树找if (cur->_key > val) {parent = cur;cur = cur->_left;}//如果val值的结点大于根结点那就去右子树找else if (cur->_key < val) {parent = cur;cur = cur->_right;}else  //找到的情况{//处理特征2,考虑左边为null,删除只有一个孩子的结点if (!cur->_left) {if(_root == cur){_root = cur->_right;}else{if (parent->_left == cur)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;}//考虑右边为nullelse if (!cur->_right) {if(_root == cur){_root = cur->_left;}if (parent->_left == cur)parent->_left = cur->_left;elseparent->_right = cur->_left;delete cur;}//处理特征3else {Node* minparent = cur;Node* minRight = cur->_right;while (minRight->_left)  //找右子树的最左结点,右子树的最左结点适合当替代结点{minparent = minRight;minRight = minRight->_left;}cur->_key = minRight->_key;  //替换if (minparent->_left == minRight)  //这里需要考虑两个最左的情况	minparent->_left = minRight->_right	; //左子树的最左elseminparent->_right = minRight->_right; //右子树的最左delete minRight;}return true;}}return false;
}

递归删除

bool EraseR(const T&val)
{return _EraseR(_root, val); 
}bool _EraseR(Node*& root, const T& val) //注意这里传递的是指针的别名
{if (root->_key > val) {return _EraseR(root->_left, val);}else if (root->_key < val) {return _EraseR(root->_right, val);}else  //找到了{if (!root->_left) {Node* del = root;root = root->_right; //root是指针的别名delete del; //删除值为val的结点}else if (!root->_right) {Node* del = root;root = root->_left; //root是指针的别名delete del; //删除值为val的结点}else {Node* minright = root->_right;while (minright->_left) {minright = minright->_left; //找到右子树的最左值}T min = minright->_key;_EraseR(root->_right ,min);//将问题规模缩小到在右子树中删除替换结点,使用递归删除复用前面删除一个结点的逻辑root->_key = min;//使用min将原结点的值覆盖达到替换的目的}return true;  //删除成功返回true}return false;
}

成员函数

拷贝构造

//拷贝构造
BSTree(const BSTree<T>& root)
{_root = copy(root._root);
}
//使用前序遍历的思想,将每一个结点都开辟出来,返回的过程中链接在一起
Node* copy(Node *root) 
{	if (!root){return nullptr;}else {Node* copynode = new Node(root->_key);copynode->_left = copy(root->_left);copynode->_right = copy(root->_right);return copynode;}}

拷贝赋值

//拷贝赋值 ,现代写法,借助形参对象来构造*this对象,交换他们的_root
BSTree<T>& operator=(BSTree<T> Tree)
{swap(_root, Tree._root);return *this;
}

析构

~BSTree()
{_Destroy(_root);_root = nullptr; //防止野指针
}
//释放结点申请的内存,使用后序遍历的思想,从最后一个结点开始倒着删除
void _Destroy(Node *root) 
{if (!root) return;_Destroy(root->_left);_Destroy(root->_right);delete root;
}

二叉搜索树的应用

搜索二叉树的kv模型只要在现有的基础上增加键值对就行,相关的代码博主已经上传在git上: link.

1、 K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:以单词集合中的每个单词作为key,构建一棵二叉搜索树在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

void func()
{BSTree<string, string> bs;bs.Insert("administration","管理");bs.Insert("translate", "翻译");bs.Insert("modern", "现代");bs.Insert("tape", "磁带");bs.Insert("hard disk", "硬盘");bs.Insert("computer", "电脑");string str;	while (cin >> str){BSTNode<string, string>* ret = bs.FindR(str);if (!ret)cout << "拼写错误没有该单词" << endl;elsecout << ret->_key << ":" << ret->_value << endl;}
}

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

BSTree<string, int> bs;
string str[] = {"电脑","cpu","电脑","硬盘","硬盘","显卡","显卡","显示器"};
for (const auto &ref : str)
{BSTNode<string, int>* ret = bs.FindR(ref);if (!ret) //如果词典中没有ref这个单词就插入一个单词bs.InsertR(ref, 1);elseret->_value++;	//对重复存在的单词计数
}
bs.Inorder();

二叉搜索树的性能分析

1、插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。

2、但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:O(log n)
在这里插入图片描述
最坏情况下,二叉搜索树为右单支,其平均比较次数为:O(n / 2)
在这里插入图片描述

在这里插入图片描述


http://chatgpt.dhexx.cn/article/5LBGHBxm.shtml

相关文章

【数据结构】二叉搜索树

目录 一.二叉搜索树规则 二.框架 三.实现 1.查找, 插入, 删除 迭代法实现 递归法实现 2.构造, 赋值 默认构造 拷贝构造 赋值运算符重载 3.中序遍历 四.时间复杂度及稳定性 五.key模型与key/value模型 1.key模型 2.key/value模型 一.二叉搜索树规则 二叉搜索树又…

数据结构——二叉搜索树详解

二叉搜索树 一、什么是二叉搜索树 二叉搜索树&#xff08;BST&#xff0c;Binary Search Tree&#xff09;&#xff0c;也称二叉排序树或二叉查找树。 二叉搜索树&#xff1a;一棵二叉树&#xff0c;可以为空&#xff1b;如果不为空&#xff0c;满足以下性质&#xff1a; 非空…

【数据结构】——二叉搜索树

目录 前言 二叉搜索树的概念 二叉搜索树的操作 树的节点实现 搜索树的基本结构 插入数据 查找 删除 拷贝构造函数 二叉搜索树的应用 前言 在c中的容器里map和set的学习需要二叉搜索树的铺垫&#xff0c;也为后边的的红黑树和AVL树做铺垫&#xff0c;也就是说&#xff0c;…

二叉搜索树(二叉排序树)

一.概念 二叉搜索树又称二叉排序树&#xff0c;具有以下性质&#xff1a; 若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节点的值若它的右子树不为空&#xff0c;则右子树上所有节点的值都大于根节点的值它的左右子树也分别为二叉搜索树 注意&#xff1a;二…

二叉排序树(二叉查找树、二叉搜索树)(图解+完整代码)

目录 ⚽1.什么是二叉排序树 &#x1f3d0;2.构建二叉排序树 &#x1f3c0;3.二叉排序树的查找操作 &#x1f94e;4.二叉排序树的删除 &#x1f3b1;5.完整代码 ⚽1.什么是二叉排序树 我们直接看它的性质&#xff1a; 若它的左子树不空&#xff0c;则左子树上所有结点的值…

种树:二叉树、二叉搜索树、AVL树、红黑树、哈夫曼树、B树、树与森林

虽然今天不是植树节&#xff0c;但是我今天想种树。 文章目录 树&#xff0c;什么是树&#xff1f;二叉树定义二叉树的创建二叉树的前中后序遍历前序遍历&#xff1a;中序遍历后序遍历已知前序、中序遍历结果&#xff0c;还原二叉树已知后序、中序遍历结果&#xff0c;还原二叉…

java lrucache_Java LruCache 的使用及原理

概述 LRU (Least Recently Used) 的意思就是近期最少使用算法&#xff0c;它的核心思想就是会优先淘汰那些近期最少使用的缓存对象。 在我们日常开发中&#xff0c;UI 界面进行网络图片加载是很正常的一件事情&#xff0c;但是当界面上的图片过于多的时候&#xff0c;不可能每次…

LruCache和DiskLruCache

前言 Android中的三级缓存主要就是内存缓存和硬盘缓存。 Lru(least recently used)意为最近最少使用算法&#xff0c;核心思想就是当缓存满时&#xff0c;会优先淘汰最近最少使用的缓存对象。 LruCache的使用 在Android中可以直接使用LruCache&#xff0c;算法原理是&#xff1…

Android LruCache 缓存

使用场景 1、场景一&#xff1a;图片缓存利器。 可以规定缓存大小、有效避免OOM、自动移除队尾不用的图片缓存、避免HashMap各种问题。 2、场景二&#xff1a;通信缓存 从服务端需要获取数据&#xff0c;但是当访问的数据比较大&#xff0c;比较多&#xff0c;并且是重复数…

Java——LRUCache

概念 简单来说&#xff0c;由于我们的空间是有限的&#xff0c;所以发明了这个数据结构&#xff0c;当我们的空间不够添加新的元素时&#xff0c;就会删除最近最少使用的元素。 其底层逻辑通过哈希表和链表共同实现。哈希表中存储链表的每一个元素&#xff0c;方便进行元素的…

LruCache缓存

Lru算法&#xff1a; Lru 指的是“Least Recently Used-近期最少使用算法”。 1、那么LruCache到底是什么呢&#xff1f; LruCache 是对限定数量的缓存对象持有强引用的缓存&#xff0c;每一次缓存对象被访问&#xff0c;都会被移动到队列的头部。当有对象要被添加到已经达到数…

LruCache 源码解析

1. 概述 对于 Android 开发者&#xff0c;LruCache 肯定不陌生&#xff0c;几乎所有的图片缓存框架都会用到它来实现内存缓存等&#xff0c;可见 LruCache 在 Android 开发中的重要性。LRU 是 Least Recently Used 的缩写&#xff0c;近期最少使用的意思。当我们进行缓存的时候…

LruCache

LruCache这个类是通过Glide得知的&#xff0c;不过它是自己又基于LRU算法自己写了个LruCache工具类&#xff0c;不过基本原理类似&#xff0c;都是基于LRU算法实现的 1.来源 一般来说&#xff0c;缓存策略主要包含缓存的添加、获取和删除这三类操作。如何添加和获取缓存这个比…

Android基础-LruCache原理解析

一、Android中的缓存策略 一般来说&#xff0c;缓存策略主要包含缓存的添加、获取和删除这三类操作。如何添加和获取缓存这个比较好理解&#xff0c;那么为什么还要删除缓存呢&#xff1f;这是因为不管是内存缓存还是硬盘缓存&#xff0c;它们的缓存大小都是有限的。当缓存满了…

LRUCache详解

1.概念 LRU是Least Recently Used的缩写&#xff0c;意思是最近最少使用&#xff0c;它是一种Cache替换算法。 Cache的容量有限&#xff0c;因此当Cache的容量用完后&#xff0c;而又有新的内容需要添加进来时&#xff0c; 就需要挑选并舍弃原有的部分内容&#xff0c;从而腾出…

LRUCache 详解

LRU算法详解 一、什么是 LRU 算法 就是一种缓存淘汰策略。 计算机的缓存容量有限&#xff0c;如果缓存满了就要删除一些内容&#xff0c;给新内容腾位置。但问题是&#xff0c;删除哪些内容呢&#xff1f;我们肯定希望删掉哪些没什么用的缓存&#xff0c;而把有用的数据继续…

LRU Cache

前言 哈喽&#xff0c;各位小伙伴大家好&#xff0c;本章内容为大家介绍计算机当中为了提高数据相互传输时的效率而引进的一种重要设计结构叫做LRU Cache,下面将为大家详细介绍什么是LRU Cache,以及它是如何是实现的&#xff0c;如何提升效率的。 1.什么是LRU Cache? LRU是L…

uniapp日历原生插件

<template><!-- 打卡日历页面 --><view classall><view class"bar"><!-- 上一个月 --><view class"previous" click"handleCalendar(0)"><button class"barbtn">上一月</button><…

微信小程序使用日历插件

一&#xff0c;添加插件 1&#xff0c;在你的小程序关联的微信公众平台打开 设置》第三方服务》添加插件 2&#xff0c;直接AppID&#xff08;wx92c68dae5a8bb046&#xff09;搜索到该插件并申请授权&#xff0c;授权成功即可在小程序使用 二&#xff0c;小程序使用插件 app…

日历组件

日历组件&#xff1a; <template><div class"calendar" click.stop><div class"input-wrap"><inputtype"text"v-if"dateChangeSets":placeholder"placeholder"class"input dateChangeSets middle…