数据结构进阶——AVL树

数据结构进阶——AVL树

  • 0. 前言
  • 1. AVL树的概念
  • 2. AVL树节点,和树的定义
  • 3. AVL树的插入
  • 4. AVL树的旋转
  • 5. AVL树的验证
  • 6. AVL树的删除(了解)
  • 7. AVL树实现完整代码
  • 8. AVL树的性能


0. 前言


学习本章,需要大家先掌握搜索二叉树,了解键值对pair

  • 学习搜索二叉树点击此处:https://blog.csdn.net/weixin_73870552/article/details/138686066?spm=1001.2014.3001.5501

1. AVL树的概念


1. 搜索二叉树的弊端:

  • 搜索二叉树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),就可以降低树的高度,从而减少平均搜索长度。

2. AVL树的性质:

在这里插入图片描述

  • 左右子树都是AVL树;
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1;
  • 空树是AVL树。

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度 O ( l o g 2 n ) O(log_2 n) O(log2n)


2. AVL树节点,和树的定义


1. 节点:

template<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;pair<K, V> _kv;int _bf;	// balance factor 平衡因子AVLTreeNode(const pair<K, V>& kv): _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}
};
  • 该节点的定义是一个三叉链,_left_right分别指向左右子树,_parent指向父节点;
  • 节点中存储的有效数据为pair<K, V>,类型的键值对;
  • _bf为平衡因子,在此我们定义为右树高度减去左树高度,用来控制左右子树的高度(注意:平衡因子只是其中一种控制平衡的手段,并不是唯一的);
  • 定义了一个模版构造函数,以便后续使用。

2. 树:

template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:...private:Node* _root = nullptr;size_t _size = 0;
};
  • _size记录树中一共有多少数据(多少节点)。

3. AVL树的插入


1. 思路:

  • AVL树就是在搜索二叉树的基础上引入了平衡因子,因此AVL树也可以看成是搜索二叉树。那么AVL树的插入过程可以分为两步:
    • 按照二叉搜索树的方式插入新节点;
    • 调整节点的平衡因子。
  • 调整平衡因子的过程,又可以细分为两部分:
    • 平衡因子的更新;
    • 节点的调整(旋转)+ 平衡因子的更新。

1. 模拟插入过程(红色节点表示新插入节点):

