C++——搜索二叉树

作者:几冬雪来

时间:2023年11月7日

内容:C++的搜索二叉树讲解

目录

前言: 

什么是搜索二叉树:

搜索二叉树的增删查改: 

搜索二叉树的定义初始化:

搜索二叉树增操作:

搜索二叉树找操作: 

搜索二叉树删操作:

搜索二叉树增删查改递归版本:

搜索二叉树查操作(递归版本): 

搜索二叉树增操作(递归版本):

搜索二叉树删操作(递归版本): 

其余操作:

析构函数: 

深拷贝: 

赋值:

搜索二叉树的应用: 

代码:

结尾: 


前言: 

在上一篇博客中我们艰难的结束了C++部分多态知识的讲解,多态属于C++的一个重要的知识。而在多态结束之后,下来要讲解的就是C语言时期二叉树进阶部分的知识,那在讲解二叉树的进阶之前这里要先了解一下搜索二叉树。 

 

什么是搜索二叉树:

在学习搜索二叉树之前我们先要了解什么是搜索二叉树

在C语言学习时期我们就有学习过普通的二叉树,而搜索二叉树就是在普通二叉树中再添加一些新的特征

那么它有什么不同的特征就成为了讨论的话题。

在这个地方如果左子树所有的节点都小于根,右子树的所有结点都大于根,每一棵子树都满足这个特征,这里这棵二叉树就是搜索二叉树

为什么这样的树被称为搜索二叉树

这里的搜索二叉树就是字面意思,方便搜索,因为这里左树的节点都小于根,右树的结点都大于根,加入我们要寻找的一个值比根大,这个地方我们就可以直接访问右树不用理会左树节点的值。 

那么在这个地方如果要从搜索二叉树中找到一个值,那么它最多是不是找高度次(O(N))

其实不然,通常我们的二叉树都是左边这种形式,都是如果将左边这棵二叉树换成根节点非常大的话就会变成右边这样的树

这个时候我们就不能简单的去分析它们搜索所用的时间

那么这个地方就会涉及到另外两棵树——红黑树和AVL树

与此同时在这个地方搜索二叉树也可以被叫做二叉查找树和二叉排序树

那么它为什么又被叫为二叉排序树呢,这里就要谈一谈这棵树的中序或者中序遍历是一个什么样子的情况了

如果搜索二叉树在这里走的是中序的话,那么它最后出来的结果就是有序的,因为搜索二叉树是左小右大的形式,中序排序的结果就是——左->中->右,所以说它的结果是有序的

那么接下来我们就来书写搜索二叉树的代码了。

搜索二叉树的增删查改: 

再接下来我们就要讲解搜索二叉树的增删查改了。

在C语言时期我们并没有书写普通二叉树的增删查改,这并不意味着二叉树的增删查改没有书写的必要,知识因为在普通二叉树中,因为插入的地方没有限制哪里都可以进行插入操作,所以使得这些操作没有意义

但是类似搜索二叉树这种特殊二叉树,对比起普通二叉树它就有限制插入的地方,所以特殊二叉树的增删查改是有意义的

搜索二叉树的定义初始化:

还是一样写二叉树的时候我们通常都会通过定义一个类来存储节点的左节点右节点和它自己本身的值 。 

这个地方的第一个模板我们用其来构造左右节点

第二个模板在私有处定义一个_root来作为头节点

最后的在第二个模板之中我们要typedef第一个模板将它重新命名,然后再对我们的头节点进行初始化

搜索二叉树增操作:

定义和初始化搜索二叉树之后,接下来我们就要根据搜索二叉树来书写它的增的操作

在这个地方我们定义一个bool函数,再然后就是进行判断

这里进行判断,如果一开始我们的树里面没有值,也就是树为空。这个时候就需要我们new一个节点作为我们的头节点使用

如果这棵树不是空的话这里就要定义一个cur和parent指针,然后进入循环判断以cur为条件,如果cur为空就意味着到了空节点

