binary_search_tree的介绍与实现(二叉搜索树精美图示详解哦)

二叉搜搜索树

  • 引言
  • 二叉搜索树的介绍
  • 二叉搜索树的实现
    • 框架
    • 默认成员函数
      • 构造
      • 析构
      • 赋值重载
    • InsertR(插入)
    • EraseR(删除)
    • SearchR(查找)
  • 源码概览
  • 总结

引言

在C语言部分,我们已经认识了树与二叉树的结构:
戳我看树与二叉树介绍
并且了解了二叉树顺序结构带来的一些应用,即在中的应用:
戳我看堆详解哦
戳我看优先级队列详解

但是二叉树的链式结构仿佛在数据处理方面并没有什么突出的表现,在对一个没有什么特征的二叉树进行增删查改是没有什么意义的。但是如果链式的二叉树具有了一些特性,就会使它在数据处理上的效率出现质的提升。

二叉搜索树,顾名思义,在搜索方面的表现很突出:
之前我们了解到的效率很高的搜索算法就是二分查找算法,在二分查找算法下,搜索的时间复杂度可以达到 O(logN)。但是二分查找有一个很大的限制条件,即它必须要求数据有序
但是对于二叉搜索树而言,在建立二叉搜索树结构后,就可以实现查找效率为 O(logN)

二叉搜索树的介绍

二叉搜索树即在二叉树的基础上满足:
对于任何一个根结点,若左子树不为空,它的左子树的值全部小于根节点;若右子树不为空,右子树的值全部大于根节点。这也就意味着,如果我们中序遍历一个二叉搜索树,得到的一定是一个递增序列:

在这里插入图片描述
在这样的搜索二叉树中,如果我们想要查找某个值target,我们只需要从根结点开始,若当前结点的值大于target向左找,小于target向右找。就可以以很高的效率找到target
在这里插入图片描述

但是,同样是这样的10个数据,根据插入的顺序不同,就会有不同形状的二叉搜索树。在最坏的情况下,二叉搜索树的形状就与单链表类似:
在这里插入图片描述
在这种情况中,搜索的时间复杂度就变成了O(N)(在下一篇文章中介绍到的平衡二叉树,就可以解决这样的问题)

二叉搜索树的实现

在了解了二叉搜索树后,就来详细介绍其实现:

框架

在二叉树的链式结构中,父结点有指向左右孩子结点的指针,依次向下结构相同。即树形结构具有很好的递归特性,所以在此次实现中选择使用递归的方式(函数名中的R表示递归版本实现);
当然,为防止命名冲突,将实现放在我们的命名空间qqq中。