在这里插入图片描述

  • 插入新节点后,第一件事,是更新该新节点的父亲的平衡因子:
    • 新增节点在左,父亲bf--
    • 新增节点在右,父亲bf++
  • 更新完该新节点的父亲后,还要判断是否需要往上更新(新增节点可能会影响祖先,但是一定不会影响兄弟):
    • 更新后,父亲bf == 0,父亲所在的子树高度不变,不用再继续往上更新了,插入结束。 (ps:在插入节点前,父亲bf == 1 or -1,子树一边高,一边低,新插入节点填补低的那边
    • 更新后,父亲bf == 1 or -1,父亲所在的子树高度变了,需要继续往上更新。(ps:在插入节点前,父亲bf == 0,两边一样高,插入新节点导致高度变化
    • 更新后,父亲bf == 2 or -2,父亲所在的子树不平衡,需要旋转调整。
template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){// 插入,类比搜索二叉树插入if (_root == nullptr){_root = new Node(kv);_size++;return true;}// 通过parent向上找父节点Node* parent = nullptr;Node* cur = _root;while (cur){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);if (kv.first > parent->_kv.first){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}_size++;// 调整,AVL树的核心部分while (parent){// 平衡因子的更新if (cur == parent->_left){// 插入在左节点,_bf--parent->_bf--;}else{// 插入在右节点,_bf++parent->_bf++;}// 判断是否要继续向上更新,和是否旋转调整if (parent->_bf == 0){// 更新后 _bf == 0,说明子树高度不变,不需要往上更新break;}else if (parent->_bf == 1 || parent->_bf == -1){// 更新后子树高度改变,需要往上更新cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){// 旋转调整...}else{// 平衡因子绝对值大于2,直接报错assert(false);}}return true;}...private:Node* _root = nullptr;size_t _size = 0;
};

4. AVL树的旋转

  • 如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:

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

  • 先考虑一种最简单的场景(场景1):

    • 直接将8节点链接到9节点的左边即可。
      在这里插入图片描述
  • 再看一种更复杂的场景(场景2):

    • 需要将13节点链接到9节点的右子树,将9节点链接到15节点的左子树。
      在这里插入图片描述
  • 还有更加复杂的情况,但是我们可以对所有的情况进行一个归类,画出抽象图:

    • a,b,c 都是高度为h的AVL平衡树;
    • 只需要将b框中的节点(subRL),链接到30节点(parent)的右子树(将60节点的左子树链接到30节点的右边);再将30节点(parent),连接到60节点(subR)的左子树即可。(别忘了是三叉链,要同步更新父节点)
    • 旋转完后别忘了更新平衡因子,左单旋后,parentsubR节点的_bf都为0,其余节点的平衡因子均不会受到印象。
    • h = 0时,就对应场景1的情况;h = 1时,就对应场景2的情况。

在这里插入图片描述

  • 代码实现:
    • 注意,在更新subRL节点的父节点时,需要先判断subRL是否为空(h=0的情况),如果为空,就不要访问subRL了;
    • 还需要记录parent的父节点,方便旋转后将subR链接给parent->_parent,和整棵树链接起来;
    • parent就是根节点时,需要特殊处理,要更新根节点为subR
template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:// 左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;// 更新左右节点parent->_right = subRL;subR->_left = parent;// 提前记录parent的parentNode* parentParent = parent->_parent;// 更新_parentparent->_parent = subR;if (subRL)	// 判断为不为空,subRL是唯一一个可能为空的节点{subRL->_parent = parent;}// 处理根if (_root == parent){_root = subR;subR->_parent = nullptr;}else if (parentParent->_left == parent){parentParent->_left = subR;subR->_parent = parentParent;}else{parentParent->_right = subR;subR->_parent = parentParent;}// 更新平衡因子parent->_bf = subR->_bf = 0;}...private:Node* _root = nullptr;size_t _size = 0;
};

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

  • 这里不带着大家一步一步分析了,直接上抽象图:
    • a,b,c 均为高度为h的AVL平衡树;
    • 只需要将subLRparent的左,再将parentsubL的右即可;
    • 随后更新平衡因子,subLparent_bf都更新为0。

在这里插入图片描述

  • 类比左单旋,直接上代码:
template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:// 右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;// 更新左右节点subL->_right = parent;parent->_left = subLR;Node* parentParent = parent->_parent;// 更新_parentparent->_parent = subL;if (subLR){subLR->_parent = parent;}if (_root == parent){_root = subL;subL->_parent = nullptr;}else if (parentParent->_left == parent){parentParent->_left = subL;subL->_parent = parentParent;}else{parentParent->_right = subL;subL->_parent = parentParent;}parent->_bf = subL->_bf = 0;}...private:Node* _root = nullptr;size_t _size = 0;
};

3. 新节点插入较高左子树的右侧—左右:先左单旋再右单旋(左右双旋)

  • 注意:该抽象图只是左右双旋中的一种情况,为在60的左子树新增。还有两种情况没有画出来,分别是(1)60作为新增节点的情况;(2)在60的右子树新增的情况。不同的情况,旋转后得到的平衡因子有差别。
    在这里插入图片描述

  • h = 0 的情况:60就是新插入节点。此时b,c子树不存在,注意是不存在,连空都不是。此时,parentsubLsubLR的平衡因子均为0。
    在这里插入图片描述

  • h = 1的情况(该情况又分两种):

    • 在60的左子树新增:注意此时subLsubLR的平衡因子均为0,只有parent的平衡因子为1,和h = 0的情况不一样。
      在这里插入图片描述
    • 在60的右子树新增:这种情况最后的到的子树,和上图只有一处区别,就是b变成了NULLc变成了50,故此时subL的平衡因子为-1,parentsubLR的平衡因子均为0。和在60左子树新增的情况,平衡因子又不一样。
  • 代码实现:

    • 我们可以根据新节点插入后,subLR的平衡因子来确定到底是上述哪种情况。(1)subLR->_bf == 0,说明subLR就是新增节点;(2)subLR->_bf == -1,说明是在subLR的左子树新增;(3)subLR->_bf == 1,说明是在subLR的右子树新增。