然后就是判断,如果父节点存放的值小于要插入的值这个地方就放在左边,反之则放在右边,这里注意有一个特殊情况,那就是要存的值和节点的值相同,这种情况我们的数据就不能进行存储,这里就需要返回false

 

但是上边的代码只是单纯的进行比较确定位置而已,这个地方要完成最终的插入操作的话,这里还要创建一个新的节点给cur

然后进行第二次比较,因为parent这个时候因为没有进行重新的赋值初始化,因此这里只需要判断一次然后将节点插入即可

这里的最后就是书写二叉树中序遍历的代码,先走左边然后走中间最后走右边,三者都是以递归的形式

搜索二叉树找操作: 

然后就是搜索二叉树中一个比较简单的操作,也就是查找操作

那么搜索二叉树的查找操作的代码要如何书写呢? 

 

这里就是我们查找操作的代码,它的代码甚至可以沿用搜索二叉树中增操作的代码, 因为查找操作比较简单,因此也不过多的讲解

搜索二叉树删操作:

与搜索二叉树查找操作的难度不同,删除可谓是搜索二叉树中增删查改里面最为困难的一个操作,删除操作才是搜索二叉树的重点

这个地方的删除操作分为几种情况。

第一种情况就是要删去这棵树值为7的节点,这里因为值为7的节点刚刚好是我们的叶子节点,因此这个地方只需要将值为6节点的右子树置空即可

接下来就是第二种情况,要删除的节点是值为14的节点。这里值为14的节点只有一个叶子节点,这里也是十分的容易,只需要将值为10的节点右子树链接上值为13的节点即可

当然以上两种删除都是较为简单的问题,接下来就是搜索二叉树难操作的地方了。

最后一种情况就是该节点有两个子节点,这里拿头节点举例子,假如这个地方我们将头节点删去,那么这棵树要怎么进行调整

这个地方我们不能顺便的拉一个节点来作为这棵树的头节点

这里解决问题的方法就是从这一棵树中找出左树最大值的节点又或者是右树最小值的节点来作为我们的头节点,这样才能真正的解决问题

接下来我们就来书写搜索二叉树删除操作的代码。

这个地方要实现删除操作,首先还是要定义一个自身节点和父节点,然后对其进行初始化的操作。 

如果然后就是判断节点值与左右子树的大小,如果我们要查找的值小于父节点的值就像左走,反之向右走。 

然后就是找到了的情况,这里先处理两个简单的情况,那就是这个地方删除的节点刚好左右子树右一边为空

然后对要删除的值之后就需要进一步的进行判断,最后进行链接,然后将cur的值进行删除即可。但是这个地方还有一种特殊的情况需要处理

 

如这里的这张图,这棵树只有右树,如果这里要删除的节点是数据为8的节点,这会导致程序的崩溃

而且应对这个问题的方法就是将根节点进行转移,从值为8节点转移到下一个节点中去

解决这种情况的方法就是在判断是否为空之后,对根节点进行判断,如果根节点就是我们要删除的值,这里就将下一个值定为新的根节点

接下来还有一种最复杂的情况,那就是左右都不为空的情况

这个地方如果左右都不为空的话,就需要我们去找左树的最大值又或者右树的最小值和这里要删除的值进行一个替换操作

这里我们就找左树的最大值,这里一开始就走节点的左边,接下来就是进入循环,因为我们这棵树是搜索二叉树,因此条件就是节点一直走右边,直到空为止

但是这里的代码还有许多坑的地方存在。

首先就是替换的问题,因为在图中7和8节点替换之后,这里我们就要重新编译一遍,所以为了防止找不到要替换的值,这里我们需要找到父节点

然后就是它的代码。

但是如果这样子书写的话又会有特殊情况的出现。

特殊情况也会导致最后代码的崩溃。

就如上图,原本值为3的节点有左树无右树,同时它也是根节点的下一个节点。这个地方如果要3和8的节点进行调换的话

如果是这种情况的话,判断条件的leftMax->_right指向的刚刚好是空,这会导致循环进不去有间接的让父节点依旧为空

