【数据结构】二叉搜索树


🚀 作者简介:一名在后端领域学习,并渴望能够学有所成的追梦人。
🐌 个人主页:蜗牛牛啊
🔥 系列专栏:🛹数据结构、🛴C++
📕 学习格言:博观而约取,厚积而薄发
🌹 欢迎进来的小伙伴,如果小伙伴们在学习的过程中,发现有需要纠正的地方,烦请指正,希望能够与诸君一同成长! 🌹


文章目录

  • 二叉搜索树的概念
  • 二叉搜索树的操作及实现
    • 二叉搜索树的结构
    • 二叉搜索树的构造函数
    • 二叉搜索树的接口(非递归实现)
      • 二叉搜索树的插入
      • 二叉搜索树的中序遍历
      • 二叉搜索树的查找
      • 二叉搜索树的删除
    • 二叉搜索树的接口(递归实现)
      • 二叉搜索树的插入
      • 二叉搜索树的查找
      • 二叉搜索树的删除
    • 二叉搜索树的拷贝构造
    • 二叉搜索树的赋值
    • 二叉搜索树的析构函数
  • 二叉搜索树的应用
  • 二叉搜索树性能分析

二叉搜索树的概念

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

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

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

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

image-20230724112755992

二叉搜索树还有一个特征:按照中序走的话是一个升序的状态。所以二叉树搜索树可以叫做二叉排序树或二叉查找树。

二叉搜索树的操作及实现

二叉搜索树的结构

首先实现一个结点类,结点类当中包含三个成员变量:结点值、左指针、右指针,同时结点类当中要对成员变量进行初始化,需要实现一个构造函数,用于将结点的左右指针置空和初始化指定结点值。

结点类的代码实现:

//二叉树搜索树结点类
template<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 BSTreeNode<K> Node;
private:Node* _root = nullptr; //可以给一个构造函数,也可以直接写一个缺省值
};

二叉搜索树的构造函数

//构造函数
BSTree():root(nullptr)
{}

我们也可以这样写:

//default:默认情况下不会生成,让其强制生成构造函数
BSTree() = default;//指定强制生成默认构造

二叉搜索树的接口(非递归实现)

二叉搜索树的插入

通过插入函数在二叉搜索树中插入一个值,如果成功返回true,失败返回false。

当我们想进行插入数据时,要和树的根结点及各个子树的根结点进行比较,如果待插入结点值比当前结点小就插入到该结点的左子树;如果待插入结点值比当前节点值大就插入到该结点的右子树。

默认的搜索二叉树是不允许冗余的,有相同的值会插入失败。

根的值是怎么来的?插入的第一个值就是根。所以如果是同样的值,插入的顺序不同二叉搜索树的形状就不同。

在实现时我们要定义一个parent指针,方便新增节点和父结点链接。同时在链接的时候还要判断一下是和父亲的左边链接还是右边链接。

image-20230724163746453

代码实现:

bool Insert(const K& key)
{//确定插入的是否是第一个值,如果是第一个值插入的值就是根结点if (_root == nullptr){_root = new Node(key);//申请一个新节点return true;}//当根不是空时找对应的位置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;}//当相等时返回false,搜索二叉树中不能有相等的else{return false;}}cur = new Node(key);//将其链接到父结点上if (parent->_key > key){parent->_left = cur;}else if (parent->_key < key){parent->_right = cur;}return true;
}	

二叉搜索树的中序遍历

二叉搜索树中序遍历出来的顺序是升序的,我们可以实现一个中序遍历来验证。

void InOrder(Node* root)
{if (root == nullptr){return;}InOrder(root->_left);cout << root->_key << endl;InOrder(root->_right);
}

但是我们发现这个函数调用时候不好处理,因为要传根结点,但是并没有根结点,如果参数没有根,又没办法递归。

所以我们可以套上一层函数:调用无参的函数,当我们调用时调用无参的函数就可以实现中序遍历了。

//套用一层函数
void InOrder()
{_InOrder(_root);
}
//实现中序遍历,中序遍历打印出来是升序的
void _InOrder(Node* root)
{if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);
}

