数据结构——AVL树(详解 + C++模拟实现)

文章目录

  • 前言
  • AVL树的概念
  • AVL树节点的定义
  • AVL树类框架
  • AVL树的插入
  • AVL树的旋转
    • 新节点插入较高子树的左侧 —— 左左: 右单旋
    • 新节点插入较高右子树的右侧——右右: 左单旋
    • 新节点插入较高左子树的右侧 —— 左右: 先左单旋然后再有单旋
    • 新节点插入较高右子树的左侧:右左单旋
    • 旋转总结
  • AVL树插入完整代码
  • AVL树的验证
    • 验证其为二叉搜索树
    • 验证其为平衡树
  • AVL树的删除
  • AVL树的性能
  • 完整实现代码
  • 总结

前言

本篇博客将为大家详细讲述AVL树是什么以及其相对于普通的二叉搜索树有什么优点,将详细讲述其拥有哪些性质,并且通过模拟实现的方式让大家对该数据结构有更深入的理解和认识,对于该数据结构的增删查改操作,其中的删除操作是在普通搜索二叉树的基础上进行一些改进,会简单提及,但不会细讲,重点将会讲述插入操作,查和改操作和二叉搜索树一模一样,也不做讲解。

由于AVL树是一棵特殊的二叉搜索树,因此想要学习AVL树需要先知道二叉搜索树是什么东西,如果有不知道二叉搜索树是什么的小伙半可以先看看博主的另一篇博客:
数据结构—— 二叉搜索树(附c++模拟实现)
该篇博客详细介绍了二叉搜索树。

AVL树的概念

我们知道,二叉搜索树虽然可以缩短查找的效率,但如果数据有序或接近有序,那么此时二叉搜索树将会退化成单支树,此时的查找效率和在链表中搜索等同,效率低下,因此,两位俄罗斯的数学家(G.M.Adelson-Velskii 和E.M.Landis)在1962年的时候发明了一种解决上述问题的方法:

当向二叉搜索树中插入新节点后,如果能够保证每个节点的左右子树高度差的绝对值不超过1(在不破坏二叉搜素树性质的情况下对树中节点进行调整),即可降低树的高度,从而减少平均搜索长度。

因此,AVL树就这样诞生了!
一棵AVL树可以是空树,或者是具有如下性质的搜索二叉树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差的绝对值超过1

在这里插入图片描述

因此,对于一棵AVL树,最重要的就是如何控制每棵树左右子树高度之差都不超过1,这种控制是通过翻转操作来实现的,博主在下文会重点讲解。

另外,AVL树的实现方式有两种,一种就是在插入的过程中动态的检查左右子树的高度差是否超过1,另外一种就是引入一个新的概念——平衡因子,对于每个节点都存储一个int值表示该根节点左右子树的高度差,负数代表左子树更高,正数代表右子树更高,然后在插入的过程中不断维护每个节点的平衡因子即可。

这里由于第二种方法实现起来相对逻辑更加清晰,所以我们采用第二种方法进行模拟实现,并且用key_value的模型进行实现

AVL树节点的定义

和普通二叉搜索树节点定义不同的是,AVL树的节点为了方便进行旋转操作,需要多加一个指针指向其双亲节点,并且还要有一个int值表示平衡因子,定义如下:

template<typename K, typename V>
struct TreeNode
{pair<K, V> _kv;TreeNode<K, V>* _parent = nullptr;TreeNode<K, V>* _left = nullptr;TreeNode<K, V>* _right = nullptr;int _bf = 0;          //balance factorTreeNode(const K& key, const V& value):_kv({ key, value }){}
};

AVL树类框架

template<typename K, typename V>
class AVL_Tree
{typedef TreeNode<K, V> node;
private:node* _root = nullptr;
public:bool insert(const pair<K, V>& kv)	void inorder();void is_AVL();
};

AVL树的插入