这里经过一系列的修改之后,我们的代码呈现这个样子。首先为了防止父节点为空的情况发生,一开始我们就不将它初始化为空

然后就是判断,如果父节点的下一个结点正好是左树最大节点(要删除的节点),同时这个它的右树为空。 

这里我们就要将父节点的左给leftMax的左,反之则是父节点的将leftMax的左给父节点的右。最后将leftMax给给cur,然后将cur进行delete操作即可删除这个值的节点

搜索二叉树增删查改递归版本:

上边讲解了搜索二叉树的增删查,这个地方我们并没有再写搜索二叉树修改数据的代码,因为在这之前我们要实现搜索二叉树增删查递归方式的书写

这里就要查找操作来举例

这里要注意一个点,那就是在C++当中凡是要递归走树型结构我们都需要写一个东西

这个地方要想写搜索二叉树增删查改递归版本的话,首要的就是要在私有类中书写一个子函数,因为这里我们要实行递归操作就必须有数的变化。(有变化才能递归)

因此如果要书写搜索二叉树递归部分的代码的话,我们就要将可能会发生变化的root加上

搜索二叉树查操作(递归版本): 

知道了这一个知识点之后,接下来我们就来书写搜索二叉树查操作的代码

这里的查操作的递归相较于删和增的递归版本要容易一些

这个地方我们的代码这样书写即可。如果值大于要找的值,这个地方就进入递归它的右边,如果小的话就递归它的左边,如果找到的话,这里我们就只需要返回true即可

这就是查找操作递归方法的书写。 

搜索二叉树增操作(递归版本):

在讲解完了二叉树递归版本的查找数据后,接下来就来讲解搜索二叉树的增操作代码要如何去书写

这就是我们插入时候的代码了,中间查找比较大小的操作,这段代码可以直接将查操作的代码拿过来使用,这里就不过多的讲解了

但是这个地方的代码有些许的不同,可以看见在函数对象中我们为root加入了一个引用操作

这是因为要在递归判断之后,顺便将我们要插入的节点插入进去

对象中引用操作也就是在这里进行使用,这里root如果为空的话,也就是找到了要插入的位置。这个地方就不需要像以前一样重新遍历一遍找到父节点进行插入

因为有引用的存在,这个地方如果root为空找到插入的位置,因为是引用因为我们就能直接new一个新节点出来,将值放进去,这样就能完成需求

搜索二叉树删操作(递归版本): 

在这个地方搜索二叉树的删除操作递归形式可以算我们增删查改中最为困难的一个操作了。

平时考试,问答搜索二叉树的问题,一般都是提问搜索二叉树增删查改中删除操作,那么下面我们就来看看删除操作要怎么书写?

这个地方要进行删除操作的话,首先还是免不了使用查找操作来找到我们想要删掉值的节点,这里简单来说就是进行比较。

如果大于就走左边,小于就走右边

最后也是当我们找到要删除值的节点了,这里的代码是最具有难度的,因为删除操作要兼顾三种情况,也就是左为空,右为空和左右都不为空的情况

这里如果两边有一边为空的情况我们就像上图一样书写,这个地方在一开始要为root加一个引用,这样子就可以通过引用来改变指向,从而做到删除的作用

类似有一个值为14的节点,它有一个父节点和一个右边的子节点,这里判断后将root->_right赋给root,这样就使得递归回去14的父节点会指向14的子节点

然后就是两边都不为空的情况

这里先定义一个del,在else中我们先找左树的最大节点,找到那个节点之后在将根节点和这个左树最大节点替换

然后再走一遍EraseR进行删除操作,这个地方要记得传的参数为root->_left而不是leftMax,这是因为leftMax为临时变量,如果传临时变量的话再怎么修改递归中的root,它都没有实际效果

其余操作:

在讲解完了两个版本的增删查改后,接下来我们就简单将剩下的一些操作的代码给它写上和补齐。 

析构函数: 

有一开始有构造函数,那么结束的时候就有析构函数,在这里我们就来书写搜索二叉树的析构函数,用递归的形式。