测试一下:

void Test_BSTree()
{int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };BSTree<int> t1;for (auto e : a){//插入t1.Insert(e);}//中序遍历t1.InOrder();
}
int main()
{Test_BSTree();return 0;
}

打印结果:

image-20230724164737575

二叉搜索树的查找

在二叉搜索树中也可以通和根结点和左右子树的根结点比较查找指定节点值,如果找到返回true,没找到返回false。

代码实现:

//查找接口
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;
}

二叉搜索树的删除

首先查找要删除的元素是否存在,如果不存在,则返回;否则要删除的结点可能分下面三种情况:

a.要删除的结点只有左孩子结点(包含要删除的结点无孩子结点)

b.要删除的结点只有右孩子结点

c.要删除的结点有左、右孩子结点

所以我们针对上面三种情况进行分析:

情况a:要删除的结点只有左孩子结点(包含要删除的结点无孩子结点)

此时删除要删除的结点之后,使被删除节点的父结点指向被删除节点的左孩子结点(直接删除法)

image-20230724171954648

情况b.要删除的结点只有右孩子结点

此时删除要删除的结点之后,使被删除节点的父结点指向被删除节点的右孩子结点(直接删除法)

image-20230724172146756

情况c.要删除的结点有左、右孩子结点

若待删除结点有左、右孩子结点,可以使用替换法进行删除。
可以找到待删除结点左子树中结点值最大的结点,或者是待删除结点右子树中结点值最小的结点来代替待删除结点被删除。代替待删除结点被删除的结点,在左右子树当中至少有一个为空树,那删除该结点之后可以利用上面两种情况来处理。

必须是待删除结点左子树中结点值最大的结点,或者是待删除结点右子树中结点值最小的结点代替待删除结点被删除,只有这样才能保证删除后的二叉树仍保持二叉搜索树的特性。

image-20230724174830726

删除的时候也要用parent记录父结点,保证当被删除结点还有孩子时能够被接管。

代码实现:

bool Erase(const K& key)
{Node* parent = nullptr;//父结点Node* cur = _root;//循环查找while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}//找到该节点,删除该节点else {//如果该结点只有左孩子if (cur->_right == nullptr){//当只有一边时,需要更新_rootif (cur == _root){//左孩子为空,让_root等于右孩子_root = cur->_left;}else{//判断是哪边的,让其接管if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}//删除该结点delete cur;}//该节点只有右孩子else if (cur->_left == nullptr){//当只有右边时,需要更新_rootif (cur == _root){_root = cur->_right;}else {if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_left;}}//删除结点delete cur;}//该节点有左右孩子else{//找右树的最小结点替代//这里不能等于空//Node* pminRight = nullptr;Node* pminRight = cur;Node* minRight = cur->_right;while (minRight->_left){pminRight = minRight;minRight = minRight->_left;}//找到右树的最小结点之后再把key传过去cur->_key = minRight->_key;if (pminRight->_left == minRight){pminRight->_left = minRight->_right;}else{pminRight->_right = minRight->_right;}delete minRight;}return true;}}return false;
}

image-20230724193645593

同时我们在实现时应该注意当其为如下特殊情况时,更新_root

image-20230724192934049

二叉搜索树的接口(递归实现)

一般在类里面写递归都要套上一层。

二叉搜索树的插入

要在搜索树里面进行一个插入:比根结点大的值和右子树根结点比较插入;比根结点小的值和左子树根结点比较插入,需要注意插入要和父结点链接起来。我们可以传入父结点,但是这里使用引用是最优的,root_root->left或者_root->right的别名(上一层的别名),就能够链接上。要注意C++的引用不能改变指向,循环里面不能用引用。

递归实现插入代码:

bool _InSertR(Node*& root, const K& key)
{if (root == nullptr){root = new Node(key);return true;}if (root->_key > key){_InSertR(root->_left, key);}if (root->_key < key){_InSertR(root->_right, key);}elsereturn false;
}
bool InSertR(const K& key)
{return _InSertR(_root, key);
}

二叉搜索树的查找

查找,如果在二叉树中返回true,否则返回false。