AVL树就是在二叉搜索树的基础上引入平衡因子,因此插入过程其实可以分成两步:

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子

对于第一步按照二叉搜索树的规则插入新节点的步骤这里不进行详解,这里重点讲解如何调整插入节点后各个AVL树节点的平衡因子。

pCur表示新插入的节点,pParent表示新插入节点的父节点,(需要找到父节点是节点定义时需要定义指向双亲的指针的原因之一)。
pCur插入后, pParent的平衡因子一定需要调整,插入之前,pParent的平衡因子分为三种情况(-1/0/1),而根据pCur插入位置的不同,可以分为以下两种情况:

  1. 如果pCur插入到pParent的左侧,只需要给pParent的平衡因子减一
  2. 如果pCur插入到pParent的右侧,只需要给pParent的平衡因子加一

pParent的平衡因子经过修改后,可能会出现五种情况,0,±1,±2

  1. 如果pParent的平衡因子为0,说明修改后以pParent为根节点的最高高度并没有发生变化,所以无需继续调整,插入成功
    如下图所示:在这里插入图片描述
  2. 如果插入后pParent的平衡因子为±1,说明插入前pParent的平衡因子一定是0,插入后被更新成±1,说明插入后以pParent为根的子树高度增加了1,也就是说我们需要继续向上更新祖先节点的平衡因子
    如下图所示:
    在这里插入图片描述
    这个过程将不断循环,直到一直更新到pParent的平衡因子为0或者pParent更新到根节点为止。
  1. 如果pParent的平衡因子为±2,那么此时以pParent为根的树已经不满足AVL树的性质了,此时,就需要进行旋转操作,对于旋转是什么,我们在下一个小节进行讲解,这里先给出插入代码整体框架:
	bool insert(const pair<K, V>& kv){//如果头节点为空,直接将值赋给头节点即可if (!_root){node* newNode = new node(kv.first, kv.second);_root = newNode;return true;}//如果不为空,寻找插入位置node* parent = nullptr, *cur = _root;while (cur){auto& key = cur->_kv.first;parent = cur;if (kv.first > key)cur = cur->_right;else if (kv.first < key)cur = cur->_left;else return false;}node* newNode = new node(kv.first, kv.second);//循环结束,找到插入位置if (parent->_kv.first < kv.first)parent->_right = newNode;elseparent->_left = newNode;newNode->_parent = parent;cur = newNode;//更新平衡因子while (parent){//先修改因子//判断新增节点的方位对parent的平衡因子进行处理if (parent->_right == cur)parent->_bf++;elseparent->_bf--;if (parent->_bf == 0) break;//如果parent的平衡因子为±1,继续处理else if (abs(parent->_bf) == 1){cur = parent;parent = parent->_parent;}else if (abs(parent->_bf) == 2){//此时以pParent为根的子树已经违反了AVL树的特性,需要进行旋转处理//...}//由于平衡因子的情况只有以上五种,出现其他种类的平衡因子说明AVL树已经被破坏//通过抛异常来显示else throw "the bf out_of_range";}return true;}

AVL树的旋转

上一小节说到,在一棵本来是平衡的AVL树中插入一个新节点,可能导致不平衡,必须调整树的结构,使之平衡,这一步也叫做旋转,AVL树的旋转也分为四种:

旋转的本质其实是使高度较高的子树高度降低,然后将降低的高度给到其另一个较低的子树

新节点插入较高子树的左侧 —— 左左: 右单旋

在这里插入图片描述

上图是左单旋的普遍思路,但是我们还需要考虑一些特殊场景:

  1. 30的右孩子可能存在,也可能不存在
  2. 60可能是根节点,也可能是子树
    如果60是根节点,旋转完成之后需要更新根节点,如果60是子树,可能是左子树也可能是右子树,需要注意更改上面的链接关系

这里大家可以自行画图模拟一下各种特殊场景,至于为什么要考虑这些场景,是因为虽然整个思路很简单,但是由于我们整棵树是以三叉链的形式来存储的,所以修改过程中需要维护这一结构。
下面是右旋代码:

void reverseR(node* parent)
{node* cur = parent->_left, *ppnode = parent->_parent;node* curR = cur->_right;//连接parent和curRparent->_left = curR;if (curR)curR->_parent = parent;//连接cur和parentcur->_right = parent;parent->_parent = cur;//连接ppnode和curcur->_parent = ppnode;if (ppnode){if (ppnode->_right == parent)ppnode->_right = cur;elseppnode->_left = cur;}else_root = cur;//跟新bfparent->_bf = cur->_bf = 0;
}

新节点插入较高右子树的右侧——右右: 左单旋

在这里插入图片描述
由于右单旋和左单旋基本类似,这里不进行细致讲解,下面是实现代码:

void reverseL(node* parent)
{node* ppnode = parent->_parent, *cur = parent->_right;node* curL = cur->_left;// 连接parent和curLeftparent->_right = curL;if (curL) curL->_parent = parent;// 连接parent和curcur->_left = parent;parent->_parent = cur;//跟新parent和cur的平衡因子parent->_bf = cur->_bf = 0;//更新根节点或者与ppnode的连接关系if (ppnode){if (ppnode->_right == parent)ppnode->_right = cur;elseppnode->_left = cur;cur->_parent = ppnode;}else{_root = cur;cur->_parent = nullptr;}
}

新节点插入较高左子树的右侧 —— 左右: 先左单旋然后再有单旋

在这里插入图片描述
其次,对于右左单旋来说还有一个需要考虑的点就是平衡因子的更新,对于这个问题,我们可以先总结一下上图,(90作为pParent, 30作为pCur, 60作为curR)左右单旋的结果其实使把curR的左子树与pCur链接,curR的右子树与pParent链接,然后curR作为新树的根,那么平衡因子的修改就需要根据60的平衡因子为状况进行修改了,curR的平衡因子一共有三种情况(-1/0/1).我们逐步分析:

  1. curR是新插入的节点 —— curR的平衡因子是0,相当于上图中h等于0的情况

插入后pParentpCurcurR的平衡因子都变成0

  1. 插入在curR的左子树 —— curR的平衡因子是-1

由于插入后curR的左子树交给了pCur,也就是上图中的情况,此时pCur和curR的平衡因子都变成0,pParent的平衡因子变成1

  1. 插入在curR的右子树——curR的平衡因子是1

对应的就是上图中c的高度是h, b的高度是h - 1, 此时pParent 和 curR的平衡因子都变成0, pCur的平衡因子变成-1

因此,旋转后平衡因子的改变是根据curR的平衡因子的状况就行分类修改的,并且由于上文中我们定义了左右旋转的函数,直接复用就可以得到左右单选的函数,代码如下:

	void reverseRL(node* parent){node* cur = parent->_right, * curL = cur->_left;int bf = curL->_bf;reverseR(cur);reverseL(parent);if (bf == 0)cur->_bf = parent->_bf = curL->_bf = 0;else if (bf == -1){cur->_bf = 1;parent->_bf = curL->_bf = 0;}else if (bf == 1){parent->_bf = -1;cur->_bf = curL->_bf = 0;}elsethrow "balance factor out_of_range";}

新节点插入较高右子树的左侧:右左单旋

在这里插入图片描述
这里的思考方式和右左单选相同,留给大家自己思考。

旋转总结

假如以pParent为根的子树不平衡,即pParent的平衡因子为±2,分以下情况考虑:

  1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pCur
  • 当pCur的平衡因子为1是,执行左单旋
  • 如果是-1,执行右左单选
  1. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pCur
  • 当pCur的平衡因子为-1时,执行右单旋
  • 当pCur的平衡因子为1时,执行左右单旋
    另外,我们可以发现旋转完成之后,当前根的平衡因子都变成了0,因此不需要继续向上更新

AVL树插入完整代码

public:bool insert(const pair<K, V>& kv){//如果头节点为空,直接将值赋给头节点即可if (!_root){node* newNode = new node(kv.first, kv.second);_root = newNode;return true;}//如果不为空,寻找插入位置node* parent = nullptr, *cur = _root;while (cur){auto& key = cur->_kv.first;parent = cur;if (kv.first > key)cur = cur->_right;else if (kv.first < key)cur = cur->_left;else return false;}node* newNode = new node(kv.first, kv.second);//循环结束,找到插入位置if (parent->_kv.first < kv.first)parent->_right = newNode;elseparent->_left = newNode;newNode->_parent = parent;cur = newNode;//更新平衡因子while (parent){//先修改因子//判断新增节点的方位对parent的平衡因子进行处理if (parent->_right == cur)parent->_bf++;elseparent->_bf--;if (parent->_bf == 0) break;//如果parent的平衡因子为±1,继续处理else if (abs(parent->_bf) == 1){cur = parent;parent = parent->_parent;}else if (abs(parent->_bf) == 2){//进行翻转操作if (parent->_bf == 2 && cur->_bf == 1) reverseL(parent);else if (parent->_bf == -2 && cur->_bf == -1)reverseR(parent);else if (parent->_bf == 2 && cur->_bf == -1) reverseRL(parent);else if (parent->_bf == -2 && cur->_bf == 1) reverseLR(parent);break;}//由于平衡因子的情况只有以上五种,出现其他种类的平衡因子说明AVL树已经被破坏//通过抛异常来显示else throw "the bf out_of_range";}return true;}
private:void reverseL(node* parent){node* ppnode = parent->_parent, *cur = parent->_right;node* curL = cur->_left;// 连接parent和curLeftparent->_right = curL;if (curL) curL->_parent = parent;// 连接parent和curcur->_left = parent;parent->_parent = cur;//跟新parent和cur的平衡因子parent->_bf = cur->_bf = 0;//更新根节点或者与ppnode的连接关系if (ppnode){if (ppnode->_right == parent)ppnode->_right = cur;elseppnode->_left = cur;cur->_parent = ppnode;}else{_root = cur;cur->_parent = nullptr;}}void reverseR(node* parent){node* cur = parent->_left, *ppnode = parent->_parent;node* curR = cur->_right;//连接parent和curRparent->_left = curR;if (curR)curR->_parent = parent;//连接cur和parentcur->_right = parent;parent->_parent = cur;//连接ppnode和curcur->_parent = ppnode;if (ppnode){if (ppnode->_right == parent)ppnode->_right = cur;elseppnode->_left = cur;}else_root = cur;//跟新bfparent->_bf = cur->_bf = 0;}void reverseRL(node* parent){node* cur = parent->_right, * curL = cur->_left;int bf = curL->_bf;reverseR(cur);reverseL(parent);if (bf == 0)cur->_bf = parent->_bf = curL->_bf = 0;else if (bf == -1){cur->_bf = 1;parent->_bf = curL->_bf = 0;}else if (bf == 1){parent->_bf = -1;cur->_bf = curL->_bf = 0;}elsethrow "balance factor out_of_range";}void reverseLR(node* parent){node* cur = parent->_left;node* curR = cur->_right;int bf = curR->_bf;reverseL(cur);reverseR(parent);if (bf == 0)cur->_bf = parent->_bf = curR->_bf = 0;else if (bf == -1){parent->_bf = -1;cur->_bf = curR->_bf = 0;}else if (bf == 1){cur->_bf = 1;curR->_bf = parent->_bf = 0;}else throw"balance factor out_of_range";}

AVL树的验证

可以通过监视窗口进行验证,但是这样过于麻烦,我们可以设计一个验证函数:

验证其为二叉搜索树

如果中序遍历能够得到一个有序序列,那就说明是二叉搜索树
public:void inorder() {_inorder(_root); cout << endl;return;}
private:void _inorder(node* root){if (!root) return;_inorder(root->_left);cout << root->_kv.first << ' ' << root->_kv.second << endl;_inorder(root->_right);}

验证其为平衡树

  • 每个节点子树的高度绝对值不超过1
  • 验证节点的平衡因子是否计算正确
    博主采用的是回溯的方法来验证,先验证左子树和右子树是否为AVL_Tree,同时返回该树的高度,用于验证上层的树是否为AVL_Tree。
    代码如下:
public:void is_AVL() {auto ret = _is_AVL(_root); if (ret.second) cout << "this tree is AVL_tree\n";}
private:pair<int, bool> _is_AVL(node* root){pair<int, bool> ret{ 0, true };if (!root) return ret;auto ret_left = _is_AVL(root->_left);auto ret_right = _is_AVL(root->_right);ret.second = ret_left.second && ret_right.second;//判断平衡因子是否正确int ans_bf = ret_right.first - ret_left.first;if (ans_bf != root->_bf){printf("平衡因子计算错误,正确平衡因子: %d, 当前平衡因子:%d", ans_bf, root->_bf);ret.second = false;}if (abs(ans_bf) >= 2){cout << "平衡因子超过最大值\n";ret.second = false;}ret.first = max(ret_left.first, ret_right.first) + 1;return ret;}

AVL树的删除

因为AVL树也是搜索二叉树,所以可以按照搜索二叉树的方式将节点删除,然后只需要加入更新平衡因子的步骤就可以了,比较不同的是删除操作下平衡因子的更新是如果删除后节点的平衡因子为0还需要继续更新,而如果是±1不需要继续更新,±2进行旋转,但是旋转完成之后由于根的平衡因子变成了0,还有可能需要继续向上更新。

AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这 样可以保证查询时高效的时间复杂度,即 l o g 2 ( N ) log_2 (N) log2(N)。但是如果要对AVL树做一些结构修改的操 作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时, 有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数 据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

完整实现代码

template<typename K, typename V>
struct TreeNode
{pair<K, V> _kv;TreeNode<K, V>* _parent = nullptr;TreeNode<K, V>* _left = nullptr;TreeNode<K, V>* _right = nullptr;int _bf = 0;TreeNode(const K& key, const V& value):_kv({ key, value }){}
};template<typename K, typename V>
class AVL_Tree
{typedef TreeNode<K, V> node;
private:node* _root = nullptr;
public:bool insert(const pair<K, V>& kv){//如果头节点为空,直接将值赋给头节点即可if (!_root){node* newNode = new node(kv.first, kv.second);_root = newNode;return true;}//如果不为空,寻找插入位置node* parent = nullptr, *cur = _root;while (cur){auto& key = cur->_kv.first;parent = cur;if (kv.first > key)cur = cur->_right;else if (kv.first < key)cur = cur->_left;else return false;}node* newNode = new node(kv.first, kv.second);//循环结束,找到插入位置if (parent->_kv.first < kv.first)parent->_right = newNode;elseparent->_left = newNode;newNode->_parent = parent;cur = newNode;//更新平衡因子while (parent){//先修改因子//判断新增节点的方位对parent的平衡因子进行处理if (parent->_right == cur)parent->_bf++;elseparent->_bf--;if (parent->_bf == 0) break;//如果parent的平衡因子为±1,继续处理else if (abs(parent->_bf) == 1){cur = parent;parent = parent->_parent;}else if (abs(parent->_bf) == 2){//进行翻转操作if (parent->_bf == 2 && cur->_bf == 1) reverseL(parent);else if (parent->_bf == -2 && cur->_bf == -1)reverseR(parent);else if (parent->_bf == 2 && cur->_bf == -1) reverseRL(parent);else if (parent->_bf == -2 && cur->_bf == 1) reverseLR(parent);break;}//由于平衡因子的情况只有以上五种,出现其他种类的平衡因子说明AVL树已经被破坏//通过抛异常来显示else throw "the bf out_of_range";}return true;}void inorder() {_inorder(_root); cout << endl;return;}void is_AVL() {auto ret = _is_AVL(_root); if (ret.second) cout << "this tree is AVL_tree\n";}
private:void reverseL(node* parent){node* ppnode = parent->_parent, *cur = parent->_right;node* curL = cur->_left;// 连接parent和curLeftparent->_right = curL;if (curL) curL->_parent = parent;// 连接parent和curcur->_left = parent;parent->_parent = cur;//跟新parent和cur的平衡因子parent->_bf = cur->_bf = 0;//更新根节点或者与ppnode的连接关系if (ppnode){if (ppnode->_right == parent)ppnode->_right = cur;elseppnode->_left = cur;cur->_parent = ppnode;}else{_root = cur;cur->_parent = nullptr;}}void reverseR(node* parent){node* cur = parent->_left, *ppnode = parent->_parent;node* curR = cur->_right;//连接parent和curRparent->_left = curR;if (curR)curR->_parent = parent;//连接cur和parentcur->_right = parent;parent->_parent = cur;//连接ppnode和curcur->_parent = ppnode;if (ppnode){if (ppnode->_right == parent)ppnode->_right = cur;elseppnode->_left = cur;}else_root = cur;//跟新bfparent->_bf = cur->_bf = 0;}void reverseRL(node* parent){node* cur = parent->_right, * curL = cur->_left;int bf = curL->_bf;reverseR(cur);reverseL(parent);if (bf == 0)cur->_bf = parent->_bf = curL->_bf = 0;else if (bf == -1){cur->_bf = 1;parent->_bf = curL->_bf = 0;}else if (bf == 1){parent->_bf = -1;cur->_bf = curL->_bf = 0;}elsethrow "balance factor out_of_range";}void reverseLR(node* parent){node* cur = parent->_left;node* curR = cur->_right;int bf = curR->_bf;reverseL(cur);reverseR(parent);if (bf == 0)cur->_bf = parent->_bf = curR->_bf = 0;else if (bf == -1){parent->_bf = -1;cur->_bf = curR->_bf = 0;}else if (bf == 1){cur->_bf = 1;curR->_bf = parent->_bf = 0;}else throw"balance factor out_of_range";}//需要知道两个东西,层数高度以及高度差pair<int, bool> _is_AVL(node* root){pair<int, bool> ret{ 0, true };if (!root) return ret;auto ret_left = _is_AVL(root->_left);auto ret_right = _is_AVL(root->_right);ret.second = ret_left.second && ret_right.second;//判断平衡因子是否正确int ans_bf = ret_right.first - ret_left.first;if (ans_bf != root->_bf){printf("平衡因子计算错误,正确平衡因子: %d, 当前平衡因子:%d", ans_bf, root->_bf);ret.second = false;}if (abs(ans_bf) >= 2){cout << "平衡因子超过最大值\n";ret.second = false;}ret.first = max(ret_left.first, ret_right.first) + 1;return ret;}void _inorder(node* root){if (!root) return;_inorder(root->_left);cout << root->_kv.first << ' ' << root->_kv.second << endl;_inorder(root->_right);}};

总结

AVL树的出现较为有效的解决了二叉搜索树在极端情况下效率低下的问题,但还处理的不够完善,因此,后面又出现了红黑树,对于AVL树在一些地方会更有优势,红黑树博主在之后也会讲解!关于AVL树的知识就到此结束了,如果大家有什么疑惑或者发现博主写的有哪些问题,欢迎在评论区指出!

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

1500*B. Zero Array(贪心数学找规律)

Problem - 1201B - Codeforces 解析&#xff1a; 因为每次减少2&#xff0c;如果总和为奇数肯定无法实现。 特例&#xff0c;如果某个数大于其他所有数的总和&#xff0c;同样无法实现。 其他均可实现。 #include<bits/stdc.h> using namespace std; #define int long l…

基于生物地理学优化的BP神经网络(分类应用) - 附代码

基于生物地理学优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于生物地理学优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.生物地理学优化BP神经网络3.1 BP神经网络参数设置3.2 生物地理学算法应用 4…

PyQt5+Qt设计师初探

在上一篇文章中我们搭建好了PyQt5的开发环境&#xff0c;打铁到趁热我们基于搭建好的环境来简单实战一把 一&#xff1a;PyQt5包模块简介 PyQt5包括的主要模块如下。 QtCore模块——涵盖了包的核心的非GUI功能&#xff0c;此模块被用于处理程序中涉及的时间、文件、目录、数…

【Spring Cloud】基于 Feign 实现远程调用,深入探索 Feign 的自定义配置、性能优化以及最佳实践方案

前言 在微服务架构中&#xff0c;服务之间的通信是至关重要的&#xff0c;而远程调用则成为实现这种通信的一种常见方式。在 Java 中&#xff0c;使用 RestTemplate 是一种传统的远程调用方式&#xff0c;但它存在一些问题&#xff0c;如代码可读性差、编程体验不一致以及参数…

基于水循环优化的BP神经网络(分类应用) - 附代码

基于水循环优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于水循环优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.水循环优化BP神经网络3.1 BP神经网络参数设置3.2 水循环算法应用 4.测试结果&#x…

使用css制作3D盒子,目的是把盒子并列制作成3D货架

注意事项&#xff1a;这个正方体的其他面的角度很难调&#xff0c;因此如果想动态生成&#xff0c;需要很复杂的设置动态的角度&#xff0c;反正我是折腾了半天没继续搞下去&#xff0c; 1. 首先看效果&#xff08;第一个五颜六色的是透明多个面&#xff0c;第2-3都是只有3个面…

PageRank(下):数据分析 | 数据挖掘 | 十大算法之一

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ &#x1f434;作者&#xff1a;秋无之地 &#x1f434;简介&#xff1a;CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作&#xff0c;主要擅长领域有&#xff1a;爬虫、后端、大数据…

云服务器CVM_云主机_云计算服务器_弹性云服务器-腾讯云

腾讯云服务器CVM提供安全可靠的弹性计算服务&#xff0c;腾讯云明星级云服务器&#xff0c;弹性计算实时扩展或缩减计算资源&#xff0c;支持包年包月、按量计费和竞价实例计费模式&#xff0c;CVM提供多种CPU、内存、硬盘和带宽可以灵活调整的实例规格&#xff0c;提供9个9的数…

linearlayout中使用多个weight导致部分子控件消失异常

问题描述&#xff1a; 在一个linearlayout中写了两个用到weight的布局&#xff0c;在androidstudio中显示正常 但是代码跑起来之后最下面哪一行都消失了&#xff1b; 解决办法1 把两个用到weight的改成一个了&#xff0c;外面那层的weight写成固定宽度就能正常显示出丢失的…

二叉树--翻转二叉树

文章前言&#xff1a;如果有小白同学还是对于二叉树不太清楚&#xff0c;作者推荐&#xff1a;二叉树的初步认识_加瓦不加班的博客-CSDN博客 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 如果思路不清楚&#xff0c;请看动态页面&am…

【Spring内容介绍 | 第一篇】什么是事务管理

前言&#xff1a; 当今软件开发行业中&#xff0c;事务管理是一个不可或缺的重要组成部分。随着企业应用的复杂性和数据交互的增加&#xff0c;确保数据的一致性和完整性变得越来越关键。Spring框架作为一种全功能的应用程序开发框架&#xff0c;为我们提供了强大而灵活的事务管…

【yolo系列:yolov7训练添加spd-conv】

系列文章目录 yolov7训练添加spd-conv 文章目录 系列文章目录一、spd-conv是什么&#xff1f;二、使用步骤1.第一步&#xff1a;先在models/common.py加上2.第二步&#xff1a;models/yolo.py加上2.第三步&#xff1a;修改yolov7的yaml文件 总结 提示&#xff1a;以下是本篇文…

Linux指令大全(文件和目录操作、文件内容查看和编辑、系统信息和管理、网络和通信、压缩和解压缩、权限管理、包管理……)

目录 前言 VMware 16.2.4Ubuntu18.04 Windows11安装WSL Linux指令大全 一、文件和目录操作指令 cd&#xff1a;切换当前目录 ls&#xff1a;列出目录内容 mkdir&#xff1a;创建新目录 rm&#xff1a;删除文件或目录 cp&#xff1a;复制文件或目录 mv&#xff1a;移…

java 将字符串转为Base64格式与将Base64内容解析出来

首先要引入依赖包 import java.nio.charset.StandardCharsets; import java.util.Base64;然后对应一下两个代码 将字符串转为Base64 Base64.getEncoder().encodeToString(需要转换的字符串.getBytes(StandardCharsets.UTF_8));将 Base64 字符串解析成原来的内容 byte[] deco…

SpringBoot详解

文章目录 1. Xml 和 JavaConfig1.1 什么是 JavaConfig1.2 JavaConfig 配置容器1.3 ImportResource1.4 PropertyResource 2. 注解SpringBootApplication3.Spring Boot 核心配置文件3.1 yaml格式3.2 .yml 文件3.3 多环境配置3.4 Spring Boot 自定义配置3.4.1 Value 注解 3.5 注解…

基于知识蒸馏的两阶段去雨去雪去雾模型学习记录(三)之知识测试阶段与评估模块

去雨去雾去雪算法分为两个阶段&#xff0c;分别是知识收集阶段与知识测试阶段&#xff0c;前面我们已经学习了知识收集阶段&#xff0c;了解到知识阶段的特征迁移模块&#xff08;CKT)与软损失&#xff08;SCRLoss&#xff09;,那么在知识收集阶段的主要重点便是HCRLoss(硬损失…

css--踩坑

1. 子元素的宽高不生效问题 设置flex布局后&#xff0c;子元素的宽高不生效问题。 如果希望子元素的宽高生效&#xff0c;解决方法&#xff0c;给子元素添加如下属性&#xff1a; flex-shrink: 0; flex-shrink: 0;2. 横向滚动&#xff08;子元素宽度不固定&#xff09; /* tab…

大数据软件项目的应用行业

大数据软件项目可以应用于各种不同的行业&#xff0c;以帮助组织更好地理解和利用其数据资产&#xff0c;从而做出更明智的决策、提高效率并推动创新。以下是一些主要行业&#xff0c;大数据软件项目可以发挥重要作用的示例&#xff0c;希望对大家有所帮助。北京木奇移动技术有…

0基础学习VR全景平台篇 第104篇:720全景后期软件安装

上课&#xff01;全体起立~ 大家好&#xff0c;欢迎观看蛙色官方系列全景摄影课程&#xff01; 摄影进入数码时代&#xff0c;后期软件继承“暗房工艺”&#xff0c;成为摄影师表达内在情感的必备工具。 首先说明&#xff0c;全景摄影与平面摄影的一个显著的区别是全景图片需…

【unity】制作一个角色的初始状态(左右跳二段跳)【2D横板动作游戏】

前言 hi~ 大家好&#xff01;欢迎大家来到我的全新unity学习记录系列。现在我想在2d横板游戏中&#xff0c;实现一个角色的初始状态-闲置状态、移动状态、空中状态。并且是利用状态机进行实现的。 本系列是跟着视频教程走的&#xff0c;所写也是作者个人的学习记录笔记。如有错…