书写析构函数之前需要我们去写出它的一个子函数,因为我们析构递归没有参数

这里使用递归去析构函数而不是循环,是因为通常析构二叉树或者多叉树都是从数的叶子结点开始往回析构,都是后序遍历删除比较容易,循环析构比较麻烦

这里我们就只需要递归一下左边再递归一下右边,然后将root节点删除,最后要记得参数加上引用符合后将删除的节点进行置空操作

深拷贝: 

然后接下来因为上面的析构为递归式的析构,因此在这里会发生浅拷贝的问题,这里就需要我们写一个深拷贝的代码来解决这个问题。

这里我们就需要拷贝一棵新树出来,先将这棵树传出去,然后判断是否为空树,如果为空树那么就返回空

如果不为空树那就先创建一个头节点出来,然后就是前序遍历拷贝,递归拷贝先拷贝左在拷贝右。将这棵树拷贝出来最后返回这棵树的根节点即可。 

赋值:

最后的一个常用操作就是赋值操作,这里就直接写代码即可。

这里就是简单的交换操作

最后返回this指针即可

搜索二叉树的应用: 

在了解完了搜索二叉树后,我们就要来了解搜索二叉树会在哪些地方被应用到。

如上图,搜索二叉树通常都是用来解决K(key),V(value)问题。 

代码:

#pragma oncetemplate<class K>
struct BSTreeNode
{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode(const K& key):_left(nullptr),_right(nullptr),_key(key){}
};template<class K>
class BSTree
{typedef BSTrssNode<K> Node;
public:BSTree():_root(nullptr){}BSTree(const BSTree<K>& t){_root = Copy(t._root);}BSTree<K>& operator=(BSTree<K> t){swap(_root, t._root);return *this;}~BSTree(){Destory(_root);}bool Insert(const K& key){if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;Node* parent = cur;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;}bool 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 false;}}}}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 (cur == _root){_root = cur->_right;}else{if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}}else if(cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}}else{Node* parent = cur;Node* leftMax = cur->_left;while (leftMax->_right){parent = leftMax;leftMax = leftMax->_right;}swap(cur->_key, leftMax->_key);if (parent->_left == leftMax){parent->_left = leftMax->_left;}else{parent->_right = leftMax->_left;}cur = leftMax;}delete cur;return true;}}return 0;}void InOrder(){_InOrder(_root);}void _InOrder(Node* root){if (root == NUll){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}bool FindR(const K& key){return _FindR(_root, key);}bool InsertR(const K& key){return _InsertR(_root, key);}bool EraseR(const K& key){_EraseR(_root, key);}private:Node* Copy(Node* root){if (root == nullptr){return nullptr;}Node* copyroot = new Node(root->_key);copyroot->_left = Copy(root->_left);copyroot->_right = Copy(root->_right);return copyroot;}void Destory(Node*& root){if (root == nullptr){return;}Destory(root->_left);Destory(root->_right);delete root;root = nullptr; }bool _EraseR(Node*& root, const K& key){if(root == nullptr){return false;}if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else{Node* del = root;if (root->_left == nullptr){root = root->_right;}else if (root->_right == nullptr){root = root->_left;}else{Node* leftMax = root->_left;while (leftMax->_right){leftMax = leftMax->_right;}swap(root->_key, leftMax->_key);return EraseR(root->_left, key);}delete del;  return true;}}bool _InsertR(Node*& root, const K& key){if (root == nullptr){root = new Node(key);return true;}if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return true} }bool _FindR(Node* root,const K& key){if (root == nullptr){return false;}if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return true}}private:Node* _root;
};void TestBSTree1()
{}

结尾: 

到这里我们的搜索二叉树的基本内容就讲解完了,但是这并不意味着它的结束,在上文我们有说过。搜索二叉树还涉及到我们C++中的AVL树和红黑树,同时K,V问题也是后面我们要学习好了解的问题,最后希望之篇博客能带来帮助。

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

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

相关文章

目前安卓、鸿蒙、澎湃的关系

