【C++】-二叉搜索树的详解(递归和非递归版本以及巧用引用)

在这里插入图片描述
💖作者:小树苗渴望变成参天大树🎈
🎉作者宣言:认真写好每一篇博客💤
🎊作者gitee:gitee✨
💞作者专栏:C语言,数据结构初阶,Linux,C++ 动态规划算法🎄
如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!

文章目录

  • 前言
  • 一、什么是二叉搜索树?
  • 二、模拟实现
    • 2.1 中序遍历
    • 2.2 插入操作
    • 2.3查找操作
    • 2.4删除操作
    • 2.5拷贝构造
    • 2.6析构函数
    • 2.7赋值运算符
  • 三、二叉搜索树的性能分析
  • 四、二叉搜索树的应用
  • 五、非递归和递归的完整代码
  • 六、总结


前言

今天我要来给大家讲解一下二叉树的一些进阶部分的知识,与其这样说不如说是为了红黑树和AVL树和红黑树做铺垫,本篇的内容相对来说理解起来比前面的简单,博主也会分两个版本给大家介绍,一个递归版本的,一个非递归版本的,两个会一个介绍的,话不多说,我们开始进入正文


一、什么是二叉搜索树?

这个树不像普通的树一样每颗结点都是杂乱无章的,他符合一个特性:
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树

在这里插入图片描述
在这里插入图片描述

通过上面这个树我们分析,每棵子树的根节点的值都是这颗子树的左子树中最大的,是这颗子树的右子树中最小的,我们在来看,以8这个根节点为例,它的左子树中最大的值是这颗子树的最右边的结点,而它的右子树中最小的结点是这颗子树的最左边的结点
在这里插入图片描述
大家先提前了解这个特点因为一会在删除的时候需要使用到这个特点。


我们发现将二叉搜索树进行中序遍历,就是一个有序的,所以接下来我们验证的时候也是将二叉搜索树按照中序打印出来进行验证

二、模拟实现

我们今天实现的二叉搜索树是没有重复数字的,这个到AVL树的时候才能解决。我们就实现插入,删除,查找的主要功能。

我们现将结点进行封装,这个封装在list的实现的时候也了解过了,来一个框架:

template<class k>
struct BSNode
{BSNode<k>* _left;BSNode<k>* _right;k _data;BSNode(const k& data=k()):_left(nullptr),_right(nullptr),_data(data){}
};template<class k>
class BSTree
{typedef BSNode<k> Node;
public:BSTree(){}
private:Node* _root;//根节点
};

2.1 中序遍历

我们进行中序遍历的时候,需要传根节点进去的,如果不封装一层,我们在类外面是没有访问到私有的_root的,所以要进行封装,递归版本的都要进行封装。

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

2.2 插入操作

我们普通二叉树进行插入没啥意义,但是对于二叉搜索树进行插入有意义,它插入的位置是确定的,不是随便插入的。

非递归:

bool insert(const k& data){if (_root == nullptr)//树为空的情况{_root = new Node(data);return true;}Node* cur = _root;Node* parent = nullptr;//为了记录将要插入位置的父节点,不然申请结点没有办法进行链接操作while (cur)//为了找到插入位置{parent = cur;if (cur->_data < data){cur = cur->_right;}else if (cur->_data > data){cur = cur->_left;}else{return false;}}cur = new Node(data);if (parent->_data > data)//判断插入到父节点的哪边{parent->_left = cur;}else{parent->_right = cur;}return true;}

在这里插入图片描述

递归版本:

bool InsertR(const k& data){return _InsertR(_root, data);}
bool _InsertR(Node*& root, const k& data)//传引用的好处就是得到父节点的指针,不用关心当前插入位置结点是父节点的哪个结点{if (root == nullptr){root = new Node(data);return true;}if (root->_data < data){return _InsertR(root->_right, data);}else if (root->_data > data){return _InsertR(root->_left, data);}else//相等的时候就不用插入了{return false;}}

这个巧妙的设计就是传引用进去了,因为我要通过父节点来确定链接关系,结果通过引用直接获得父节点指向的指针,将指针里面的内容修改成要插入结点的就行了,不需要保留父节点,也不需要判断位于父节点那边了
在这里插入图片描述

2.3查找操作

查找操作就比较简单
非递归:

bool find(const k& data){if (_root == nullptr){return false;}Node* cur = _root;while (cur){if (cur->_data > data){cur = cur->_left;}else if (cur->_data < data){cur = cur->_right;}else//找到了就返回真{return true;}}return false;//到这还没有返回,说明没有找到}

递归版本:

bool FindR(const k& data){return _FindR(_root, data);}
bool _FindR(Node* root, const k& data){if (root == nullptr){return false;}if (root->_data < data){return _FindR(root->_right, data);}else if (root->_data > data){return _FindR(root->_left, data);}else{return true;}}

2.4删除操作

这个操作也是最复杂的,情况也是最多的

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点
看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:
情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除
在这里插入图片描述
我们看到这种情况其实是有三种小情况的,而要删除的结点没有左右孩子的情况,他的左右指针都是空,所以可以放在b,c情况里面

情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除
在这里插入图片描述

情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题--替换法删除

在这里插入图片描述
非递归:

bool erase(const k& data){Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_data < data){parent = cur;cur = cur->_right;}else if (cur->_data > data){parent = cur;cur = cur->_left;}else{break;}}if (cur == nullptr)//没找到要删除的元素{return false;}if (cur->_left == nullptr)//情况b{if (cur == _root){_root= cur->_right;}else{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}}else if (cur->_right == nullptr)//情况c{if (cur == _root){_root=cur->_left;}else{if (parent->_right == cur){parent->_right= cur->_left;}else{parent->_left = cur->_left;}}}else//情况d{Node* pcur = cur->_left;//找左子树中最大值parent = cur;while (pcur->_right != nullptr)//找到左子树的最大值{parent = pcur;pcur = pcur->_right;}swap(pcur->_data, cur->_data);//替换if (parent->_left == pcur)//最右边的结点还有左子树·,但是没有右子树了{parent->_left = pcur->_left;}else//大部分都是这种情况{parent->_right =pcur->_left;}cur = pcur;}delete cur;return true;}

递归版本:

bool EraseR(const k& data){return _EraseR(_root, data);}
bool _EraseR(Node*& root, const k& data){if (root == nullptr)return false;if (root->_data < data){return _EraseR(root->_right, data);}else if (root->_data > data){return _EraseR(root->_left, data);}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(leftmax->_data, root->_data);return _EraseR(root->_left, data);//递归去删除替换后的结点}delete del;return true;}}

大家好好这个引用,节画画图来理解一下

2.5拷贝构造

这个需要一个一个的拷贝:两个版本是一样的

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

2.6析构函数

~BSTree(){Destroy(_root);}void Destroy(Node*& root){`在这里插入代码片`if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;//加引用可以置空,因为你要删除的结点的父节点就要指向空,而root刚好是指针的别名,置空,就相当于将父节点的指向置空了}

2.7赋值运算符

BSTree<k>& operator=(BSTree<k> t){swap(_root, t._root);return *this;}

至此我们的两个版本的二叉搜索树就模拟实现完成了。

三、二叉搜索树的性能分析

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

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
在这里插入图片描述
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N

问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?那么我们后续章节学习的AVL树和红黑树就可以上场了。

四、二叉搜索树的应用

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

我们上面实现的写法就是k模型

  1. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:

比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文英文单词与其对应的中文<word, chinese>就构成一种键值对;

在这里插入图片描述

再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对
在这里插入图片描述


在这里插入图片描述

我们来看测试代码:

#include<iostream>
using namespace std;
template<class k, class v >
struct BSNode
{BSNode<k,v>* _left;BSNode<k,v>* _right;k _data;v _value;BSNode(const k& data = k(), const v& value = v()):_left(nullptr), _right(nullptr), _data(data),_value(value){}
};template<class k,class v>
class BSTreeRKV
{typedef BSNode<k,v> Node;
public:BSTreeRKV() {}BSTreeRKV(const BSTreeRKV<k,v>& t){_root = Copy(t._root);}BSTreeRKV<k,v>& operator=(BSTreeRKV<k,v> t){swap(_root, t._root);return *this;}void InOrder(){_InOrder(_root);cout << endl;}bool InsertRKV(const k& data,const v& value){return _InsertRKV(_root, data,value);}Node* FindRKV(const k& data){return _FindRKV(_root, data);}bool EraseRKV(const k& data){return _EraseRKV(_root, data);}~BSTreeRKV(){Destroy(_root);}
private:Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* copynode = new Node(root->_data);copynode->_left = Copy(root->_left);copynode->_right = Copy(root->_right);return copynode;}void Destroy(Node*& root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;//加引用可以置空,因为你要删除的结点的父节点就要指向空,而root刚好是指针的别名,置空,就相当于将父节点的指向置空了}bool _EraseRKV(Node*& root, const k& data){if (root == nullptr)return false;if (root->_data < data){return _EraseRKV(root->_right, data);}else if (root->_data > data){return _EraseRKV(root->_left, data);}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(leftmax->_data, root->_data);return _EraseRKV(root->_left, data);}delete del;return true;}}Node* _FindRKV(Node* root, const k& data){if (root == nullptr){return nullptr;}if (root->_data < data){return _FindRKV(root->_right, data);}else if (root->_data > data){return _FindRKV(root->_left, data);}else{return root;}}bool _InsertRKV(Node*& root, const k& data, const v& value)//传引用的好处就是得到父节点的指针,不用关心当前插入位置结点是父节点的哪个结点{if (root == nullptr){root = new Node(data,value);return true;}if (root->_data < data){return _InsertRKV(root->_right, data,value);}else if (root->_data > data){return _InsertRKV(root->_left, data,value);}else{return false;}}void _InOrder(Node* root)//中序遍历{if (root == nullptr){return;}_InOrder(root->_left);cout << root->_data << ":"<<root->_value<<endl;_InOrder(root->_right);}
private:Node* _root;//根节点
};void BSTRKV1()
{BSTreeRKV<string, string> b;b.InsertRKV("sort", "排序");b.InsertRKV("left", "左边");b.InsertRKV("right", "右边");string str;while (cin >> str){auto* ret = b.FindRKV(str);if (ret != nullptr){cout << ret->_value << endl;}else{cout << "没有此单词" << endl;}}
}void BSTRKV2()
{BSTreeRKV<string, int> b;string str[] = { "苹果","香蕉","苹果","梨子","苹果","香蕉","梨子" };for (int i = 0; i < 7; i++){auto* ret = b.FindRKV(str[i]);if (ret == nullptr){b.InsertRKV(str[i], 1);}else{ret->_value++;}}b.InOrder();
}

五、非递归和递归的完整代码

非递归:

#include<iostream>
using namespace std;
template<class k>
struct BSNode
{BSNode<k>* _left;BSNode<k>* _right;k _data;BSNode(const k& data=k()):_left(nullptr),_right(nullptr),_data(data){}
};template<class k>
class BSTree
{typedef BSNode<k> Node;
public:BSTree(){}BSTree(const BSTree<k>& t){_root = Copy(t._root);}Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* copynode = new Node(root->_data);copynode->_left = Copy(root->_left);copynode->_right = Copy(root->_right);return copynode;}~BSTree(){Destroy(_root);}void Destroy(Node*& root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;//加引用可以置空,因为你要删除的结点的父节点就要指向空,而root刚好是指针的别名,置空,就相当于将父节点的指向置空了}BSTree<k>& operator=(BSTree<k> t){swap(_root, t._root);return *this;}bool insert(const k& data){if (_root == nullptr){_root = new Node(data);return true;}Node* cur = _root;Node* parent = nullptr;while (cur)//为了找到插入位置{parent = cur;if (cur->_data < data){cur = cur->_right;}else if (cur->_data > data){cur = cur->_left;}else{return false;}}cur = new Node(data);if (parent->_data > data){parent->_left = cur;}else{parent->_right = cur;}return true;}bool erase(const k& data){Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_data < data){parent = cur;cur = cur->_right;}else if (cur->_data > data){parent = cur;cur = cur->_left;}else{break;}}if (cur == nullptr){return false;}if (cur->_left == nullptr){if (cur == _root){_root= cur->_right;}else{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = 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* pcur = cur->_left;//找左子树中最大值parent = cur;while (pcur->_right != nullptr)//找到最大值{parent = pcur;pcur = pcur->_right;}swap(pcur->_data, cur->_data);//替换if (parent->_left == pcur)//此时就左子树就一个结点{parent->_left = pcur->_left;}else//大部分都是这种情况{parent->_right =pcur->_left;}cur = pcur;}delete cur;return true;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_data << " ";_InOrder(root->_right);}void InOrder(){_InOrder(_root);cout << endl;}bool find(const k& data){if (_root == nullptr){return false;}Node* cur = _root;while (cur){if (cur->_data > data){cur = cur->_left;}else if (cur->_data < data){cur = cur->_right;}else{return true;}}return false;}private:Node* _root;//根节点
};

递归:

#include<iostream>
using namespace std;
template<class k>
struct BSNode
{BSNode<k>* _left;BSNode<k>* _right;k _data;BSNode(const k& data = k()):_left(nullptr), _right(nullptr), _data(data){}
};
template<class k>
class BSTreeR
{typedef BSNode<k> Node;
public:BSTreeR() {}BSTreeR(const BSTreeR<k>& t){_root = Copy(t._root);}BSTreeR<k>& operator=(BSTreeR<k> t){swap(_root, t._root);return *this;}void InOrder(){_InOrder(_root);cout << endl;}bool InsertR(const k& data){return _InsertR(_root, data);}bool FindR(const k& data){return _FindR(_root, data);}bool EraseR(const k& data){return _EraseR(_root, data);}~BSTreeR(){Destroy(_root);}
private:Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* copynode = new Node(root->_data);copynode->_left = Copy(root->_left);copynode->_right = Copy(root->_right);return copynode;}void Destroy(Node*& root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;//加引用可以置空,因为你要删除的结点的父节点就要指向空,而root刚好是指针的别名,置空,就相当于将父节点的指向置空了}bool _EraseR(Node*& root, const k& data){if (root == nullptr)return false;if (root->_data < data){return _EraseR(root->_right, data);}else if (root->_data > data){return _EraseR(root->_left, data);}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(leftmax->_data, root->_data);return _EraseR(root->_left, data);}delete del;return true;}}bool _FindR(Node* root, const k& data){if (root == nullptr){return false;}if (root->_data < data){return _FindR(root->_right, data);}else if (root->_data > data){return _FindR(root->_left, data);}else{return true;}}bool _InsertR(Node*& root, const k& data)//传引用的好处就是得到父节点的指针,不用关心当前插入位置结点是父节点的哪个结点{if (root == nullptr){root = new Node(data);return true;}if (root->_data < data){return _InsertR(root->_right, data);}else if (root->_data > data){return _InsertR(root->_left, data);}else{return false;}}void _InOrder(Node* root)//中序遍历{if (root == nullptr){return;}_InOrder(root->_left);cout << root->_data << " ";_InOrder(root->_right);}
private:Node* _root;//根节点
};

六、总结

我们二叉搜索树实现起来还是比较简单的,要考虑的东西并不是特别多,但是二叉搜索树有最坏的情况,所以我们后面学的AVL树和红黑树可以解决这问题,其次连哥哥版本的实现,递归版本的代码两少很多,尤其在删除的时候,巧妙的使用了引用,希望大家下去画画图,理解一下怎么使用,接下来我们将通过一篇博客用刷题的刷题的方式,带大家再来更好的学习二叉树相关的知识,我们下篇再见

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

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

相关文章

LAXCUS分布式操作系统引领科技潮流,进入百度首页

信息源自某家网络平台&#xff0c;以下原样摘抄贴出。 随着科技的飞速发展&#xff0c;分布式操作系统做为通用基础平台&#xff0c;为大数据、高性能计算、人工智能提供了强大的数据和算力支持&#xff0c;已经成为了当今计算机领域的研究热点。近日&#xff0c;一款名为LAXCU…

一起学算法(栈篇)

1.栈的概念 1.栈的定义 栈是仅限在表尾进行插入和删除的线性表&#xff0c;栈又被称为先进后出的线性表&#xff0c;简称“LIFO” 我们这次用数组作为我们栈的底层数据结构&#xff0c;代码会放到结尾供大家参考使用 2.栈顶的定义 栈是一个线性表&#xff0c;我们允许插入…

Coremail中睿天下|2023年第二季度企业邮箱安全态势观察

7月24日&#xff0c;Coremail邮件安全联合中睿天下发布《2023第二季度企业邮箱安全性研究报告》&#xff0c;对2023第二季度和2023上半年的企业邮箱的安全风险进行了分析。 一、垃圾邮件同比下降16.38% 根据Coremail邮件安全人工智能实验室&#xff08;以下简称AI实验室&#…

【云原生-制品管理】制品管理的优势

制品介绍制品管理-DevOps制品管理优势总结 制品介绍 制品管理指的是存储、版本控制和跟踪在软件开发过程中产生的二进制文件或“制品”的过程。这些制品可以包括编译后的源代码、库和文档&#xff0c;包括操作包、NPM 和 Maven 包&#xff08;或像 Docker 这样的容器镜像&…

机器学习(一)---概述

文章目录 1.人工智能、机器学习、深度学习2.机器学习的工作流程2.1 获取数据集2.2 数据基本处理2.3 特征工程2.3.1 特征提取2.3.2 特征预处理2.3.3 特征降维 2.4 机器学习2.5 模型评估 3.机器学习的算法分类3.1 监督学习3.1.1 回归问题3.1.2 分类问题 3.2 无监督学习 1.人工智能…

【高级数据结构】并查集

目录 修复公路&#xff08;带扩展域的并查集&#xff09; 食物链&#xff08;带边权的并查集&#xff09; 修复公路&#xff08;带扩展域的并查集&#xff09; 洛谷&#xff1a;修复公路https://www.luogu.com.cn/problem/P1111 题目背景 A 地区在地震过后&#xff0c;连接…

数控机床主轴品牌选择及选型,如何维护和保养?

数控机床主轴品牌选择及选型&#xff0c;如何维护和保养&#xff1f; 数控机床是一种高精度、高效率、高自动化的机床。其中&#xff0c;主轴是数控机床的核心部件&#xff0c;承担着转动工件、切削加工的任务&#xff0c;决定了加工的转速、切削力度和加工效率。因此&#xff…

深空物联网通信中视频流的智能多路TCP拥塞控制|文献阅读|文献分析和学习|拥塞控制|MPTCP|SVC

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 首先是博主的高质量博客的汇总&#xff0c;这个专栏里面的博客&#xff0c;都是博主最最用心写的一部分&#xff0c;干货满满&#xff0c;希望对大家有帮助。 高质量博客汇总https://blog.csdn.net/yu_cblog/categ…

前端构建(打包)工具发展史

大多同学的前端学习路线&#xff1a;三件套框架慢慢延伸到其他&#xff0c;在这个过程中&#xff0c;有一个词出现的频率很高&#xff1a;webpack 。 作为一个很出名的前端构建工具我们在网上随便一搜&#xff0c;就会有各种教程&#xff1a;loader plugin entry吧啦吧啦。 但…

企业可以申请DV https证书吗

DV https证书是有基础认证的数字证书&#xff0c;所以DV https证书也可以叫DV基础型https证书。DV基础型https证书是众多https证书中既支持个人&#xff0c;也支持企事业单位申请的https证书&#xff0c;所以企事业单位都可以申请DV基础型https证书&#xff0c;不论是企业门户网…

边写代码边学习之卷积神经网络CNN

1. 卷积神经网络CNN 卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;CNN&#xff09;是一种深度学习神经网络的架构&#xff0c;主要用于图像识别、图像分类和计算机视觉等任务。它是由多层神经元组成的神经网络&#xff0c;其中包含卷积层、池化层和全连接…

【数据结构】实验十:哈夫曼编码

实验十 哈夫曼编码 一、实验目的与要求 1&#xff09;掌握树、森林与二叉树的转换&#xff1b; 2&#xff09;掌握哈夫曼树和哈夫曼编码算法的实现&#xff1b; 二、 实验内容 1. 请编程实现如图所示的树转化为二叉树。 2. 编程实现一个哈夫曼编码系统&#xff0c;系统功能…

C语言预备

安装Visual studio 官方网址 https://visualstudio.microsoft.com/zh-hans/ 选择第一个社区版本&#xff08;免费&#xff09; 下载完成后打开安装包 安装完成后会自动打开程序选择c项目然后安装即可&#xff08;c兼容c&#xff09; 安装完成后启动程序注意这里需要注册也可…

scrapy框架简单实现豆瓣评分爬取案例

豆瓣网址&#xff1a;https://movie.douban.com/top250 1.创建scrapy框架 scrapy startproject 项目名(scrapy_test_one)创建好以后的目录是这样的 2.创建spider文件 在spiders目录下创建一个spider_one.py文件&#xff0c;可以随意命名&#xff0c;该文件主要是让我们进行数…

树、二叉树(C语言版)详解

&#x1f355;博客主页&#xff1a;️自信不孤单 &#x1f36c;文章专栏&#xff1a;数据结构与算法 &#x1f35a;代码仓库&#xff1a;破浪晓梦 &#x1f36d;欢迎关注&#xff1a;欢迎大家点赞收藏关注 文章目录 &#x1f34a;树的概念及结构1. 树的概念2. 树的相关概念3.树…

解决sonar的单元测试的覆盖率会为0问题

今天做项目遇到一个问题&#xff0c;明明做单元测试时覆盖率已经百分百了&#xff0c;然后传到Jenkin上&#xff0c;构建也成功了&#xff0c;但偏偏覆盖率就是为零&#xff0c;非常确定代码没有问题&#xff0c;所以唯一的问题就是出现在配置上了。 一开始的结果如下&#xf…

css 利用模糊属性 制作水滴

<style>.box {background-color: #111;height: 100vh;display: flex;justify-content: center;align-items: center;/* 对比度*/filter: contrast(20);}.drop {width: 150px;height: 159px;border-radius: 50%;background-color: #fff;position: absolute;/* 模糊 */filt…

Flowable-服务-微服务任务

目录 定义图形标记XML内容界面操作 定义 Sc 任务不是 BPMN 2.0 规范定义的官方任务&#xff0c;在 Flowable 中&#xff0c;Sc 任务是作为一种特殊的服务 任务来实现的&#xff0c;主要调用springcloud的微服务使用。 图形标记 由于 Sc 任务不是 BPMN 2.0 规范的“官方”任务…

CASAIM自动化平面度检测设备3D扫描零部件形位公差尺寸测量

平面度是表面形状的度量&#xff0c;指示沿该表面的所有点是否在同一平面中&#xff0c;当两个表面需要连接在一起形成紧密连接时&#xff0c;平面度检测至关重要。 CASAIM自动化平面度检测设备通过搭载领先的激光三维测头和智能检测软件自动获取零部件高质量测量数据&#xf…

剑指offer刷题笔记--Num61-68

1--扑克牌中的顺子&#xff08;61&#xff09; 主要思路&#xff1a; 五个数是顺子的充要条件&#xff1a;① 最大值 - 最小值 < 5&#xff08;大小王除外&#xff09;&#xff1b;② 没有出现重复的值&#xff08;大小王除外&#xff09;&#xff1b; 判断是否出现重复的值…