二叉搜索树BSTreeR是一个类模板,这样就可以支持二叉搜索书中存储各种数据。它的成员变量为根结点的指针:

	template<class K>class BSTreeR{protected:Node* _root; //Node 即 BSTreeNodeR<K> 的重命名};

二叉搜索树的结点类BSTreeNodeR当然也是一个类模板:
在结点类中有当前结点的数据左右孩子结点的指针。在构造函数中对数据进行初始化,并且将左右孩子结点指针置空即可(由于在对树进行操作时,需要经常访问左右结点与数据,所以这个结点类直接使用struct来定义):

	template<class K>struct BSTreeNodeR {BSTreeNodeR<K>* _left;BSTreeNodeR<K>* _right;K _key;BSTreeNodeR(const K& key): _key(key), _left(nullptr), _right(nullptr){}};

另外,要想递归的实现二叉搜索树的增删查改,那么这个接口的参数中一定是有根结点指针的。但是根结点指针是一个私有的成员变量,所以我们在类外是不能调用这些递归的函数进行增删查改的。
所以我们选择将递归调用的函数(瓤)用另一个对外的接口(壳)包起来,我们在类外调用壳接口时,在壳中调用递归函数就可以解决不能访问私有成员变量的问题了(以InsertR为例):

	template<class K>class BSTreeR{public:typedef BSTreeNodeR<K> Node;public://壳接口bool InsertR(const K& key){return _InsertR(_root, key);}protected://递归函数bool _InsertR(Node*& root, const K& key);//树根节点指针Node* _root;};

默认成员函数

构造

对于构造函数,我们实现无参构造拷贝构造
无参构造没有什么说的,在构造函数**初始化列表中将根结点指针初始化为nullptr**即可:

	BSTreeR(): _root(nullptr){}

拷贝构造就需要逐一使用原树中的结点去对新树的结点进行递归赋值
在壳接口BSTreeR()中去调用递归函数_constructor():

	BSTreeR(const BSTreeR<K>& ctree){_constructor(_root, ctree._root);}

_constructor()有两个参数,即原树的根结点指针croot新树的根节点指针root的引用

  • 在函数中:首先判断原树的指针croot是否为空,若为空说明这给条分支已经拷贝完毕,返回给上一层去另外的分支拷贝;
  • croot不为空,就使用原树结点中的数据来new一个结点;
  • 然后使root指向新创建出的结点(由于root是根结点的引用,在该栈帧中进行修改同时也修改了上层调用的数据,即在这一步中就实现了将这个新结点与其父节点进行链接);
  • 然后两次递归调用,拷贝当前结点地两个子结点:

在这里插入图片描述

	void _constructor(Node*& root, Node* croot){if (croot == nullptr)return;Node* newnode = new Node(croot->_key);root = newnode;_constructor(root->_left, croot->_left);_constructor(root->_right, croot->_right);}

析构

析构函数中,我们需要逐一释放每一个结点
使用壳接口~BSTreeR()调用递归函数_destructor()

	~BSTreeR(){_destructor(_root);}

_destructor的参数为根结点指针的引用。在析构时,如果先析构了当前结点,就不能再访问其左右结点,也就不能再继续析构下去了,所以这里使用的是后序遍历地析构方式,先析构其左右结点,再析构根结点:

  • 在函数中:首先判断root是否为空,为空说明这条分支已经走到底,向上一层返回;
  • 这里执行的是后序遍历,当不为空时,继续向下访问去析构左子树;
  • 左子树析构完后向上返回,再继续向下访问去析构右子树;
  • 右子树析构完后向上返回,才可以析构当前结点,即要析构当前结点,要么当前结点左右子树都为空,要么当前结点左右子树都已经被析构完了

在这里插入图片描述

	void _destructor(Node*& root){if (root == nullptr)return;_destructor(root->_left);_destructor(root->_right);delete root;root = nullptr;}

赋值重载

赋值重载,直接使用现代写法

  • 使用值传递,传参时生成一个临时对象ctree
  • 然后在函数中将当前对象的根结点指针与临时对象的根结点指针互换;
  • 然后直接返回*this即可(被换值的临时对象在函数栈帧销毁时会自动析构,不用我们处理):
	BSTreeR<K>& operator=(BSTreeR<K> ctree){swap(_root, ctree._root);return *this;}

InsertR(插入)

在树中插入元素时,首先需要递归找到插入位置,然后进行链接。
当然,我们使用一个壳接口InsertR,其中调用递归函数_InsertR

	bool InsertR(const K& key){return _InsertR(_root, key);}

_InsertR有两个参数,即根结点指针的引用要插入的元素。返回值为bool

在向下递归找正确位置的过程中:

  • 若新数据小于当前结点的数据,向左边递归;
  • 若新数据大于当前结点的数据,向右边递归;
  • 若新数据等于当前结点的数据,说明这个数据已经存在,不能再插入,返回false

当向下遍历到树底时,即root == nullptr时,说明当前的位置就是插入位置,将新结点与上层结点链接即可。
但是这里存在着一个问题:即当前结点并不清楚自己是上层结点的左结点还是右结点。如果要在递归遍历时同时记录父结点,在不确定时与父节点重新比较一遍,这样就太麻烦了。
根节点指针的引用传参就完美的解决了这个问题:我们只需要让这个引用指向新结点即可。如果上层传参的是左孩子指针的引用,就能直接使左孩子指针指向新结点,传参右孩子指针的引用亦然:
在这里插入图片描述

	bool _InsertR(Node*& root, const K& key){if (root == nullptr){Node* newnode = new Node(key);root = newnode;return true;}if (key < root->_key){return _InsertR(root->_left, key);}else if (key > root->_key){return _InsertR(root->_right, key);}else{cout << "The key has existed" << endl;return false;}}

EraseR(删除)

删除的实现在二叉搜索树中是最复杂的一部分,其中有各种繁琐的情况,接下来就来一一分析解决:

先来简单的介绍一下删除的思路

  • 要在二叉搜索树中删除一个数据,我们首先要找到该数据的结点,然后才能将这个目标结点删除;
  • 对于只有一个子树的目标结点而言,我们只需要将它的那个子树托管给它的父节点(若该数据小于其父结点,则其左右子树数据都小于其父结点,反之则都大于其父结点),然后直接释放它即可(这类似于单链表的删除);
  • 但是,如果这个目标结点有左右子树,那么就不能直接进行托管了,解决方案是:
    先将目标结点的数据于其左子树中的最大结点的数据进行交换(右树最小也可行,这里不进行介绍),这个左子树的最大结点一定只有一个左子树,或者没有孩子结点;
    然后按照上面的只有单孩子的思路进行删除即可:
    (这里将各个情况都进行图示)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    不可能有第四种情况了!

在写代码时,递归的方式其实要比非递归的方式要简便一些,虽然思路是一模一样的。

不必多说,在壳接口EraseR中调用递归函数_EraseR

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

_EraseR有两个参数,即根结点指针的引用要删除的数据,返回值类型为bool

_EraseR函数中,首先递归寻找要删除的数据(若目标数据小于当前结点,向子树递归,反之向右递归),如果root == nullptr 就说明找到底都没有要删除的那个数据,返回false即可:

  •   //递归找到要删的结点if (root == nullptr){cout << "The key does not exist" << endl;return false;}if (key < root->_key){return _EraseR(root->_left, key);}else if (key > root->_key){return _EraseR(root->_right, key);}else //找到{}
    

当找到要删除的元素后,判断这个目标结点的子树情况:

  • 目标结点只有左子树,将root->_right赋值给root后(前面提到过,这里的root是上层子结点指针的引用,所以这样的赋值可以直接完成链接),deleteroot即可。但是如果在赋值后delete,释放掉的就是本来的root->right,如果在赋值前delete,之后就无法进行赋值,所以这里先用临时变量deltemp来存原来的root,在链接完成后释放deltemp即可(目标结点只有右子树情况与之相反):
  •     if (root->_left == nullptr) //左为空{Node* deltemp = root;root = root->_right;delete deltemp;return true;}else if (root->_right == nullptr) //右为空{Node* deltemp = root;root = root->_left;delete deltemp;return true;}
    
  • 目标结点有左右子树,首先循环寻找左子树种的最大结点;
  • 然后将最大结点的值与目标结点交换,这样要删除的目标结点就满足前面的情况了;
  • 最后再递归调用一下_EraseR,参数就传交换后的目标结点,即之前的左树最大结点即可。这样就可以直接完成删除:
  •   	else//有两个个孩子时,找左树中的最大元素,交换后再调该函数{Node*& leftMax = root->_left;while (leftMax->_right) //循环找左最大{leftMax = leftMax->_right;}std::swap(leftMax->_key, root->_key);//交换值return _EraseR(leftMax, key);}
    

总体代码:

	bool _EraseR(Node*& root, const K& key){//递归找到要删的结点if (root == nullptr){cout << "The key does not exist" << endl;return false;}if (key < root->_key){return _EraseR(root->_left, key);}else if (key > root->_key){return _EraseR(root->_right, key);}else //找到{if (root->_left == nullptr) //左为空{Node* deltemp = root;root = root->_right;delete deltemp;return true;}else if (root->_right == nullptr) //右为空{Node* deltemp = root;root = root->_left;delete deltemp;return true;}else//有两个个孩子时,找左树中的最大元素,交换后再调该函数{Node*& leftMax = root->_left;while (leftMax->_right){leftMax = leftMax->_right;}std::swap(leftMax->_key, root->_key);//交换值return _EraseR(leftMax, key);}}}

在这里简单介绍一下非递归的实现方式复杂在哪里了
首先在找目标结点时,就需要一个辅助指针parentnode,来描述当前结点的父结点;
然后在只有一个子树或无子树的情况种,要判断目标结点与其父节点的大小,来决定要托管给父结点的哪个子树;
另外在寻找左子树的最大结点时,需要三个变量同时移动:一个变量leftmax描述左子树最大结点、一个变量maxparent描述这个最大结点的父节点、一个变量temp做临时变量;
最后对于要删除的目标结点究竟是无子节树点还是只有左子树的情况要进行判断:
(这里放一段非递归实现的代码,供大家参考)

		//非递归实现的erasebool Erase(const K& key){Node* parentnode = _root;Node* cur = _root;while (cur)//找cur{if (key > cur->_key){parentnode = cur;cur = parentnode->_right;}else if (key < cur->_key){parentnode = cur;cur = parentnode->_left;}else{break;}}if (cur == nullptr){cout << "The key does not exist" << endl;return false;}//要删除的结点只有一个孩子,将其孩子直接给其父亲//要删除的结点有左右孩子,用左子树中最大的元素替代它后,将左子树的元素删除。//若替代后,原左子树的最大结点存在左孩子,将其左孩子给其父亲if (cur->_left == nullptr && cur->_right == nullptr){if (parentnode == cur){_root = nullptr;}else if (cur->_key > parentnode->_key){parentnode->_right = nullptr;delete cur;}else{parentnode->_left = nullptr;delete cur;}}else if (cur->_left == nullptr && cur->_right != nullptr){if (parentnode == cur){_root = cur->_right;}else if (cur->_key > parentnode->_key){parentnode->_right = cur->_right;delete cur;}else{parentnode->_left = cur->_right;delete cur;}}else if (cur->_left != nullptr && cur->_right == nullptr){if (parentnode == cur){_root = cur->_left;}else if (cur->_key > parentnode->_key){parentnode->_right = cur->_left;delete cur;}else{parentnode->_left = cur->_left;delete cur;}}else //左右孩子均不为空{//找左子树中的最大元素Node* maxparent = cur;Node* leftmax = maxparent->_left;Node* temp = leftmax->_right;while (temp){maxparent = leftmax;leftmax = temp;temp = temp->_right;}std::swap(cur->_key, leftmax->_key);if (leftmax == maxparent->_left) //只有在这种情况下,才需要将最大值的左树(只可能是左)给maxparent的左{maxparent->_left = leftmax->_left;delete leftmax;}else{maxparent->_right = leftmax->_left;delete leftmax;}}return true;}

SearchR(查找)

其实在介绍完删除操作之后,查找操作就很简单了:

壳接口SearchR中调用递归函数_SearchR

	Node* SearchR(const K& key){return _SearchR(_root, key);}

函数_SearchR的参数为根结点的指针要查找的数据(这里不需要指针的引用传参了,是因为我们不需要建立与上层的链接了):

在查找过程中

  • 若目标结点小于当前结点,在左子树中递归寻找;
  • 若目标结点大于当前结点,在右子树中递归寻找;
  • 若等于则返回这个目标结点的指针。

如果root == nullptr 说明找到底也没有目标结点,返回nullptr即可:

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

这里提一下为什么在之前的插入与删除操作中没有复用这个查找函数,因为插入与删除的操作在查找到目标结点后,都需要修改与上一层调用中结点的链接,所以这里的查找函数还是不要凑热闹。

源码概览

namespace qqq
{//递归template<class K>struct BSTreeNodeR{BSTreeNodeR<K>* _left;BSTreeNodeR<K>* _right;K _key;BSTreeNodeR(const K& key): _key(key), _left(nullptr), _right(nullptr){}};template<class K>class BSTreeR{public:typedef BSTreeNodeR<K> Node;public:BSTreeR(): _root(nullptr){}BSTreeR(const BSTreeR<K>& ctree){_constructor(_root, ctree._root);}BSTreeR<K>& operator=(BSTreeR<K> ctree){swap(_root, ctree._root);return *this;}~BSTreeR(){_destructor(_root);}bool InsertR(const K& key){return _InsertR(_root, key);}bool EraseR(const K& key){return _EraseR(_root, key);}Node* SearchR(const K& key){return _SearchR(_root, key);}void InOrderR()  //测试代码{_InOrderR(_root);cout << endl;}protected:void _constructor(Node*& root, Node* croot){if (croot == nullptr)return;Node* newnode = new Node(croot->_key);root = newnode;_constructor(root->_left, croot->_left);_constructor(root->_right, croot->_right);}void _destructor(Node*& root){if (root == nullptr)return;_destructor(root->_left);_destructor(root->_right);delete root;root = nullptr;}bool _InsertR(Node*& root, const K& key){if (root == nullptr){Node* newnode = new Node(key);root = newnode;return true;}if (key < root->_key){return _InsertR(root->_left, key);}else if (key > root->_key){return _InsertR(root->_right, key);}else{cout << "The key has existed" << endl;return false;}}bool _EraseR(Node*& root, const K& key){//递归找到要删的结点if (root == nullptr){cout << "The key does not exist" << endl;return false;}if (key < root->_key){return _EraseR(root->_left, key);}else if (key > root->_key){return _EraseR(root->_right, key);}else //找到{if (root->_left == nullptr) //左为空{Node* deltemp = root;root = root->_right;delete deltemp;return true;}else if (root->_right == nullptr) //右为空{Node* deltemp = root;root = root->_left;delete deltemp;return true;}else//有两个个孩子时,找左树中的最大元素,交换后再调该函数{Node*& leftMax = root->_left;while (leftMax->_right){leftMax = leftMax->_right;}std::swap(leftMax->_key, root->_key);//交换值return _EraseR(leftMax, key);}}}Node* _SearchR(Node* root, const K& key){if (root == nullptr)return nullptr;if (key < root->_key){return _SearchR(root->_left, key);}else if(key > root->_key){return _SearchR(root->_right, key);}else{return root;}}void _InOrderR(Node* root) //测试代码{if (root == nullptr)return;_InOrderR(root->_left);cout << root->_key << " ";_InOrderR(root->_right);}//树根节点指针Node* _root;};}

总结

到此,关于二叉搜索树的介绍与实现就结束了
在本文刚开始的时候,我们也提到了一些二叉搜索树的弊端,它最坏的时间复杂度为O(N)。在接下来的文章中将会介绍一些平衡二叉树。相比于二叉搜索树又增加了一些特征,以解决这种漏洞

如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出

如果本文对你有帮助,希望一键三连哦

希望与大家共同进步哦

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

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

相关文章

【嵌入式经验积累】C语言实现傅里叶变换

文章目录 C 语言实现离散傅利叶变换&#xff08;DFT&#xff09;代码如下&#xff1a;C 语言实现快速傅利叶变换&#xff08;FFT&#xff09;&#xff0c;代码如下&#xff1a;分裂基快速傅利叶变换代码如下。实序列快速傅利叶变换&#xff08;一&#xff09;&#xff0c;代码如…

Nginx 实战指南

Nginx 是一款高性能的开源反向代理服务器,也可用作负载均衡器、Web服务器和缓存服务器。本实战指南将带你深入了解 Nginx 的安装、基础配置、高级配置、最佳实践以及性能调优。 步骤 1: 安装 Nginx Ubuntu sudo apt update sudo apt install nginx CentOS sudo yum insta…

python使用分治算法找出出现次数最多的数字

对于给定的一个长度为n的数组nums&#xff0c;需要找出起重工出现次数大于n/2向下取整的元素&#xff0c;假设给定的数组中一定存在符合给定要求的数&#xff0c;例如给定如下的例子&#xff1a; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 添加图片注…

docker使用http_proxy配置代理

钢铁知识库&#xff0c;一个学习python爬虫、数据分析的知识库。人生苦短&#xff0c;快用python。 在内网服务器中&#xff0c;docker经常需要下载拉取镜像&#xff0c;但由于没有网络要么只能手动导入镜像包&#xff0c;又或者通过http_proxy代理到其它服务器下载。 解决方法…

看书标记【R语言数据分析项目精解:理论、方法、实战 9】

看书标记——R语言 Chapter 9 文本挖掘——点评数据展示策略9.1项目背景、目标和方案9.1.1项目背景9.1.2项目目标9.1.3项目方案1.建立评论文本质量量化指标2.建立用户相似度模型3.对用户评论进行情感性分析 9.2项目技术理论简介9.2.1评论文本质量量化指标模型1.主题覆盖量2.评论…

FA-分配行重分配报错【APP-OFA-48313】

FA-重分配行报错 已存在行只能分多次转移调整 Ref1&#xff1a; APP-OFA-48313 You Cannot Create Identical Distribution Lines with Transfer (Doc ID 336894.1) APPLIES TO: Oracle Assets - Version 11.5.10.0 and later Information in this document applies to any…

Ubuntu20 服务器版磁盘扩容

Ubuntu20 服务器版磁盘扩容 Ubuntu20 服务器版磁盘不够用进可以使用fdisk命令对磁盘进行扩容 本案例中是基于vmware虚拟化环境下&#xff0c;ubuntu服务器按默认磁盘大小16G进行安装后&#xff0c;运行一段时间后发面/分区磁盘空间全部用完&#xff0c;导致服务无法正常运行。基…

计算机网络自顶向下Wireshark labs1-Intro

Wireshark labs1 实验文档&#xff1a;http://www-net.cs.umass.edu/wireshark-labs/Wireshark_Intro_v8.0.pdf 介绍 加深对网络协议的理解通常可以通过观察协议的运行和不断调试协议来大大加深&#xff0c;具体而言&#xff0c;就是观察两个协议实体之间交换的报文序列&…

厨艺学习_

选食用油 https://www.bilibili.com/video/BV1oC4y1u799 葵花籽油&#xff1a;万能 玉米油&#xff1a;有香气 菜籽油&#xff1a;又香气&#xff0c;但芥酸高 对三高不好 对老人不好 花生油&#xff1a;不要清炒蔬菜、不能油炸 大豆油&#xff1a;便宜&#xff0c;高温炒…

长虹智能电视 Q2F系列 ZLS59GiQ2机芯 强制刷机升级方法,及刷机升级数据,维修模式进入方法等

适配机芯&#xff1a;ZLS59GiQ2 适配机型&#xff1a;50Q2F、48Q2E、55Q2E、55Q2F、58Q2F、43Q2F、49Q2F、32Q2F 软件强制升级方法&#xff1a; 1、下载后解压&#xff0c;找到upgrade_ZLS59GiQ2_V1.00XXX.bin 、ZLS59GiQ2_mboot.bin复制到U盘根目录&#xff08;不要有任何…

C++ //练习 2.31 假设已有上一个练习中所做的那些声明,则下面的哪些语句是合法的?请说明顶层const和底层const在每个例子中有何体现。

C Primer&#xff08;第5版&#xff09; 练习 2.31 练习 2.31 假设已有上一个练习中所做的那些声明&#xff0c;则下面的哪些语句是合法的&#xff1f;请说明顶层const和底层const在每个例子中有何体现。 r1 v2; p1 p2; p2 p1; p1 p3; p2 p3;环境&#xff1a;Linux Ubun…

React三大属性

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 知…

只会 Python 不行,不会 Python 万万不行 。。。

当下的环境大家有目共睹&#xff0c;未来一段时间情况如何&#xff0c;想必不少人心里也清楚&#xff0c;技术人走到中年&#xff0c;难免会焦虑&#xff0c;职场上干得不爽&#xff0c;但是跳槽也不容易&#xff0c;加上不少企业裁员&#xff0c;换个满意的工作更是难上加难。…

数学建模--Radar图绘制

1.Radar图简介 最近在数学建模中碰见需要绘制Radar图(雷达图)的情况来具体分析样本的各个特征之间的得分与优劣关系&#xff0c;这样的情况比较符合雷达图的使用场景&#xff0c;一般来说&#xff0c;雷达图适用于展示多个维度的数据&#xff0c;并在一个平面上直观地呈现出不同…

长虹智能电视ZLS58Gi4X机芯刷机升级方法,附整机USB升级固件数据 U3、1U、U1、U3C、6000i系列等适用

适用机芯&#xff1a;ZLS58Gi4X 适用机型&#xff1a; 40U3、65U3、43A1U、49A1U、55A1U、43C1U、50C1U、43U1、49U1、50U1、55U1、58U1、43U1A、49U1A、50U1A、55U1A、58U1A、43U3C、49U3C、50U3C、55U3C、43Z80U、50Z80U、55Z80U、LED49Z80U、LED55Z80U、UD43D6000i、UD49D…

sql数据库的相关概念与底层介绍

本文中的数据库指的是磁盘数据库。如果有sql语言&#xff08;CRUD&#xff0c;增删改查&#xff09;的使用经验会更容易理解本文的知识点。 数据库与redis的区别 数据库&#xff1a;数据存储长期在磁盘中&#xff0c;小部分频繁需要的数据会被临时提取在内存中。 Redis&…

np.argsort排序问题(关于位次)-含GitHub上在numpy项目下提问的回复-总结可行方案

np.argsort 与获取位相关问题 位次: 数组中的数据在其排序之后的另一个数组中的位置 [1,0,2,3] 中 0的位次是1 1的位次是2 2的位次是3 3的位次是4 这里先直接给出结论&#xff0c;np.argsort()返回的索引排序与实际位次在确实在某些情况下会出现一致&#xff0c;但后来numpy的开…

用pandas实现用前一行的excel的值填充后一行

今天接到一份数据需要分析&#xff0c;数据在一个excel文件里&#xff0c;内容大概形式如下&#xff1a; 后面空的格子里的值就是默认是前面的非空的值&#xff0c;由于数据分析的需要需要对重复的数据进行去重&#xff0c;去重就需要把控的cell的值补上&#xff0c;然后根据几…

HCIP网络的类型

一.网络类型&#xff1a; 点到点 BMA&#xff1a;广播型多路访问 -- 在一个MA网络中同时存在广播&#xff08;泛洪&#xff09;机制 NBMA&#xff1a;非广播型多路访问 -- 在一个MA网络中&#xff0c;没有泛洪机制-----不怎么使用了 MA&#xff1a;多路访问 -- 在一个…

JavaEE 文件操作IO

文件操作&IO 文章目录 文件操作&IO1. 认识文件2. 文件操作2.1 File 类2.2 文件读写2.2.1 FileInputStream2.2.2 FileOutputStream2.2.3 FileReader2.2.4 FileWriter2.2.5 Scanner读取文件 3. 案例练习3.1 案例一3.2 案例二3.3 案例三 在进行文件操作之前&#xff0c;我…