1、了解AOSP是什么 AOSP全名为Android Open-Source Project&#xff0c;中文为安卓开源项目&#xff0c;开源即开放源代码。Android是一个基于Linux&#xff0c;由Google主导的开源系统。 2、AOSP谁的贡献最大&#xff1f; 3、华为的鸿蒙、小米的澎湃是套壳安卓吗&#xff1…

QGC 中添加海康威视摄像头记录(Qt For Android 使用 JNI 进行JAVA 与 C++ 的通讯)

文章目录 1. 配置海康威视 SDK 下载库文件移植工程文件添加动态库&#xff08;.so&#xff09;Android xml 配置添加 java 文件 2. JavaQGCActivity.javaHkwsManager.java 3. C头文件添加&#xff1a;C 中调用 Java 静态函数&#xff08;hcnNetSDKInit&#xff09;JNI 传入规则…

JJJ:PCI / PCIE 的一些术语和概念

转发事务和非转发事务 在PCIe&#xff08;Peripheral Component Interconnect Express&#xff09;总线中&#xff0c;存在两种类型的事务&#xff1a;转发事务和非转发事务。 1、转发事务&#xff08;Forwarded Transactions&#xff09;&#xff1a;转发事务是指从一个PCIe…

11 传输层协议

1、传输层里比较重要的两个协议&#xff0c;一个是 TCP&#xff0c;一个是UDP 对于不从事底层开发的人员来讲&#xff0c;或者对于开发应用的人来讲&#xff0c;最常用的就是这两个协议。 2、TCP 和 UDP 有哪些区别&#xff1f; 1.TCP 是面向连接的&#xff0c;UDP 是面向无…

P1903 [国家集训队] 数颜色 / 维护队列

带修改的莫队 带修改的莫队就是在基础莫队的基础上增加了一维属性&#xff0c;之前只需要维护l&#xff0c;r现在还需要维护一下时间t&#xff0c;排序还是先按照左端点块儿号排序&#xff0c;然后右端点块儿号排序&#xff0c;最后按时间排序。其它的都是差不多的。 #include…

无人机航迹规划:小龙虾优化算法COA求解无人机路径规划MATLAB(可以修改起始点,地图可自动生成)

一、小龙虾优化算法COA 小龙虾优化算法&#xff08;Crayfsh optimization algorithm&#xff0c;COA&#xff09;由Jia Heming 等人于2023年提出&#xff0c;该算法模拟小龙虾的避暑、竞争和觅食行为&#xff0c;具有搜索速度快&#xff0c;搜索能力强&#xff0c;能够有效平衡…

掌握未来技术趋势:深度学习与量子计算的融合

掌握未来技术趋势&#xff1a;深度学习与量子计算的融合 摘要&#xff1a;本博客将探讨深度学习与量子计算融合的未来趋势&#xff0c;分析这两大技术领域结合带来的潜力和挑战。通过具体案例和技术细节&#xff0c;我们将一睹这两大技术在人工智能、药物研发和金融科技等领域…

Maven多环境下 active: @profileActive@报错问题解决

1.报错&#xff1a; Caused by: org.yaml.snakeyaml.scanner.ScannerException: while scanning for the next token found character that cannot start any token.(Do not use for indentation) 2.解决办法&#xff1a; 在主pom的文件下&#xff0c;重新加载&#xff1a;

软件测试-根据状态迁移图设计测试用例

测试用例状态迁移图 许多需求用状态机的方式来描述&#xff0c;状态机的测试主要关注状态转移是否正确。对于一个有限状态机&#xff0c;通过测试验证其在给定的条件内是否能够产生需要的状态变化&#xff0c;有没有不可达的状态和非法的状态&#xff0c;是否可能产生非法的状…

newstarctf2022week2

Word-For-You(2 Gen) 和week1 的界面一样不过当时我写题的时候出了个小插曲 连接 MySQL 失败: Access denied for user rootlocalhost 这句话印在了背景&#xff0c;后来再进就没了&#xff0c;我猜测是报错注入 想办法传参 可以看到一个name2,试着传参 发现有回显三个字段…