template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:// 左右双旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;	// 提前记录subLR的平衡因子,避免单旋操作后,值丢失// 先左旋再右旋RotateL(parent->_left);RotateR(parent);// 更新平衡因子if (bf == 0){// subLR自己就是新增节点subL->_bf = subLR->_bf = parent->_bf = 0;}else if (bf == -1){// subLR的左子树新增parent->_bf = 1;subL->_bf = subLR->_bf = 0;}else if (bf == 1){// subLR的右子树新增parent->_bf = subLR->_bf = 0;subL->_bf = -1;}else{assert(false);	// 检查点}}...private:Node* _root = nullptr;size_t _size = 0;
};

4. 新节点插入较高右子树的左侧—右左:先右单旋再左单旋(右左双旋)

  • 注意:该抽象图只是右左双旋中的一种情况,为在60的右子树新增。还有两种情况没有表示出来,分别是(1)60就是新增节点;(2)在60的左子树新增。
    在这里插入图片描述
  • 类比左右双旋,上代码:
    • 还是要注意,可以通过subRL的平衡因子区分上述三种情况。(1)subRL->_bf == 0,说明subRL就是新增节点;(2) subRL->_bf == 1,说明是在subRL的右子树新增,parent->_bf == -1;(3)subRL->_bf == -1,说明是在subRL的左子树新增,subR->_bf == 1
template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:// 右左双旋void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;	// 提前记录subRL的平衡因子,避免单旋操作后,值丢失// 先右旋再左旋RotateR(parent->_right);RotateL(parent);// 更新平衡因子if (bf == 0){// subRL自己就是新增节点parent->_bf = subR->_bf = subRL->_bf = 0;}else if (bf == -1){// subRL的左子树新增parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else if(bf == 1){// subRL的右子树新增parent->_bf = -1;subR->_bf = 0;subRL->_bf = 0;}else{assert(false);	// 检查点}}...private:Node* _root = nullptr;size_t _size = 0;
};

5. 完善插入:

  • 旋转完成后是不需要继续向上更新平衡因子的,因为(1)旋转让这棵树平衡了;(2)旋转降低了这棵子树的高度,恢复到更插入前一样的高度,所以对上一层没有影响,不用更新。
template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){// 插入,类比搜索二叉树插入if (_root == nullptr){_root = new Node(kv);_size++;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);if (kv.first > parent->_kv.first){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}_size++;// 调整,AVL树的核心部分while (parent){// 平衡因子的更新if (cur == parent->_left){// 插入在左节点,_bf--parent->_bf--;}else{// 插入在右节点,_bf++parent->_bf++;}// 判断是否要继续向上更新,和是否旋转调整if (parent->_bf == 0){// 更新后 _bf == 0,说明子树高度不变,不需要往上更新break;}else if (parent->_bf == 1 || parent->_bf == -1){// 更新后子树高度改变,需要往上更新cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){// 平衡因子绝对值等于2,旋转调整if (parent->_bf == 2 && cur->_bf == 1){// 左单旋调整RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == -1){// 右单旋调整RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == -1){// 先右边高,再左边高,右左双旋RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){// 先左边高,再右边高,左右双旋RotateLR(parent);}else{assert(false);}// 完成旋转后,不需要再往上更新平衡因子了,直接break//	1、旋转让这棵树平衡了//	2、旋转降低了这棵子树的高度,恢复到更插入前一样的高度,所以对上一层没有影响,不用更新break;}else{// 平衡因子绝对值大于2,直接报错assert(false);}}return true;}...private:Node* _root = nullptr;size_t _size = 0;
};

5. AVL树的验证


1. 验证其为二叉搜索树:

  • 如果中序遍历可得到一个有序的序列,就说明为二叉搜索树。

2. 验证其为平衡树:

  • 每个节点子树高度差的绝对值不超过1;
  • 节点的平衡因子是否计算正确。

3. 相关函数实现:

template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}void InOrder(){_InOrder(_root);cout << endl;}int _Height(Node* root){if (root == nullptr)return 0;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}int Height(){return _Height(_root);}bool IsBalance(){return _IsBalance(_root);}bool _IsBalance(Node* root){if (root == nullptr)return true;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);if (rightHeight - leftHeight != root->_bf){cout << root->_kv.first << "平衡因子异常" << endl;return false;}return abs(rightHeight - leftHeight) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);}size_t Size() const {return _size;}...private:Node* _root = nullptr;size_t _size = 0;
};

4. 测试代码:

void Test()
{const int N = 100;srand(time(0));vector<int> v;for (int i = 0; i < N; i++){v.push_back(rand());}AVLTree<int, int> tree;for (auto e : v){tree.Insert(make_pair(e, e));}tree.InOrder();		// 检查是否有序cout << tree.IsBalance() << endl;	// 检查是否平衡cout << tree.Height() << endl;	// 看看高度cout << tree.Size() << endl;	// 看看数据量
}

6. AVL树的删除(了解)


1. 思路:

  • 因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子。只不过,平衡因子更新最差情况下要一直调整到根节点的位置。

2. 例:

在这里插入图片描述

  • 假如我们要删除根节点,可以先在右子树找到替换节点6,进行替换删除,然后更新平衡因子,得到的树如下图所示:

在这里插入图片描述

  • 更新7这个节点的平衡因子,更新为2,不用继续向上更新了,对7节点进行左单旋调整即可。

删除的代码不再写了,面试时也几乎不会考察,最多考察思路。


7. AVL树实现完整代码


#pragma once#include<iostream>
#include<assert.h>using namespace std;template<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;pair<K, V> _kv;int _bf;	// balance factor 平衡因子AVLTreeNode(const pair<K, V>& kv): _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){// 插入,类比搜索二叉树插入if (_root == nullptr){_root = new Node(kv);_size++;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);if (kv.first > parent->_kv.first){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}_size++;// 调整,AVL树的核心部分while (parent){// 平衡因子的更新if (cur == parent->_left){// 插入在左节点,_bf--parent->_bf--;}else{// 插入在右节点,_bf++parent->_bf++;}// 判断是否要继续向上更新,和是否旋转调整if (parent->_bf == 0){// 更新后 _bf == 0,说明子树高度不变,不需要往上更新break;}else if (parent->_bf == 1 || parent->_bf == -1){// 更新后子树高度改变,需要往上更新cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){// 平衡因子绝对值等于2,旋转调整if (parent->_bf == 2 && cur->_bf == 1){// 左单旋调整RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == -1){// 右单旋调整RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == -1){// 先右边高,再左边高,右左双旋RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){// 先左边高,再右边高,左右双旋RotateLR(parent);}else{assert(false);}// 完成旋转后,不需要再往上更新平衡因子了,直接break//	1、旋转让这棵树平衡了//	2、旋转降低了这棵子树的高度,恢复到更插入前一样的高度,所以对上一层没有影响,不用更新break;}else{// 平衡因子绝对值大于2,直接报错assert(false);}}return true;}// 左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;// 更新左右节点parent->_right = subRL;subR->_left = parent;// 提前记录parent的parentNode* parentParent = parent->_parent;// 更新_parentparent->_parent = subR;if (subRL)	// 判断为不为空,subRL是唯一一个可能为空的节点{subRL->_parent = parent;}// 处理根if (_root == parent){_root = subR;subR->_parent = nullptr;}else if (parentParent->_left == parent){parentParent->_left = subR;subR->_parent = parentParent;}else{parentParent->_right = subR;subR->_parent = parentParent;}// 更新平衡因子parent->_bf = subR->_bf = 0;}// 右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;// 更新左右节点subL->_right = parent;parent->_left = subLR;Node* parentParent = parent->_parent;// 更新_parentparent->_parent = subL;if (subLR){subLR->_parent = parent;}if (_root == parent){_root = subL;subL->_parent = nullptr;}else if (parentParent->_left == parent){parentParent->_left = subL;subL->_parent = parentParent;}else{parentParent->_right = subL;subL->_parent = parentParent;}parent->_bf = subL->_bf = 0;}// 右左双旋void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;	// 提前记录subRL的平衡因子,避免单旋操作后,值丢失// 先右旋再左旋RotateR(parent->_right);RotateL(parent);// 更新平衡因子if (bf == 0){// subRL自己就是新增节点parent->_bf = subR->_bf = subRL->_bf = 0;}else if (bf == -1){// subRL的左子树新增parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else if(bf == 1){// subRL的右子树新增parent->_bf = -1;subR->_bf = 0;subRL->_bf = 0;}else{assert(false);	// 检查点}}// 左右双旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;	// 提前记录subLR的平衡因子,避免单旋操作后,值丢失// 先左旋再右旋RotateL(parent->_left);RotateR(parent);// 更新平衡因子if (bf == 0){// subLR自己就是新增节点subL->_bf = subLR->_bf = parent->_bf = 0;}else if (bf == -1){// subLR的左子树新增parent->_bf = 1;subL->_bf = subLR->_bf = 0;}else if (bf == 1){// subLR的右子树新增parent->_bf = subLR->_bf = 0;subL->_bf = -1;}else{assert(false);	// 检查点}}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}void InOrder(){_InOrder(_root);cout << endl;}int _Height(Node* root){if (root == nullptr)return 0;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}int Height(){return _Height(_root);}bool IsBalance(){return _IsBalance(_root);}bool _IsBalance(Node* root){if (root == nullptr)return true;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);if (rightHeight - leftHeight != root->_bf){cout << root->_kv.first << "平衡因子异常" << endl;return false;}return abs(rightHeight - leftHeight) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);}size_t Size() const {return _size;}private:Node* _root = nullptr;size_t _size = 0;
};