递归实现查找代码:

//套用一层函数
bool _FindR(Node* root,const K& key) 
{if (root == nullptr){return false;}if (root->_key == key){return true;}if (root->_key > key){return _FindR(root->_left, key);}else{return _FindR(root->_right, key);}
}
bool FindR(const K& key)
{return _FindR(_root,key);
}

二叉搜索树的删除

删除的思路和非递归方式一样,当要删除的结点有左右孩子的时候使用替换法,找左子树的最大值或者右子树的最小值(下面的递归实现采用的是找左子树的最大值),但是注意递归这里不能使用root->_key = maxLeft->_key,如果这样两个值相同了,不能找到要删除的结点。

递归删除函数子函数中必须使用引用接收参数,保证能够链接起来。

root是指针,直接让指针指向其指定结点就可以了,不用找到父节点。要保存一下要删除的结点Node* del = root,不然改变root指针后没办法删除要删除的结点。

bool _EarseR(Node*& root,const K& key)
{if (root == nullptr){return false;}if (root->_key > key){return _EarseR(root->_left, key);}else if (root->_key < key){return _EarseR(root->_right, key);}else {Node* del = root;//保存一下要删除的结点if (root->_left == nullptr)//root是指针,直接让指针指向其指定结点,这时就链接成功了root = root->_right;else if (root->_right == nullptr)root = root->_left;else{//去找左树的最大值Node* maxLeft = root->_left;while (maxLeft->_right){maxLeft = maxLeft->_right;}//找到进行替代,直接交换swap(root->_key, maxLeft->_key);//交换值的时候不能使用root->_key = maxLeft->_key,如果这样两个值相同了,不能找到要删除的结点。return _EarseR(root->_left,key);//转换成在子树去删除}delete del;}
}
bool EarseR(const K& key)
{return _EarseR(_root, key);
}

return _EraseR(root->_left, key);这里不能使用maxLeft,因为要使用引用,maxLeft只是一个局部变量,会出问题的,引用在递归里面又变成别名。

二叉搜索树的拷贝构造

当我们没有实现拷贝构造时候,使用的都是默认拷贝构造函数,属于浅拷贝。

当我们在没有实现析构函数时使用以下代码时并不会出问题:

void Test_BSTree() {int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };BSTree<int> t1;for (auto e : a){t1.Insert(e);}t1.InOrder();cout << endl;BSTree<int> t2(t1);t2.InOrder();
}

监视窗口如下:

image-20230724203331219

但是当我们实现析构函数之后就会报错:

image-20230724204001640

所以拷贝构造我们要写成深拷贝(推荐使用递归去实现):

主要思想就是先去创建结点,在返回的时候才开始将各个节点链接起来。

从根结点开始,不能使用插入函数,因为插入顺序不一样,形状不一样:

BSTree(const BSTree<K>& t)
{_root = Copy(t._root);
}
Node* Copy(Node* root)
{if (root == nullptr){return nullptr;}Node* newRoot = new Node(root->_key);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;
}

二叉搜索树的赋值

利用拷贝构造然后将其根结点交换即可。

BSTree& operator=(const BSTree<K> t)
{swap(_root, t._root);return *this;//支持连续赋值
}

二叉搜索树的析构函数

二叉树搜索树的析构函数我们采用一个后序的递归删除完成:

//析构函数
~BSTree()
{Destroy(_root);_root = nullptr;
}
void Destroy(Node* root)
{if (root == nullptr){return;}Destroy(root->_left);Destroy(root->_right);delete root;
}

也可以在Destroy那里使用引用传参,从而可以不在析构函数那里写_root = nullptr;,直接在Destroy函数实现,因为root就是_root的别名。

~BSTree()
{Destroy(_root);
}
void Destroy(Node*& root)
{if (root == nullptr){return;}Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;
}

二叉搜索树的应用

K模型

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

我们在本篇中讲到的构建、查找、插入就属于K模型。

KV模型

KV模型,对于每一个关键码key,都有与之对应的值value,即<key, value>的键值对。

通过一个值查找另一个值:如中英文互译字典、电话号码查询快递信息等。