51基于matlab模拟退火算法矩形排样

基于matlab模拟退火算法矩形排样&#xff0c;基于最低水平线算法完成矩形板材下料优化&#xff0c;输出最优剩料率和最后的水平线&#xff0c;可替换自己的数据进行优化&#xff0c;程序已调通&#xff0c;可直接运行。 51matlab模拟退火算法矩形排样 (xiaohongshu.com)

Unity 实现文字过长显示省略号

为了整体效果&#xff0c;当文字过长时&#xff0c;我们就会把超出范围的文字弄成省略号。 要实现文字过长显示省略号&#xff0c;只需要使用TextMeshPro&#xff0c;并设置Overflow属性为Ellipsis即可。 如下图&#xff1a; 记。

【广州华锐互动】VR综合布线虚拟实验教学系统

随着科技的不断发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术已经逐渐渗透到各个领域&#xff0c;为人们的生活和工作带来了前所未有的便利。在建筑行业中&#xff0c;VR技术的应用也日益广泛&#xff0c;尤其是在综合布线方面。 广州华锐互动开发的VR综合布线虚拟实…

【Git】Git基础命令操作速记

【Git】Git基础命令操作速记 文章目录 【Git】Git基础命令操作速记1. 初始化1.1 设置用户名和邮箱1.2 初始化仓库 2. 基础命令2.1 add和commit2.2 reset2.3 查看日志2.4 删除/找回本地仓库文件2.5 找回暂存区文件2.6 diff命令(找不同) 3. 分支命令3.1 查看分支3.2 创建分支3.3 …

ArcGIS Pro设置谷歌影像(无水印)

1 打开软件&#xff0c;命名工程文件&#xff0c;底图&#xff08;Basemap&#xff09;选择【天地图】。 2 点击【视图&#xff08;View&#xff09;】——>【目录面板&#xff08;Catalog pane&#xff09;】&#xff0c;在【门户&#xff08;Portal&#xff09;】中搜索【…

redis: 记录一次线上redis内存占用过大问题解决过程

引言 记录一次线上redis占用过大的排查过程&#xff0c;供后续参考 问题背景 测试同事突然反馈测试环境的web系统无法登陆&#xff0c;同时发现其他子系统也存在各类使用问题 排查过程 1、因为首先反馈的是测试环境系统无法登陆&#xff0c;于是首先去查看了登陆功能的报错…

Linux 进程控制

进程地址空间的收尾 task_struct有一个结构体成员叫mm_struct&#xff0c;也就是进程地址空间。 为什么要有进程地址空间&#xff1a;进程内存地址管理&#xff0c;保护物理内存&#xff0c;进行权限审查&#xff0c;从无序变有序&#xff0c;让我们从统一的视角看待进程代码…

ModuleNotFoundError: No module named ‘torchvision.models.utils‘

如图报错&#xff1a;No module named torchvision.models.utils解决方案&#xff1a;由于当前python环境高版本的torch&#xff0c; 代码用的是低版本的语法 将 from torchvision.models.utils import load_state_dict_from_url换成 from torch.hub import load_state_dict_fr…

数据结构与算法—插入排序选择排序

目录 一、排序的概念 二、插入排序 1、直接插入排序 直接插入排序的特性总结&#xff1a; 2、希尔排序 希尔排序的特性总结&#xff1a; 三、选择排序 1、直接选择排序 时间复杂度 2、堆排序—排升序(建大堆) 向下调整函数 堆排序函数 四、交换排序 1、冒泡排…

Elasticsearch:RAG vs Fine-tunning (大语言模型微调)

如果你对 RAG 还不是很熟悉的话&#xff0c;请阅读之前的文章 “Elasticsearch&#xff1a;什么是检索增强生成 - RAG&#xff1f;”。你可以阅读文章 “Elasticsearch&#xff1a;在你的数据上训练大型语言模型 (LLM)” 来了解更多关于如何训练你的模型。在今天的文章中&#…