C++《二叉搜索树》
在初阶数据结构中我学习了树基础的概念以及了解了顺序结构的二叉树——堆和链式结构二叉树该如何实现,那么接下来我们将进一步的学习二叉树,在此会先后学习到二叉搜索树、AVL树、红黑树;通过这些的学习将让我们更易于理解后面set、map、哈希等的使用以及对底层结构的了解。在此先本篇中我们将了解二次搜索树的概念以及实现二叉搜索树插入、删除等的操作,在了解了这些之后相信在下一篇的set和map的学习你将轻松许多,接下来就开始本篇的学习吧!!!
1.二叉搜索树的概念
二叉搜索树又称二叉排序树,它或者是⼀棵空树,或者是具有以下性质的二叉树:
• 若它的左子树不为空,则左子树上所有结点的值都小于等于根结点的值
• 若它的右子树不为空,则右子树上所有结点的值都大于等于根结点的值
• 它的左右子树也分别为二叉搜索树
• 二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景定义,后续我们学习map/set/multimap/multiset系列容器底层就是二叉搜索树,其中map/set不支持插入相等值,multimap/multiset支持插入相等值
例如以下左边图示的就是不支持插入相等值得二叉搜索树,右边就是支持插入相等值得二叉搜索树
2. 二叉搜索树的性能分析
最好得情况下,在此在二叉搜索树当中最为完全二叉树(或者接近完全二叉树),其高度为:
例如以下示例:
最差情况下,在此⼆叉搜索树退化为单支树(或者类似单支),其高度为:
例如以下示例:
那么通过以上对二叉树最好和最坏情况的分析就可以得出综合而言二叉搜索树增删查改时间复杂度为:
那么通过以上的分析可以看出二叉搜索树这样的效率显然无法满足我们需求的,我们后续需要继续讲解二叉搜索树的变形,平衡二叉搜索树AVL树和红黑树,才能适用于我们在内存中存储和搜索数据。
在此你可能会想到在二叉搜索树中查找数据不就类似与二分查找吗,那么直接使用之前学习的二分查找不就可以了吗,二分查找的效率相比二叉搜索树还更好,那为什么还要学习了解二叉搜索树呢?
在此二分查找也可以实现 O(logN) 级别的查找效率,但是二分查找有两大缺陷:
1. 需要存储在支持下标随机访问的结构中,并且有序。
在使用到二分查找示我们使用的是数据对应的下标来实现查找,这就使得当被查找的一系列数据不是存储在数组里时就需要先将数据都存储在数组当中并且还要将数据排序成升序,在这个过程中就会有时间和空间上的损耗了
2. 插入和删除数据效率很低,因为存储在下标随机访问的结构中,插入和删除数据⼀般需要挪动数据。
在二分查找当中以上提到的缺点其实还不是最致命的,最要命的是当一组数据已经存储在数组当中时并且已经排序好时,如果之后我们要在这组数据当中再插入新的元素或者是要将原数据中的一个元素删除,那么通过之前顺序表的学习我们就知道每插入或者是删除一个元素时间复杂度都为O(N),若多次进行操作这就使得时间复杂度非常高了
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
通过以上的分析这里也就体现出了平衡二叉搜索树的价值,在插入元素或者是删除元素都相比使用二分查找要有优势。
3. 二叉搜索树的插入
3.1插入分析
在此在二叉搜索树当中插入新的节点就要按照以下步骤进行分析:
1. 树为空,则直接新增结点,赋值给root指针
2. 树不空,按二叉搜索树性质,插入值比当前结点大往右走,插入值比当前结点小往左走,找到空位置,插入新结点。
3. 如果支持插入相等的值,插入值跟当前结点相等的值可以往右走,也可以往左走,找到空位置,插入新结点。(要注意的是要保持逻辑⼀致性,插入相等的值不要⼀会往右走,⼀会往左走)
例如以下示例:
假设我们要将以下的数组元素依次插入到二叉搜索树当中
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
所以元素插入完之后二叉搜索树的形式就如下所示:
这时如果我们要再插入值为16的元素在二叉搜索树中过程图就如下所示:
再插入值为3的元素在二叉搜索树中过程图就如下所示:
3.2 插入代码实现
在实现二叉搜索树的插入代码之前我们先要来实现二叉搜索树大体的结构代码
在此我们先创建一个BSTree.h的头文件在该文件当中来实现二叉搜索树的结构以及各个功能,再创建一个test.cpp的文件用于测试我们实现的二叉搜索树的各个功能是否能满足要求
实现了文件的创建之后接下来就来实现二叉搜索树的大体结构。在此由于二叉搜索树是由各个节点构成的,那么和之前实现链式结构的二叉树一样先要实现表示节点的结构体
#include<iostream> using namespace std;template<class K> struct BSTreeNode {K _key;BSTreeNode<K>* left;BSTreeNode<K>* right;BSTreeNode(const K& key):_key(key),left(nullptr),right(nullptr){} };
在此就创建一个结构体BSTreeNode来表示二叉树内节点,在节点当中有三个变量分别是_key表示节点内的数据、_left存储该节点左孩子节点的指针、_right存储该节点右孩子节点的指针。并且将该结构体实现成模板这样就可以支持节点内的元素是任意类型的数据,还有在结构体当中还实现了构造函数。
实现了节点的结构体之后接下来就可以来实现表示二叉搜索树的类,在此我们将类命名为BSTree,实现的是模板类,实现的大体结构如下所示:
template<class K>
class BSTree
{typedef BSTreeNode<K> Node;public://使用编译器生成的默认构造函数BSTree() = default;//实现各种功能的成员函数……private://头节点Node* _root = nullptr;};
完成了以上操作接下来就可以来实现插入函数的代码了
注:以下实现的插入函数是数据不支持冗余的情况也就是二叉树当中不支持插入相等的值
bool Insert(const K& key)
{//当根节点为空时if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;//节点的父节点Node* parentcur = nullptr;while (cur){//当key小于当前节点的值if (cur->_key < key){parentcur = cur;cur = cur->right;}当key大于当前节点的值else if (cur->_key > key){parentcur = cur;cur = cur->left;}//当key等于当前节点的值else{return false;}}cur = new Node(key);//当新节点内的值大于父节点内的值时if (parentcur->_key < key){parentcur->right = cur;}//当新节点内的值小于父节点内的值时else{parentcur->left = cur;}return true;}
4. 二叉搜索树的查找
4.1查找分析
在二叉搜索树中查找节点就要按照以下步骤进行分析:
1. 从根开始比较,查找x,x比根的值大则往右边走查找,x比根值小则往左边走查找。
2. 最多查找高度次,走到到空,还没找到,这个值不存在。
3. 如果不支持插入相等的值,找到x即可返回
4. 如果支持持插入相等的值,意味着有多个x存在,⼀般要求查找中序的第⼀个x。
例如以下示例:
当要查找3时,要找到1的右孩子的那个3值返回
4.2 查找代码实现
在进行了二叉搜索树的节点查找的分析之后接下来我们就来实现查找代码
注:以下实现的查找函数是数据不支持冗余的情况也就是二叉树当中不支持插入相等的值
Node* Find(const K& key)
{//当根结点为空时if (_root == nullptr){return nullptr;}Node* cur = _root;Node* parentcur = nullptr;while (cur){//当key大于当前节点的值if (cur->_key < key){parentcur = cur;cur = cur->right;}//当key小于当前节点的值else if (cur->_key > key){parentcur = cur;cur = cur->left;}//当key等于当前节点的值else{return cur;}}//当前二叉树中找不到值为key的节点return nullptr;
}
5. 二叉搜索树的删除
5.1 删除分析
在二叉搜索树当中节点的删除相比插入和查找就复杂一些了,在此会出现以下的多种情况接下来就来一一分析
在此要删除的节点会有以下的四种情况:
1.要删除结点N左右孩子均为空
当要删除节点的节点左右孩子节点都为空时就需要把N结点的父亲对应孩子指针指向空,直接删除N结点
例如以下示例:
在以上二叉搜索树当中要删除值为1的节点就需要将节点1删除之后再将其父节点的左孩子节点指向空
2. 要删除的结点N左孩子位空,右孩子结点不为空
当要删除节点的节点左孩子节点为空时就需要把N结点的父亲对应孩子指针指向N的右孩子,之后直接删除N结点
例如以下示例:
在以上二叉搜索树当中要删除值为10的节点就需要将其父节点的左孩子变为原节点的右孩子节点之后再将节点10删除
3.要删除的结点N右孩子位空,左孩子结点不为空
当要删除节点的节点右孩子节点为空时就需要把N结点的父亲对应孩子指针指向N的左孩子,之后直接删除N结点
例如以下示例:
在以上二叉搜索树当中要删除值为14的节点就需要将其父节点的左孩子变为原节点的左孩子节点之后再将节点14删除
4. 要删除的结点N左右孩子结点均不为空
当要删除的节点左右孩子节点都不为空时,这是就不能像以上的情况一样简单的改变要删除节点的父节点指针,这时由于无法直接删除N结点,这是因为N的两个孩⼦无处安放,只能用替换法删除。在此根据二叉搜索树的性质就需要找N左子树的值最大结点R(最右结点)或者N右子树的值最小结点R(最左结点)替代N,因为这两个结点中任意⼀个,放到N的位置,都满足⼆叉搜索树的规则。替代N的意思就是N和R的两个结点的值交换,转而变成删除R结点,R结点符合情况2或情况3,可以直接删除。
例如以下示例:
在以上二叉搜索树当中要删除节点值为8的节点和值为3的节点,由于这两个节点都是左右孩子节点都不为空的节点,因此要删除值为8的节点就需要找到其左子树最右的节点或者是右子树最左的节点(在此我们是找右子树最左的节点)
之后交换要删除的节点和找出的节点的值
最后删除交换之后值为8的节点即可
注:要删除值为3的节点和以上的方式也类型在此就不再细致的讲解
5.2 删除代码实现
bool Erase(const K& key)
{//当根节点为空时if (_root == nullptr){return false;}//当前节点Node* cur = _root;//当前节点的父节点Node* parentcur = _root;while (cur){//当key的值大于当前节点的值if (cur->_key < key){parentcur = cur;cur = cur->right;}//当key的值大于当前节点的值else if (cur->_key > key){parentcur = cur;cur = cur->left;}//当key的值等于当前节点的值else{//当要删除的节点左孩子节点为空if (cur->left==nullptr){//当要删除的节点为根结点时直接改变_root指针的指向,之后再将原根结点释放if (cur == _root){Node* Right = cur->right;delete cur;_root = Right;}else{//当要删除的节点cur是其父节点的左节点时if (cur == parentcur->left){parentcur->left = cur->right;delete cur;}//当要删除的节点cur是其父节点的右节点时else if (cur == parentcur->right){parentcur->right = cur->right;delete cur;}}return true;}//当要删除的节点右孩子节点为空else if (cur->right==nullptr){//当要删除的节点为根结点时直接改变_root指针的指向,之后再将原根结点释放if (cur == _root){Node* Left = cur->left;delete cur;_root = Left;}else{//当要删除的节点cur是其父节点的左节点时if (cur == parentcur->left){parentcur->left = cur->left;delete cur;}//当要删除的节点cur是其父节点的右节点时else if (cur == parentcur->right){parentcur->right = cur->left;delete cur;}}return true;}//当要删除的节点左右孩子节点都为空else{Node* minrightParent = cur;Node* minright = cur->right;//找出当前节点右子树中的最左节点while (minright->left){minrightParent = minright;minright = minright->left;}cur->_key = minright->_key;//当最左节点为其父节点的左节点时if (minrightParent->left == minright){minrightParent->left = minright->right;}//当最左节点为其父节点的右节点时else {minrightParent->right = minright->right;}delete minright;return true; }}}//当找不到要删除的节点就返回falsereturn false;}
6. 二叉搜索树遍历
由于二叉搜索树的性质一棵二叉搜索树中序遍历输出的结果就是递增的,那么接下来我们就试着来实现中序遍历的代码
void Inorder()
{_Inorder(_root);cout << endl;
}void _Inorder(Node* root)
{if (root == nullptr){return;}_Inorder(root->left);cout << root->_key <<" ";_Inorder(root->right);
}
在此我们实现的中序遍历代码如上所示,那么这时你可能就会有疑问了,为什么要实现两个中序遍历的函数,不是直接使用一个函数就可以满足要求了吗?
在此要考虑到的是在BsTree类以外用户是无法得到二叉搜索树的根节点的,但是在调用中序遍历的函数根据之前我们使用递归的方式是需要一开始就需要将二叉树的根结点作为中序遍历函数的参数的。因此为了解决该问题就再在BSTree类内实现一个函数来调用中序遍历的成员函数,由于是在类的内部在此是可以访问私有的成员变量的,在类外部用户要使用中序遍历时就只需要调用无参的成员函数Inorder就可以得到二叉搜索树中序遍历的结果了。
7. 二叉搜索树完整代码
#include<iostream>
using namespace std;template<class K>
struct BSTreeNode
{K _key;BSTreeNode<K>* left;BSTreeNode<K>* right;BSTreeNode(const K& key):_key(key), left(nullptr), right(nullptr){}
};template<class K>
class BSTree
{typedef BSTreeNode<K> Node;public://使用编译器生成的默认构造函数BSTree() = default;bool Insert(const K& key){//当根节点为空时if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;//节点的父节点Node* parentcur = nullptr;while (cur){//当key小于当前节点的值if (cur->_key < key){parentcur = cur;cur = cur->right;}当key大于当前节点的值else if (cur->_key > key){parentcur = cur;cur = cur->left;}//当key等于当前节点的值else{return false;}}cur = new Node(key);//当新节点内的值大于父节点内的值时if (parentcur->_key < key){parentcur->right = cur;}//当新节点内的值小于父节点内的值时else{parentcur->left = cur;}return true;}Node* Find(const K& key){//当根结点为空时if (_root == nullptr){return nullptr;}Node* cur = _root;Node* parentcur = nullptr;while (cur){//当key大于当前节点的值if (cur->_key < key){parentcur = cur;cur = cur->right;}//当key小于当前节点的值else if (cur->_key > key){parentcur = cur;cur = cur->left;}//当key等于当前节点的值else{return cur;}}//当前二叉树中找不到值为key的节点return nullptr;}bool Erase(const K& key){//当根节点为空时if (_root == nullptr){return false;}//当前节点Node* cur = _root;//当前节点的父节点Node* parentcur = _root;while (cur){//当key的值大于当前节点的值if (cur->_key < key){parentcur = cur;cur = cur->right;}//当key的值大于当前节点的值else if (cur->_key > key){parentcur = cur;cur = cur->left;}//当key的值等于当前节点的值else{//当要删除的节点左孩子节点为空if (cur->left == nullptr){//当要删除的节点为根结点时直接改变_root指针的指向,之后再将原根结点释放if (cur == _root){Node* Right = cur->right;delete cur;_root = Right;}else{//当要删除的节点cur是其父节点的左节点时if (cur == parentcur->left){parentcur->left = cur->right;delete cur;}//当要删除的节点cur是其父节点的右节点时else if (cur == parentcur->right){parentcur->right = cur->right;delete cur;}}return true;}//当要删除的节点右孩子节点为空else if (cur->right == nullptr){//当要删除的节点为根结点时直接改变_root指针的指向,之后再将原根结点释放if (cur == _root){Node* Left = cur->left;delete cur;_root = Left;}else{//当要删除的节点cur是其父节点的左节点时if (cur == parentcur->left){parentcur->left = cur->left;delete cur;}//当要删除的节点cur是其父节点的右节点时else if (cur == parentcur->right){parentcur->right = cur->left;delete cur;}}return true;}//当要删除的节点左右孩子节点都为空else{Node* minrightParent = cur;Node* minright = cur->right;//找出当前节点右子树中的最左节点while (minright->left){minrightParent = minright;minright = minright->left;}cur->_key = minright->_key;//当最左节点为其父节点的左节点时if (minrightParent->left == minright){minrightParent->left = minright->right;}//当最左节点为其父节点的右节点时else{minrightParent->right = minright->right;}delete minright;return true;}}}//当找不到要删除的节点就返回falsereturn false;}void Inorder(){_Inorder(_root);cout << endl;}private://头节点Node* _root = nullptr;void _Inorder(Node* root){if (root == nullptr){return;}_Inorder(root->left);cout << root->_key << " ";_Inorder(root->right);}};
8. 二叉搜索树key和key/value使用场景
8.1 key搜索场景
key搜索场景的形式如下所示:
只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值,搜索场景只需要判断key在不在。key的搜索场景实现的二叉树搜索树支持增删查,但是不支持修改,修改key破坏搜索树结构了。
在以上我们了解实现的二叉搜索树其实是适用于key的场景,那么接下来就来看看那么场景是属于key的场景
场景1:小区无人值守车库,小区车库买了车位的业主车才能进小区,那么物业会把买了车位的业主的车牌号录入后台系统,⻋辆进入时扫描⻋牌在不在系统中,在则抬杆,不在则提示非本小区⻋辆,无法进入。
场景2:检查⼀篇英文文章单词拼写是否正确,将词库中所有单词放入二叉搜索树,读取文章中的单词,查找是否在二叉搜索树中,不在则波浪线标红提示。
8.2 key/value搜索场景
key/value搜索场景的形式如下所示:
每⼀个关键码key,都有与之对应的值value,value可以任意类型对象。树的结构中(结点)除了需要存储key还要存储对应的value,增/删/查还是以key为关键字走二叉搜索树的规则进行比较,可以快速查找到key对应的value。key/value的搜索场景实现的二叉树搜索树支持修改,但是不支持修改key,修改key破坏搜索树结构了,可以修改value。
接下来就来将以上我们实现的key二叉搜索树修改为key/value⼆叉搜索树代码,实现代码如下所示:
#include<iostream>
using namespace std;template<class K,class V>
struct BSTreeNode
{K _key;V _value;BSTreeNode<K ,V>* left;BSTreeNode<K ,V>* right;BSTreeNode(const K& key, const V& value):_key(key);_value(value), left(nullptr), right(nullptr){}
};template<class K,class V>
class BSTree
{typedef BSTreeNode<K,V> Node;public://使用编译器生成的默认构造函数BSTree() = default;bool Insert(const K& key, const V& value){//当根节点为空时if (_root == nullptr){_root = new Node(key,value);return true;}Node* cur = _root;//节点的父节点Node* parentcur = nullptr;while (cur){//当key小于当前节点的值if (cur->_key < key){parentcur = cur;cur = cur->right;}//当key大于当前节点的值else if (cur->_key > key){parentcur = cur;cur = cur->left;}//当key等于当前节点的值else{return false;}}cur = new Node(key, value);//当新节点内的值大于父节点内的值时if (parentcur->_key < key){parentcur->right = cur;}//当新节点内的值小于父节点内的值时else{parentcur->left = cur;}return true;}Node* Find(const K& key){//当根结点为空时if (_root == nullptr){return nullptr;}Node* cur = _root;Node* parentcur = nullptr;while (cur){//当key大于当前节点的值if (cur->_key < key){parentcur = cur;cur = cur->right;}//当key小于当前节点的值else if (cur->_key > key){parentcur = cur;cur = cur->left;}//当key等于当前节点的值else{return cur;}}//当前二叉树中找不到值为key的节点return nullptr;}bool Erase(const K& key){//当根节点为空时if (_root == nullptr){return false;}//当前节点Node* cur = _root;//当前节点的父节点Node* parentcur = _root;while (cur){//当key的值大于当前节点的值if (cur->_key < key){parentcur = cur;cur = cur->right;}//当key的值大于当前节点的值else if (cur->_key > key){parentcur = cur;cur = cur->left;}//当key的值等于当前节点的值else{//当要删除的节点左孩子节点为空if (cur->left == nullptr){//当要删除的节点为根结点时直接改变_root指针的指向,之后再将原根结点释放if (cur == _root){Node* Right = cur->right;delete cur;_root = Right;}else{//当要删除的节点cur是其父节点的左节点时if (cur == parentcur->left){parentcur->left = cur->right;delete cur;}//当要删除的节点cur是其父节点的右节点时else if (cur == parentcur->right){parentcur->right = cur->right;delete cur;}}return true;}//当要删除的节点右孩子节点为空else if (cur->right == nullptr){//当要删除的节点为根结点时直接改变_root指针的指向,之后再将原根结点释放if (cur == _root){Node* Left = cur->left;delete cur;_root = Left;}else{//当要删除的节点cur是其父节点的左节点时if (cur == parentcur->left){parentcur->left = cur->left;delete cur;}//当要删除的节点cur是其父节点的右节点时else if (cur == parentcur->right){parentcur->right = cur->left;delete cur;}}return true;}//当要删除的节点左右孩子节点都为空else{Node* minrightParent = cur;Node* minright = cur->right;//找出当前节点右子树中的最左节点while (minright->left){minrightParent = minright;minright = minright->left;}cur->_key = minright->_key;//当最左节点为其父节点的左节点时if (minrightParent->left == minright){minrightParent->left = minright->right;}//当最左节点为其父节点的右节点时else{minrightParent->right = minright->right;}delete minright;return true;}}}//当找不到要删除的节点就返回falsereturn false;}void Inorder(){_Inorder(_root);cout << endl;}private://头节点Node* _root = nullptr;void _Inorder(Node* root){if (root == nullptr){return;}_Inorder(root->left);cout << root->_key << ":" << root->_val << " ";_Inorder(root->right);}};
在以上我们了解实现的二叉搜索树其实是适用于key的场景,那么接下来就来看看那么场景是属于key的场景
场景1:商场无人值守车库,入⼝进场时扫描⻋牌,记录车牌和入场时间,出口离场时,扫描牌,查找入场时间,用当前时间-⼊场时间计算出停⻋时长,计算出停车费用,缴费后抬杆,车辆离场。
场景1:简单中英互译字典,树的结构中(结点)存储key(文)和vlaue(中文),搜索时输⼊英文,则同时查找到了英文对应的中文。
场景3:统计⼀篇文章中单词出现的次数,读取⼀个单词,查找单词是否存在,不存在这个说明第⼀次出现,(单词,1),单词存在,则++单词对应的次数。
以下的两个简单的示例就是使用二叉搜索树的key/val来解决
#include"BSTree.h"int main()
{//示例1
//将以下英文单词和对应的中文翻译绑定到一起,当用户输入输入正确的单词就输出其中文意思,否则就输出单词拼写错误BSTree<string, string> dict;dict.Insert("insert", "插入");dict.Insert("erase", "删除");dict.Insert("left", "左边");dict.Insert("string", "字符串");string str;while (cin >> str){auto ret = dict.Find(str);if (ret){cout << str << ":" << ret->_val << endl;}else{cout << "单词拼写错误" << endl;}}//示例2
//统计字符串数组当中各个水果的出现次数string strs[] = { "苹果", "西瓜", "苹果", "樱桃", "苹果", "樱桃", "苹果", "樱桃", "苹果" };// 统计水果出现的次BSTree<string, int> countTree;for (auto str : strs){auto ret = countTree.Find(str);if (ret == NULL){countTree.Insert(str, 1);}else{ret->_val++;}}countTree.Inorder();return 0;
}
以上就是本篇的区别内容了,希望能得到你的点赞和收藏 ❤️
相关文章:
C++《二叉搜索树》
在初阶数据结构中我学习了树基础的概念以及了解了顺序结构的二叉树——堆和链式结构二叉树该如何实现,那么接下来我们将进一步的学习二叉树,在此会先后学习到二叉搜索树、AVL树、红黑树;通过这些的学习将让我们更易于理解后面set、map、哈希等…...
⭐️ GitHub Star 数量前十的工作流项目
文章开始前,我们先做个小调查:在日常工作中,你会使用自动化工作流工具吗?🙋 事实上,工作流工具已经变成了提升效率的关键。其实在此之前我们已经写过一篇博客,跟大家分享五个好用的工作流工具。…...
uni-app中的样式尺寸单位,px,rpx,vh,vw
uni-app 支持less、sass、scss、stylus等预处理器。 尺寸单位 uni-app 支持的通用 css 单位包括 px、rpx px 即屏幕像素rpx 即响应式 px,一种根据屏幕宽度自适应的动态单位。以 750 宽的屏幕为基准,**750rpx 恰好为屏幕宽度。**屏幕变宽,r…...
跳表(Skip List)
跳表(Skip List) 跳表是一种用于快速查找、插入和删除的概率型数据结构,通常用于替代平衡二叉搜索树(如 AVL 树或红黑树)。跳表通过在有序链表的基础上增加多层索引,使得查找操作的平均时间复杂度降低&…...
103.【C语言】数据结构之建堆的时间复杂度分析
1.向下调整的时间复杂度 推导 设树高为h 发现如下规律 按最坏的情况考虑(即调整次数最多) 第1层,有个节点,最多向上调整h-1次 第2层,有个节点,最多向上调整h-2次 第3层,有个节点,最多向上调整h-3次 第4层,有个节点,最多向上调整h-4次 ... 第h-1层,有个节点,最多向上调整1次 第…...
数字信号处理实验报告四:IIR数字滤波器设计及软件实现
1.实验目的 (1)熟悉用双线性变换法设计IIR数字滤波器的原理与方法; (2)学会调用MATLAB信号处理工具箱中滤波器设计函数(或滤波器设计分析工具fdatool)设计各种IIR数字滤波器,学会根据滤波需求确定滤波器指标参数。 (3)掌握IIR数字滤波器的MATLAB实现方法。 (3)…...
Flutter:encrypt插件 AES加密处理
1、pubspec.yaml导入插件 cupertino_icons: ^1.0.8 # 密码加密 encrypt: 5.0.3encrypt封装 import package:encrypt/encrypt.dart; /// 加密类 class EncryptUtil {static final EncryptUtil _instance EncryptUtil._internal();factory EncryptUtil() > _instance;Encrypt…...
软银集团孙正义再度加码OpenAI,近屿智能专注AI人才培养
11月28日凌晨,全球最大财经CNBC报道,软银集团创始人兼CEO孙正义再次向人工智能领域的领军企业OpenAI投资了15亿美元。软银对OpenAI的投资已不是首次。就在上个月,软银已在OpenAI的上一轮融资中注入了5亿美元的资金。但他一直寻求获得OpenAI更…...
windows11下的Ubuntu(WSL)中安装界面测试ROS
症状:我在WSL(Ubuntu)中我自己的用户名下面安装好了ROS,输入命令行能用,就是不弹出窗口。 首先到windows应用商店安装Ubuntu,我这里安装的是20.04,然后安装对应的ROS(Noetic版本). 然后windows安装VcXsrv. Ubuntu安装xfce4。 …...
Stable Diffusion 3详解
🌺系列文章推荐🌺 扩散模型系列文章正在持续的更新,更新节奏如下,先更新SD模型讲解,再更新相关的微调方法文章,敬请期待!!!(本文及其之前的文章均已更新&…...
【CSS】设置文本超出N行省略
文章目录 基本使用 这种方法主要是针对Webkit浏览器,因此可能在一些非Chrome浏览器中不适用。 基本使用 例如:设置文本超出两行显示省略号。 核心代码: .ellipsis-multiline {display: -webkit-box; -webkit-box-orient: vertical; /* 设置…...
Python绘画:蛋糕
Python绘画:蛋糕 🐸 前言 🐸🐋 效果图 🐋🐉 代码 🐉 🌵🌲🌳🌴🌿🍀☘️🌱🍃🎋…...
使用wget在清华镜像站下载Anaconda报错ERROR 403: Forbidden.
问题描述 使用wget在清华镜像站下载Anaconda报错ERROR 403: Forbidden. Resolving mirrors.tuna.tsinghua.edu.cn (mirrors.tuna.tsinghua.edu.cn)… 101.6.15.130, 2402:f000:1:400::2 Connecting to mirrors.tuna.tsinghua.edu.cn (mirrors.tuna.tsinghua.edu.cn)|101.6.15…...
道可云人工智能元宇宙每日资讯|第三届京西地区发展论坛成功召开
道可云元宇宙每日简报(2024年11月27日)讯,今日元宇宙新鲜事有: 工信部等十二部门印发《5G规模化应用“扬帆”行动升级方案》 11月25日,工业和信息化部等十二部门印发《5G规模化应用“扬帆”行动升级方案》。《方案》…...
web安全之信息收集
在信息收集中,最主要是就是收集服务器的配置信息和网站的敏感信息,其中包括域名及子域名信息,目标网站系统,CMS指纹,目标网站真实IP,开放端口等。换句话说,只要是与目标网站相关的信息,我们都应该去尽量搜集。 1.1收集域名信息 知道目标的域名之后,获取域名的注册信…...
Google Earth Engine APP(GEE) ——基于多种机器学习多源遥感不同变量组合下的森林地表生物量模型预测APP
目录 Arguments: Returns: ui.Select Arguments: Returns: ui.Chart Arguments: Returns: ui.Chart Arguments: Returns: Classifier Arguments: Returns: Classifier Arguments: Returns: Classifier 本代码的主要功能是将我们提前准备好的森林生物量样本点上传到…...
Redis开发02:redis.windows-service.conf 默认配置文件解析与注解
文件位置:redis安装目录下的 redis.windows-service.conf ,存放了redis服务的相关配置,下面列举出默认配置的含义: 配置项含义bind 127.0.0.1限制 Redis 只监听本地回环地址,意味着只能从本地连接 Redis。protected-m…...
webrtc 3A移植以及实时处理
文章目录 前言一、交叉编译1.Pulse Audio webrtc-audio-processing2.交叉编译 二、基于alsa进行实时3A处理1.demo源码2.注意项3.效果展示 总结 前言 由于工作需要,硬件3A中的AEC效果实在太差,后面使用SpeexDSP的软3A,效果依旧不是很好&#…...
Android so库的编译
在没弄明白so库编译的关系前,直接看网上博主的博文,常常会觉得云里雾里的,为什么一会儿通过Android工程cmake编译,一会儿又通过NDK命令去编译。两者编译的so库有什么区别? android版第三方库编译总体思路: 对于新手小白来说搞明白上面的总体思路图很有必…...
Reachy 2,专为AI与机器人实验室打造的卓越开源双臂移动操作平台!
近期,花粉机器人(POLLEN ROBOTICS)隆重推出Reachy 2仿生机器人——下一代开源操作平台,为AI与机器人实验室带来理想的双臂移动操作科研平台! Reachy 2的仿生性: 》拥有两个基于Maxon无刷电机的仿生7自由度…...
Jest 测试异步函数
异步编程的发展历史 异步函数,就不用我描述了,JS是单线程的,所以没有办法处理异步问题,但是可以通过其他的机制实现 回调函数 例如,我们写一个定时器,在函数fetchData中,有一个延时处理的函数,但是,你有不能等他,如果他是一年呢? 所以,我们给他一个回调函数,来等他执行完返回处…...
linux安全管理-防火墙配置
1. 开启系统防火墙 1、检查内容 检查操作系统是否开启防火墙; 2、配置要求 操作系统开启防火墙; 3、配置方法 systemctl status firewalld ##查看系统防火墙运行状态 systemctl start firewalld ##启动防火墙 systemctl restart firewalld ##重启防火墙…...
Blender 运行python脚本
Blender 运行python脚本 步骤 1:打开 Blender 首先,打开 Blender 软件。你可以从官方网站 [blender.org]( 下载最新的 Blender 版本,并按照安装向导进行安装。 步骤 2:打开“文本编辑器”面板 在 Blender 的默认布局中ÿ…...
Three.js CSS2D/CSS3D渲染器
在Three.js开发过程中,有时需要将 HTML 元素与 Three.js 渲染的 3D 场景相结合,这就需要用到 CSS2DRenderer 和 CSS3DRenderer。本文将详细介绍这两种渲染器的原理及其应用 一、CSS2DRenderer 渲染器 概述 CSS2DRenderer 渲染器用于在 3D 场景中渲染纯…...
centos7 yum install 失败,mirrorlist.centos.org连接不上
由于centos7停止支持,导致mirrorlist.centos.orgdns解析都是失效啦,yum命令没法安装程序. 换一个镜像源就好 sudo cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak sudo curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/…...
BGP协议路由黑洞
一、实验环境 1、分公司与运营商AS自治系统内运行IGP路由协议OSPF、RIP或静态路由,AS自治系统内通过IBGP路由协议建立BGP邻居关系。 2、公司AS自治系统与运营商AS自治系统间运行EBGP路由协议。 3、通过loopback建立IBGP与EBGP邻居关系,发挥loopback建立…...
学习ASP.NET Core的身份认证(基于Session的身份认证1)
ASP.NET Core使用Session也可以实现身份认证,关于Session的介绍请见参考文献5。基于Session的身份认证大致原理就是用户验证成功后将用户信息保存到Session中,然后在其它控制器中从Session中获取用户信息,用户退出时清空Session数据。百度基于…...
《Docker Registry(镜像仓库)详解》
一、引言 在容器化技术日益普及的今天,Docker 已成为众多开发者和企业的首选工具。而 Docker Registry(镜像仓库)作为 Docker 生态系统中的重要组成部分,负责存储和分发 Docker 镜像。本文将深入探讨 Docker Registry 的概念、功能…...
Mybatis
1 什么是MyBatis MyBatis是一个优秀的持久层框架,它对JDBC操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、 结果集检索等JDBC繁杂的过程代码 。…...
uniapp学习(010-3 实现H5和安卓打包上线)
零基础入门uniapp Vue3组合式API版本到咸虾米壁纸项目实战,开发打包微信小程序、抖音小程序、H5、安卓APP客户端等 总时长 23:40:00 共116P 此文章包含第114p-116p的内容 文章目录 H5配置文件设置开始打包上传代码 安卓设置模拟器启动设置基础配置设置图标启动界面…...
IPGuard与Ping32结合,提供企业级数据加密与防泄密解决方案,全面保障敏感数据安全
随着数字化转型的深入推进,企业面临着日益复杂的安全挑战。如何在确保数据流通的同时,保障企业的核心资产不被泄露,是每个企业必须面对的难题。为此,Ping32与IPGuard联合推出了一套全面的企业级数据加密与防泄密解决方案ÿ…...
爬虫与反爬-旋转验证码突破方案(知名短视频、TK海外版 及 某东等等)
概述:文本对旋转验证码进行了突破及讲述了实现原理,代码使用纯算法 OpenCV,使用代价较小同时不用安装一大堆AI训练相关的模组,方便且能够快速上手 当前亲自验证了能够支持的网站:国内知名短视频平台、海外版 以及 某东…...
霍夫变换:原理剖析与 OpenCV 应用实例
简介:本文围绕霍夫变换相关内容展开,先是讲解霍夫变换基本原理,包含从 xy 坐标系到 kb 坐标系及极坐标系的映射等。接着介绍了 cv2.HoughLines、cv2.HoughLinesP 概率霍夫变换、cv2.HoughCircles 霍夫圆变换的函数用法、参数含义、与常规霍夫…...
虚拟机之间复制文件
在防火墙关闭的前提下,您可以通过几种不同的方法将文件从一个虚拟机复制到另一个虚拟机。这里,我们假设您想要从 IP 地址为 192.168.4.5 的虚拟机上的 /tmp 文件夹复制文件到当前虚拟机(192.168.4.6)的 /tmp 文件夹下。以下是几种…...
漏洞管理与补丁管理详解:系统安全的基石
文章目录 漏洞管理与补丁管理详解:系统安全的基石什么是漏洞管理?什么是补丁管理?漏洞管理与补丁管理的联系与区别实施漏洞管理与补丁管理的最佳实践 漏洞管理与补丁管理详解:系统安全的基石 在网络安全的防护体系中,…...
ArrayList与LinkedList的区别是什么?
ArrayList与LinkedList是Java集合框架中实现List接口的两种常见类,它们各自具有独特的数据结构和特点,适用于不同的应用场景。 一、底层数据结构 ArrayList和LinkedList的底层数据结构是它们之间最本质的区别。 ArrayList: ArrayList是基于…...
《Java-数组》
《Java-数组》 1.数组介绍 概念:数组是一种容器,用来存储同种数据类型的多个值。注意:数组容器在存储数据的时候,需要结合隐式转换考虑; 2.数组的定义和初始化 2.1数组定义 定义格式1(常用)…...
Docker 实战:搭建本地 Registry 私有镜像仓库及批量导入脚本
前言:在我之前的博客中,我分享了 Harbor 仓库搭建的详细操作步骤。然而,在实际的生产环境中,并非每个 Docker 环境都需要部署一个规模庞大的 Harbor 仓库。有时,一个轻量级的本地 Registry 私有镜像仓库会更为便捷。本…...
MySQL 启动失败问题分析与解决方案:`mysqld.service failed to run ‘start-pre‘ task`
目录 前言1. 问题背景2. 错误分析2.1 错误信息详解2.2 可能原因 3. 问题排查与解决方案3.1 检查 MySQL 错误日志3.2 验证 MySQL 配置文件3.3 检查文件和目录权限3.4 手动启动 MySQL 服务3.5 修复 systemd 配置文件3.6 验证依赖环境 4. 进一步优化与自动化处理结语 前言 在日常…...
java-分而治之算法
分而治之(Divide and Conquer)算法是一种解决问题的策略,它将一个复杂的问题分解成若干个相同或相似的子问题,递归地解决这些子问题,然后将它们的解合并以解决原始问题。这种算法通常用于排序、搜索、数学计算等领域。…...
透明化教育管理:看板如何提升班级整体效率
随着教育信息化的不断推进,传统的教学和班级管理方式逐渐暴露出时间紧、任务繁、多任务并行等问题。看板管理,作为一种高效的可视化工具,正在成为教师管理教学、提升班级协作与互动的重要利器。通过透明化、系统化的管理方式,看板…...
UDP客户端服务器通信
在这篇博客中,我们将探索 UDP(用户数据报协议) 通信,简要地说,UDP 是一种无连接、快速但不可靠的通信协议,适用于需要快速数据传输但对丢包容忍的场景,比如视频流和在线游戏。就像《我是如此相信…...
helm手动部署Kafka集群
1、到指定node节点创建pv需挂载的目录,若有分布式存储可忽略 mkdir -p /data/kafka-data-0 mkdir -p /data/kafka-data-1 mkdir -p /data/kafka-data-2 mkdir -p /data/kafka-zookeeper-data-0 2、创建pvc ---apiVersion: v1kind: PersistentVolumemetadata:n…...
vue3 ajax获取json数组排序举例
使用axios获取接口数据 可以在代码中安装axios包,并写入到package.json文件: npm install axios -S接口调用代码举例如下: const fetchScore async () > {try {const res await axios.get(http://127.0.0.1:8000/score/${userInput.v…...
c/c++ 用easyx图形库写一个射击游戏
#include <graphics.h> #include <conio.h> #include <stdlib.h> #include <time.h>// 定义游戏窗口的大小 #define WINDOW_WIDTH 800 #define WINDOW_HEIGHT 600// 定义玩家和目标的尺寸 #define PLAYER_SIZE 50 #define TARGET_SIZE 20// 玩家的结构…...
大数据新视界 -- 大数据大厂之 Hive 数据安全:权限管理体系的深度解读(上)(15/ 30)
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...
gitee:创建仓库,存入本地文件至仓库
一、git下载 git:下载与安装-CSDN博客https://blog.csdn.net/weixin_46001736/article/details/144107485?sharetypeblogdetail&sharerId144107485&sharereferPC&sharesourceweixin_46001736&spm1011.2480.3001.8118 二、创建仓库 1、主页面->右上角新增…...
联想品牌的电脑 Bios 快捷键是什么?如何进入 Bios 设置?
在某些情况下,您可能需要通过U盘来安装操作系统或进行系统修复。对于联想电脑用户来说,了解如何设置U盘作为启动设备是非常有用的技能之一。本文简鹿办公将指导您如何使用联想电脑的 U 盘启动快捷键来实现这一目标。 联想笔记本 对于大多数联想笔记本电…...
微信小程序用户登录页面制作教程
微信小程序用户登录页面制作教程 前言 在微信小程序的开发过程中,用户登录是一个至关重要的功能。通过用户登录,我们可以为用户提供个性化的体验,保护用户数据,并实现更复杂的业务逻辑。本文将为您详细讲解如何制作一个用户登录页面,包括设计思路、代码示例以及实现细节…...
Flink细粒度的资源管理
Apache Flink致力于为所有应用程序自动导出合理的默认资源需求。对于希望根据其特定场景微调其资源消耗的用户,Flink提供了细粒度的资源管理。这里我们就来看下细粒度的资源管理如何使用。(注意该功能目前仅对DataStream API有用) 1. 适用场景 使用细粒度的资源管理的可能…...