我们可以通过改一下本篇中的二叉树搜索树来认识一下key-value模型,之前的二叉树搜索树是key模型。

将代码修改,主要修改模板参数,增加一个参数:

namespace kv {template<class K,class V>struct BSTreeNode {BSTreeNode<K,V>* _left;BSTreeNode<K,V>* _right;K _key;V _value;//构造函数BSTreeNode(const K& key,const V& value):_left(nullptr), _right(nullptr), _key(key),_value(value){}};template<class K, class V>class BSTree {//喜欢在类里进行类型重定义,因为受到类域的限制typedef BSTreeNode<K,V> Node;public://插入成功返回true,插入失败返回falsebool Insert(const K& key,const V& value){if (_root == nullptr){_root = new Node(key,value);return true;}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{return false;}}cur = new Node(key,value);if (parent->_key > key){parent->_left = cur;}else {parent->_right = cur;}return true;}bool Erase(const K& key){Node* parent = nullptr;//父结点Node* cur = _root;//循环查找while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}//找到该节点,删除该节点else {//如果该结点只有左孩子if (cur->_right == nullptr){//当只有一边时,需要更新_rootif (cur == _root){//左孩子为空,让_root等于右孩子_root = cur->_left;}else{//判断是哪边的,让其接管if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}//删除该结点delete cur;}//该节点只有右孩子else if (cur->_left == nullptr){//当只有右边时,需要更新_rootif (cur == _root){_root = cur->_right;}else {if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_left;}}//删除结点delete cur;}//该节点有左右孩子else{//找右树的最小结点替代//这里不能等于空//Node* pminRight = nullptr;Node* pminRight = cur;Node* minRight = cur->_right;while (minRight->_left){pminRight = minRight;minRight = minRight->_left;}//找到右树的最小结点之后再把key传过去cur->_key = minRight->_key;if (pminRight->_left == minRight){pminRight->_left = minRight->_right;}else{pminRight->_right = minRight->_right;}delete minRight;}return true;}}return false;}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;}//套用一层函数void InOrder(){_InOrder(_root);}//实现中序遍历void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << ":" << root->_value << endl;_InOrder(root->_right);}private:Node* _root = nullptr;};
}

实现之后我们通过实现一个单词翻译来测试一下:

//测试
void Test_BSTree()
{kv::BSTree<string, string> dict;dict.Insert("sort", "排序");dict.Insert("left", "左边");dict.Insert("right", "右边");dict.Insert("string", "字符串");dict.Insert("insert", "插入");dict.Insert("erase", "删除");string str;while (cin >> str){//kv::BSTreeNode<std::string,std::string>* ret = dict.Find(str);auto ret = dict.Find(str);if (ret){cout << ":" << ret->_value << endl;}else{cout << "无此单词" << endl;}}
}
int main()
{Test_BSTree();return 0;
}

测试结果:

image-20230724212828292

这种程序怎么结束呢?while (cin >> str)ctrl+c是发送终止信号,也可以使用ctrl+z+换行来结束。

我们还可以使用修改后的代码用来测试统计水果出现的次数:

void Test_BSTree()
{string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };kv::BSTree<string, int> countTree;for (auto str : arr){//kv::BSTreeNode<string, int>* ret = countTree.Find(str);auto ret = countTree.Find(str);if (ret == nullptr){countTree.Insert(str, 1);}else{ret->_value++;}}countTree.InOrder();
}
int main()
{Test_BSTree();return 0;
}

测试结果(这里的顺序是按照string数组中出现的先后顺序来排序的):

image-20230724213300066

二叉搜索树性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

image-20230724205939957

对于有N个结点的二叉搜索树,最优的情况下,二叉搜索树为完全二叉树,其平均比较次数为:logN;最差的情况下,二叉搜索树退化为单支树,其平均比较次数为:N/2。

而时间复杂度描述的是最坏情况下算法的效率,因此普通二叉搜索树各个操作的时间复杂度都是O(N)。

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

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

相关文章

亿赛通电子文档安全管理系统任意文件上传漏洞复现

0x01 产品简介 亿赛通电子文档安全管理系统&#xff08;简称&#xff1a;CDG&#xff09;是一款电子文档安全加密软件&#xff0c;该系统利用驱动层透明加密技术&#xff0c;通过对电子文档的加密保护&#xff0c;防止内部员工泄密和外部人员非法窃取企业核心重要数据资产&…

excel隔行取数求和/均值

问题描述 如图有好多组数据&#xff0c;需要求每组数据对应位置的平均值 解决方法 SUM(IF(MOD(ROW(C$2:C$81), 8) MOD(ROW(C2), 8), C$2:C$81, 0))/10然后下拉右拉扩充即可&#xff0c;其中需要根据自身需要修改一些数据 SUM(IF(MOD(ROW(起始列$起始行:结束列$结束行), 每…

MATLAB图论合集(一)基本操作基础

本帖总结一些经典的图论问题&#xff0c;通过MATLAB如何计算答案。近期在复习考研&#xff0c;以此来巩固一下相关知识——虽然考研肯定不能用MATLAB代码哈哈&#xff0c;不过在实际应用中解决问题还是很不错的&#xff0c;比C易上手得多~ 图论中的图&#xff08;Graph&#xf…

FOSSASIA Summit 2023 - 开源亚洲行

作者 Ted 致歉&#xff1a;本来这篇博客早就该发出&#xff0c;但是由于前几个月频繁差旅导致精神不佳&#xff0c;再加上后续我又参加了 Linux 基金会 7/27 在瑞士日内瓦举办的 Open Source Congress&#xff0c;以及 7/29-30 台北的 COSCUP23&#xff0c;干脆三篇连发&#x…

仪表板展示 | DataEase看中国:2023年中国电影市场分析

背景介绍 随着《消失的她》、《变形金刚&#xff1a;超能勇士崛起》、《蜘蛛侠&#xff1a;纵横宇宙》、《我爱你》等国内外影片的上映&#xff0c;2023年上半年的电影市场也接近尾声。据国家电影专资办初步统计&#xff0c;上半年全国城市院线票房达262亿元&#xff0c;已经超…

一、计算机网络体系结构

Content 1. 计算机网络的组成2. 计算机网络的功能3. 计算机网络的分类4. 计算机网络的性能指标5. 计算机网络分层结构OSI模型TCP/IP模型互联网五层模型共同点&#xff1a; 6. 计算机网络提供的服务按三种方式分类面向连接服务和无连接服务可靠服务和不可靠服务有连接服务和无连…

近 2000 台 Citrix NetScaler 服务器遭到破坏

Bleeping Computer 网站披露在某次大规模网络攻击活动中&#xff0c;一名攻击者利用被追踪为 CVE-2023-3519 的高危远程代码执行漏洞&#xff0c;入侵了近 2000 台 Citrix NetScaler 服务器。 研究人员表示在管理员安装漏洞补丁之前已经有 1200 多台服务器被设置了后门&#x…

python学习笔记——软件安装

目录 1. 安装并验证Python环境 2. 安装并设置Visual Studio Code编辑器 3. 设置Visual Studio Code编辑器 4.软件安装包 1. 安装并验证Python环境 首先&#xff0c;双击打开python安装包。 注意⚠️ &#xff1a; 安装之前需要关闭杀毒软件&#xff0c;比如360。 然后&am…

如何快速优化 CnosDB 数据库性能与延迟:使用 Jaeger 分布式追踪系统

在正式的生产环境中&#xff0c;数据库的性能和延迟对于确保系统的稳定和高效运行至关重要。特别是在与 CnosDB 数据库进行交互时&#xff0c;更深入地了解其表现变得尤为重要。这时Jaeger 分布式追踪系统发挥了巨大的作用。在本篇博客中&#xff0c;我们将深入探讨如何通过使用…

探索网络架构的关键角色:六种常用的服务器类型

在今天的数字时代&#xff0c;服务器是支撑各种在线服务和应用的基石。不同类型的服务器在网络架构中扮演着不同的角色&#xff0c;从网页传输到电子邮件交换&#xff0c;再到文件传输和内容分发。本文将深入探讨六种最常用的服务器类型&#xff0c;解释它们的功能和重要性&…

在 OpenCV 中使用深度学习进行年龄检测-附源码

文末附完整源码和模型文件下载链接 在本教程中,我们将了解使用 OpenCV 创建年龄预测器和性别分类器项目的整个过程。 年龄检测 我们的目标是创建一个程序,使用图像来预测人的性别和年龄。但预测年龄可能并不像你想象的那么简单,为什么呢?您可能会认为年龄预测是一个回归问…

【【萌新的STM32学习-8】】

萌新的STM32学习-8 STM32CubeMX 是由 ST 公司开发的图形化代码自动生成工具&#xff0c;能够快速生成初始化代码&#xff0c; 如配置 GPIO&#xff0c;时钟树&#xff0c;中间件等&#xff0c;使用户专注于业务代码的开发。现在 ST 主推 HAL 库代码&#xff0c; 经典的标准外设…

配置使用Gitee账号认证登录Grafana

三方社会化身份源 集成gitee第三方登录 第三方登录的原理 所谓第三方登录&#xff0c;实质就是 OAuth 授权。用户想要登录 A 网站&#xff0c;A 网站让用户提供第三方网站的数据&#xff0c;证明自己的身份。获取第三方网站的身份数据&#xff0c;就需要 OAuth 授权。 举例来…

时序预测 | MATLAB实现基于CNN-GRU卷积门控循环单元的时间序列预测-递归预测未来(多指标评价)

时序预测 | MATLAB实现基于CNN-GRU卷积门控循环单元的时间序列预测-递归预测未来(多指标评价) 目录 时序预测 | MATLAB实现基于CNN-GRU卷积门控循环单元的时间序列预测-递归预测未来(多指标评价)预测结果基本介绍程序设计参考资料 预测结果 基本介绍 MATLAB实现基于CNN-GRU卷积…

分布式 - 消息队列Kafka:Kafka 消费者的消费位移

文章目录 01. Kafka 分区位移02. Kafka 消费位移03. kafka 消费位移的作用04. Kafka 消费位移的提交05. kafka 消费位移的存储位置06. Kafka 消费位移与消费者提交的位移07. kafka 消费位移的提交时机08. Kafka 维护消费状态跟踪的方法 01. Kafka 分区位移 对于Kafka中的分区而…

sql server 存储过程 set ansi_nulls set quoted_identifier,out 、output

SQL-92 标准要求在对空值(NULL) 进行等于 () 或不等于 (<>) 比较时取值为 FALSE。 当 SET ANSI_NULLS 为 ON 时&#xff0c;即使 column_name 中包含空值&#xff0c;使用 WHERE column_name NULL 的 SELECT 语句仍返回零行。即使 column_name 中包含非空值&#xff0c…

5G无人露天矿山解决方案

1、5G无人露天矿山解决方案背景 ①2010.10&#xff0c;国家安监总局《金属非金属地下矿山安全避险“六大系统”安装使用和监督检查暂行规定》 ②2016.03&#xff0c;国家发改委《能源技术革命创新行动计划&#xff08;2016-2030&#xff09;》&#xff0c;2025 年重点煤矿区采…

每天一道leetcode:1192. 查找集群内的关键连接(图论困难tarjan算法)

今日份题目&#xff1a; 力扣数据中心有 n 台服务器&#xff0c;分别按从 0 到 n-1 的方式进行了编号。它们之间以 服务器到服务器 的形式相互连接组成了一个内部集群&#xff0c;连接是无向的。用 connections 表示集群网络&#xff0c;connections[i] [a, b] 表示服务器 a …

Quivr 基于GPT和开源LLMs构建本地知识库 (更新篇)

一、前言 自从大模型被炒的越来越火之后&#xff0c;似乎国内涌现出很多希望基于大模型构建本地知识库的需求&#xff0c;大概在5月底的时候&#xff0c;当时Quivr发布了第一个0.0.1版本&#xff0c;第一个版本仅仅只是使用LangChain技术结合OpenAI的GPT模型实现了一个最基本的…