8. AVL树的性能


AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 l o g 2 ( N ) log_2 (N) log2(N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:

  • 插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。

因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树。但是一个经常修改的结构,就不太适合用AVL树。


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

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

相关文章

python GUI开发: tkinter事件处理的几种方式详解与应用实战

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

高考之后,专业与学校如何选择?

随着2024年高考的结束&#xff0c;考生们面临着选择专业或学校的难题。选择专业意味着专注于一个特定领域&#xff0c;直接影响未来的职业发展和兴趣培养&#xff1b;而选择学校则提供了更优越的学术氛围和广泛的社交机会&#xff0c;对个人综合素质提升有重要作用。无论选择哪…

【Linux命令行】从时间管理->文件查找压缩的指令详解

目录 1.date 命令&#xff08;显示时间&#xff09; 1.1 显示方面 1.2 设定时间 1.3 时间戳转换 1.4 cal&#xff08;日历&#xff09; 2. 重定向 2.1 输出重定向&#xff08;echo >&#xff09;cin 2.2 追加重定向 >> 2.3 输入重定向 < cout 3.find 指…

机器学习python实践——关于ward聚类分层算法的一些个人心得

最近在利用python跟着参考书进行机器学习相关实践&#xff0c;相关案例用到了ward算法&#xff0c;但是我理论部分用的是周志华老师的《西瓜书》&#xff0c;书上没有写关于ward的相关介绍&#xff0c;所以自己网上查了一堆资料&#xff0c;都很难说清楚ward算法&#xff0c;幸…

Java小结

# Java的特点 Java是一门面向对象的编程语言。面向对象和面向过程的区别参考下一个问题。 Java具有平台独立性和移植性。 Java有一句口号&#xff1a;Write once, run anywhere&#xff0c;一次编写、到处运行。这也是Java的魅力所在。而实现这种特性的正是Java虚拟机JVM。已…

Dart 弱引用进阶

前言 村里的老人说&#xff1a;“真正的强者&#xff0c;都是扮猪吃老虎。” 日常开发中经常需要用到弱引用&#xff0c;Dart 语言里也有提供弱引用的接口 WeakReference&#xff0c;我们可以基于它开发更强大的复杂结构。 在前面的文章中&#xff0c;我们用到了一个以弱引用…

【笔记】【Git】多个dev分支合并到master分支的文件冲突

问题描述 多个dev分支在同步开发&#xff0c;同时发起代码评审&#xff0c;但合入master的时候存在先后顺序&#xff0c;那么后面同文件的操作则会提示“合并有文件冲突”&#xff0c;导致代码无法入库&#xff0c;只能重新提交。 在个人分支中如何解决与master分支差异&#…

香港优才计划中介避坑,深圳哪家优才中介有实力?

随着香港优才计划取消配额限制以来&#xff0c;优才计划递交申请量骤增&#xff0c;许多DIY的申请人在递交申请后&#xff0c;长时间未能收到审批结果&#xff0c;甚至有人等待了12个月之久仍对审批进展一无所知。 而一些有中介协助的申请人&#xff0c;在等待审批的过程中&am…

通过git命令查询某个用户提交信息

要查询某个用户通过 Git 提交了多少行代码&#xff0c;可以使用以下步骤和命令来实现。这些命令将统计该用户的添加和删除的代码行数。 1、切换到你的 Git 仓库&#xff1a; cd /path/to/your/repositorygit命令结果&#xff1a; 2、查询所有用户&#xff1a; git log --pr…

DPDK环境配置

DPDK环境配置 DPDK&#xff08;Data Plane Development Kit&#xff09;是一个开源的软件框架&#xff0c;最初由Intel开发&#xff0c;旨在提升数据包处理性能&#xff0c;尤其是在Intel架构的处理器上。它允许开发者在用户空间&#xff08;user space&#xff09;而不是传统…

github国内加速访问有效方法

这里只介绍实测最有效的一种方法&#xff0c;修改主机的Hosts文件&#xff0c;如果访问github网站慢或者根本无法访问的时候可以采用下面方法进行解决。 1、搜索一个IP查询网站 首先百度搜索选择一个IP查询的网站&#xff0c;这里我用下面这个网站&#xff08;如果该网站失效…

重新安装TortoiseGit后提示权限错误问题解决

今天在Windows11系统中下载安装使用TortoiseGit可视化Git工具&#xff0c;进行代码提交管理。 由于电脑之前是一位开发人员在使用&#xff0c;所以曾经安装使用过这个工具。 重新安装好软件后&#xff0c;在coding网站中复制代码路径后&#xff0c;在本地目录通过鼠标右键选择…

电影《加菲猫家族》观后感

上周看了电影《加菲猫家族》&#xff0c;本片其中有很多明亮的画面&#xff0c;相关艳丽的色彩&#xff0c;充满温馨的场景&#xff0c;很符合加菲猫的一贯画风&#xff0c;即使反派出场时&#xff0c;带有阴暗的感觉&#xff0c;看起也不是特别吓人&#xff0c;比较欢乐气氛&a…

找不到com.fasterxml.jackson.core.exc.StreamWriteException的类文件

1. 前言: 使用springboot搭建的项目, 需要使用 jackson 更改json文件的内容; maven管理jar包, 导入jar包版本信息如下: <!-- 读写json文件所需依赖 --> <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databin…

数据中台-知识图谱平台

【数据分析小兵】专注数据中台产品领域,覆盖开发套件,包含数据集成、数据建模、数据开发、数据服务、数据可视化、数据治理相关产品以及相关行业的技术方案的分享。对数据中台产品想要体验、做二次开发、关注方案资料、做技术交流的朋友们&#xff0c;可以关注我。 1. 概述 随着…

[element-ui]el-select多选选择器选中其中一个选项,不可删除

背景&#xff1a; 产品真的很多奇奇怪怪的需求&#xff0c;一边吐槽一边实现。 前提&#xff1a;选择器作为表格的筛选项&#xff0c;提供三个选项值。 要求&#xff1a;默认选中其中一个值&#xff0c;这个值不可删除。 如图&#xff1a; 小声吐槽&#xff1a;搞这些有什么…

样本学习:当AI遇上“少见多怪”

东汉名臣牟融在其著作《牟子》写道&#xff1a;“少所见&#xff0c;多所怪&#xff0c;睹橐驼&#xff0c;谓马肿背。”意思是见闻少的人遇到不常见的事物就觉得奇怪&#xff0c;见到骆驼也以为是背肿了的马。因此&#xff0c;后人总用“少见多怪”来嘲笑见识浅陋的人。然而&a…

Ps:脚本事件管理器

Ps菜单&#xff1a;文件/脚本/脚本事件管理器 Scripts/Script Events Manager 脚本事件管理器 Script Events Manager允许用户将特定的事件&#xff08;如打开、存储或导出文件&#xff09;与 JavaScript 脚本或 Photoshop 动作关联起来&#xff0c;以便在这些事件发生时自动触…

Excel和Word等工具小技能分享汇编(一)

这里汇集刘小生前期微信公众号分享的Excel和Word等工具小技能&#xff0c;为方便大家查看学习&#xff0c;刘小生对其进行分类整理&#xff0c;后期也会不定期整理更新&#xff0c;如有想学习交流或其他小技巧需求&#xff0c;欢迎留言&#xff0c;我们一起学习进步&#xff01…

云动态摘要 2024-06-17

给您带来云厂商的最新动态&#xff0c;最新产品资讯和最新优惠更新。 最新优惠与活动 [低至1折]腾讯混元大模型产品特惠 腾讯云 2024-06-06 腾讯混元大模型产品特惠&#xff0c;新用户1折起&#xff01; 云服务器ECS试用产品续用 阿里云 2024-04-14 云服务器ECS试用产品续用…