【C++】AVL树(详解)

文章目录

  • 上文链接
  • 一、什么是 AVL 树
  • 二、AVL 树的实现
    • 1. 引入平衡因子
    • 2. 整体结构
    • 3. AVL 树中的插入操作
      • (1) 插入节点
      • (2) 更新平衡因子
        • 更新规则
        • 停止更新条件
    • 4. 旋转
      • (1) 旋转的目的
      • (2) 右单旋
      • (3) 左单旋
      • (4) 左右双旋
      • (5) 右左双旋
    • 5. AVL 树的查找与删除
    • 6. AVL 树的平衡检测
  • 三、完整代码
  • 下文链接

上文链接

  • 【C++】map 容器的使用

一、什么是 AVL 树

我们都知道二叉搜索树,它的搜索效率很高,可以达到 O ( log ⁡ N ) O(\operatorname{log}N) O(logN)。但是,极端情况下,一棵二叉搜索树可能退化成一个链表,就使得搜索的效率变成 O ( N ) O(N) O(N)

请添加图片描述

那么有没有什么办法可以让这个二叉搜索树左右两端 “保持平衡”,不让它退化成链表呢?答案是肯定的,这就是我们要讲的 AVL 树。

AVL 树是最先发明的自平衡的二叉搜索树,AVL 树具备以下特点:

  • 可以是一棵空树;

  • 它的左右子树都是 AVL 树;

  • 左右子树的高度差的绝对值不超过 1。

也就是说,AVL 树的任意一个子树的左右子树的高度差的绝对值都不超过 1,这样平衡之后,我们搜索的效率就可以控制在 O ( log ⁡ N ) O(\operatorname{log}N) O(logN),相比普通的二叉搜索树有了本质的提升。


二、AVL 树的实现

1. 引入平衡因子

AVL 树是一颗高度平衡的搜索二叉树,通过控制高度差去控制平衡。

在 AVL 树的实现中,我们引入一个平衡因子 (balance factor) 的概念,每个节点都有一个平衡因子,任何节点的平衡因子等于右子树的高度减去左子树的高度。也就是说任何一个节点的平衡因子都等于 0/1/-1。AVL 树并不是必须要平衡因子,但是有了平衡因子可以更方便我们去观察和控制树是否平衡,如同风向标一样。

下面这棵树就是一棵典型的 AVL 树。

请添加图片描述

而下面这棵树就不是一棵 AVL 树,因为 10 这个节点它的左右子树的高度差超过了 1。

请添加图片描述


2. 整体结构

template<class K, class V>
struct AVLTreeNode
{// 需要 parent 指针,后续更新平衡因子可以看到pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // 平衡因子AVLTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0){}
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public://...private:Node* _root = nullptr;
};

3. AVL 树中的插入操作

(1) 插入节点

在 AVL 树中插入节点的过程如下:

① 按普通二叉搜索树的规则插入节点;

② 插入新节点以后,可能会导致树的高度发生变化,也就可能会影响某些节点的平衡因子,所以我们需要更新平衡因子。但是观察一下就会发现,插入操作只会影响插入节点的祖先的平衡因子,所以只需要更新从新增节点到根节点路径上节点的平衡因子即可。实际中最坏情况下要更新到根,有些情况更新到中间就可以停止了,具体情况我们下面再详细分析;

③ 更新平衡因子过程中没有出现某个子树不平衡(平衡因子的绝对值大于 1),则插入操作结束;

④ 更新平衡因子过程中如果出现不平衡,对不平衡的子树需要进行旋转操作,旋转的目的是调整这个子树使其平衡,这个操作不会影响更上一层的平衡因子,因此旋转之后插入操作结束。


(2) 更新平衡因子

更新规则

由于这里我们规定平衡因子 = = = 右子树高度 − - 左子树高度,所以插入节点后,必然会增加该节点父节点 (parent) 子树的高度,因此如果新增节点在 parent 的右子树,那么 parent 的平衡因子 += 1,新增节点在 parent 的左子树,parent 的平衡因子 -= 1

但是这还没有结束,新增节点的所有祖先的高度都有可能发生变化,所以我们需要继续顺着从新增节点一直到根节点这条路径更新下去,那么什么时候停止呢?


停止更新条件
  • 如果更新后 parent 的平衡因子等于 0,即 parent 的平衡因子由 -1 变为 0 或者 由 1 变为 0,说明更新前以 parent 为根的子树一边高一边低,此时新增的节点插入在低的那边,插入后以 parent 为根的子树高度不变,不会影响 parent 的父亲节点的平衡因子,因此停止更新

  • 如果更新后 parent 的平衡因子等于 1 或 -1,即 parent 的平衡因子由 0 变为 1或者由 0 变为 -1,说明更新前以 parent 为根的子树两边一样高。插入节点后,以 parent 为根的子树一边高一边低,但仍然符合平衡要求,只不过高度增加了 1,此时会影响 parent 的父亲节点的平衡因子,所以要继续向上更新。继续向上更新的规则与停止条件同理。

  • 如果更新后 parent 的平衡因子等于 2 或 -2,即 parent 的平衡因子由 1 变为 2或者由 -1 变为 -2,说明更新前 parent 子树一边高一边低,新增的节点在高的那边,以 parent 为根的子树高的那边更高了,破坏了平衡,因此需要对这棵子树进行旋转处理,旋转的目的有两个:

    • parent 子树调整为平衡状态;
    • 降低 parent 子树的高度,恢复到插入节点以前的高度。

    所以旋转后也不需要继续往上更新,此时停止更新

  • 一直更新到根,更新后根的平衡因子是 0、1 或 -1 都停止更新。如果更新后为 2 或者 -2,那么对整棵树旋转后停止更新

下面是几个示例:

示例一:插入 -1 节点,由于插入在 1 节点的左子树,所以 1 节点的平衡因子 -= 1。之后由于 1 节点的平衡因子变成 -1,所以需要继续向上更新。更新到 3 节点时,平衡因子依旧 -= 1,此时平衡因子变为 0,根据规则,此时停止更新。

请添加图片描述

示例二:更新到 10 节点时,平衡因子变为 2,此时对以 10 节点为根的这棵子树进行旋转操作,调整平衡,旋转之后停止更新。

请添加图片描述

示例三:一直更新到根节点,无论是 0、1 或者 -1,都停止更新。

请添加图片描述

bool Insert(const pair<K, V>& kv)
{// 正常二叉搜索树插入逻辑if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else return false;}cur = new Node(kv);if (parent->_kv.first < kv.first) parent->_right = cur;else parent->_left = cur;cur->_parent = parent;// 更新平衡因子while (parent){if (cur == parent->_left)parent->_bf--;elseparent->_bf++;if (parent->_bf == 0)  // 更新结束break;else if (parent->_bf == 1 || parent->_bf == -1){// 继续往上更新cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){// 此时不平衡,需要旋转处理// ...break;}else assert(false);}return true;
}

4. 旋转

(1) 旋转的目的

  • 保持搜索树的规则;

  • 让不平衡的树变成平衡的,其次降低旋转树的高度。

旋转总共分为四种,左单旋/右单旋/左右双旋/右左双旋。根据不同的不平衡情况我们需要采取不同的旋转方式。


(2) 右单旋

如下图所示,一棵以 10 为根的树,由 a/b/c 抽象为三棵高度为 h 的子树 (h >= 0),a/b/c 均符合 AVL 树的要求。10 可能是整棵树的根,也可能是一个整棵树中局部的子树的根。这里 a/b/c 是高度为 h 的子树, 是一种概括抽象表示,他代表了所有右单旋的场景。

在 a 子树中插入一个新节点,导致 a 子树的高度从 h 变成 h+1,不断向上更新平衡因子,导致 10 的平衡因子从 -1 变成 -2,10 为根的树左右高度差超过 1,违反平衡规则。仔细观察可发现,新插入的节点在失衡节点 (10 节点) 的左子树的左边,这种情况可以简单记作左左型 (LL 型) 此时我们需要对以 10 为根的这棵树进行右单旋的操作。

请添加图片描述

旋转核心步骤:因为 5 < b子树的值 < 10,所以将 b 变成 10 的左子树,10 变成 5 的右子树,5 变成这棵树新的根,这样依旧符合搜索树的规则,并且控制了平衡,同时这棵的高度恢复到了插入之前,达到了旋转的目的。10 整棵树的一个局部子树,旋转后不会再影响上一层的平衡因子,插入结束。

下面是一些具体的情况:

请添加图片描述
请添加图片描述

右单旋的具体情况可以有无数多种,是列举不完的,比如下面我们可以简单分析一下:

请添加图片描述

但是实际上我们并不需要关注什么 x/y/z,我们只需要关注不平衡的那个节点以及采取何种旋转方式,至于 a/b/c 三个子树长什么样我们并不关心。

下面是代码演示:

// 在插入操作中,发现不平衡时我们需要进行旋转,并判断采取何种旋转方式
else if (parent->_bf == 2 || parent->_bf == -2)
{// 此时不平衡,需要旋转处理// 插入在失衡节点的左子树的左边 --> 右单旋if(parent->_bf == -2 && cur->_bf == -1) RotateR(parent);break;
}// 右单旋
void RotateR(Node* parent)
{Node* subL = parent->_left;  // 左子树Node* subLR = subL->_right;  // 左子树的右子树(b)parent->_left = subLR;if (subLR) subLR->_parent = parent;  // 注意 b 有可能为空,所以要判断一下Node* ppnode = parent->_parent;subL->_right = parent;parent->_parent = subL;// parent 有可能是整棵树的根,也可能是局部子树的根 // 如果是整棵树的根,要修改 _root // 如果是局部的根,指针要跟上一层链接if (parent == _root)  // 是整棵树的根{_root = subL;subL->_parent = nullptr;}else  // 是局部的根{if (ppnode->_left == parent) ppnode->_left = subL;else ppnode->_right = subL;subL->_parent = ppnode;}parent->_bf = subL->_bf = 0;
}

(3) 左单旋

如下图所示,一棵以 10 为根的树,由 a/b/c 抽象为三棵高度为 h 的子树 (h >= 0),a/b/c 均符合 AVL 树的要求。同样的,10 可能是整棵树的根,也可能是一个整棵树中局部的子树的根。这里 a/b/c 是高度为 h 的子树, 是一种概括抽象表示,他代表了所有左单旋的场景。

在 a 子树中插入一个新节点,导致 a 子树的高度从 h 变成 h+1,不断向上更新平衡因子,导致 10 的平衡因子从 1 变成 2,10 为根的树左右高度差超过 1,违反平衡规则,需要进行旋转。新插入的节点在失衡节点 (10 节点) 的右子树的右边,这种情况可以简单记作右右型 (RR 型) 此时我们需要对以 10 为根的这棵树进行左单旋的操作。

请添加图片描述

左单旋和右单旋的操作方式几乎是一样的,二者可以看作是一个镜像的关系。

下面是代码演示:

// 在插入操作中,发现不平衡时我们需要进行旋转,并判断采取何种旋转方式
else if (parent->_bf == 2 || parent->_bf == -2)
{// ...// 插入在失衡节点的右子树的右边 --> 左单旋if (parent->_bf == 2 && cur->_bf == 1) RotateL(parent);break;
}void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL) subRL->_parent = parent;Node* ppnode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (ppnode->_left == parent) ppnode->_left = subR;else ppnode->_right = subR;subR->_parent = ppnode;}parent->_bf = subR->_bf = 0;
}

(4) 左右双旋

在一些情况下,右单旋和左单旋是无法解决不平衡问题的。比如下面的两种情况,左边高时,如果插入位置不是在 a 子树,而是插入在b 子树,b 子树高度从 h 变成 h+1,引发旋转,右单旋后,树依旧不平衡。

请添加图片描述

请添加图片描述

右单旋解决的是插入在不平衡节点的左子树的左边,即纯粹的左边高,但是如果插入在 b 子树中,10 为根的子树不再是纯粹的左边高,因为对于 10 来说是左边高,而对于 5 来说是右边高,此时需要用两次旋转才能解决,以 5 为旋转点进行一个左单旋,以 10 为旋转点再进行一个右单旋,这棵树这棵树就平衡了。

像上述这种情况,插入节点在不平衡节点的左子树的右边时,可以记作左右型 (LR 型),此时采用左右双旋的方法去调整平衡,即先对不平衡节点的左子树进行一次左单旋,之后再对不平衡节点为根的子树进行一次右单旋。

具体的,可以分为以下三种情况来讨论:

请添加图片描述

下面是代码演示:

// 在插入操作中,发现不平衡时我们需要进行旋转,并判断采取何种旋转方式
else if (parent->_bf == 2 || parent->_bf == -2)
{// ...// 插入在失衡节点的左子树的右边 --> 左右双旋if (parent->_bf == -2 && cur->_bf == 1) RotateLR(parent);break;
}void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else if (bf == -1){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 1;}else if (bf == 1){subL->_bf = -1;subLR->_bf = 0;parent->_bf = 0;}else assert(false);
}

(5) 右左双旋

右左双旋与左右双旋几乎一样,二者可以看作是一个对称的关系。当插入节点在不平衡节点的右子树的左边时,可以记作右左型 (RL 型),此时采用右左双旋的方法去调整平衡,即先对不平衡节点的右子树进行一次右单旋,之后再对不平衡节点为根的子树进行一次左单旋。

具体可以分为以下几种情况:

请添加图片描述

以下是代码演示:

// 在插入操作中,发现不平衡时我们需要进行旋转,并判断采取何种旋转方式
else if (parent->_bf == 2 || parent->_bf == -2)
{// ...// 插入在失衡节点的右子树的左边 --> 右左双旋if (parent->_bf == 2 && cur->_bf == -1) RotateRL(parent);break;
}void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 1){subR->_bf = 0;subRL->_bf = 0;parent->_bf = -1;}else if (bf == -1){subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;}else assert(false);
}

5. AVL 树的查找与删除

AVL 树的查找与正常二叉搜索树的查找逻辑一样。

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;
}

AVL 树的删除操作这里不做重点讲解,这个操作会比插入稍复杂一些,但核心思路依然是走正常的二叉搜索树的删除操作 + 更新平衡因子 + 失衡时进行旋转。


6. AVL 树的平衡检测

我们实现的 AVL 树是否合格,需要通过检查左右子树高度差的的程序进行反向验证,同时检查一下节点的平衡因子更新是否出现了问题。

// 获取树的高度
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;
}bool _IsBalanceTree(Node* root)
{// 空树也是 AVL 树 if (nullptr == root)return true;// 计算 pRoot 节点的平衡因子: 即 pRoot 左右子树的高度差 int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);int diff = rightHeight - leftHeight;// 如果计算出的平衡因子与 pRoot 的平衡因子不相等// 或者 pRoot 平衡因子的绝对值超过 1,则一定不是 AVL 树 if (abs(diff) >= 2){cout << root->_kv.first << "⾼度差异常" << endl;return false;}if (root->_bf != diff){cout << root->_kv.first << "平衡因⼦异常" << endl;return false;}// pRoot 的左和右如果都是 AVL 树,则该树一定是 AVL 树 return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}

三、完整代码

// AVLTree.h
#pragma once
#include<cassert>
#include<utility>
#include<cmath>using namespace std;template<class K, class V>
struct AVLTreeNode
{// 需要 parent 指针,后续更新平衡因子可以看到pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf;  // 平衡因子AVLTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _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);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else return false;}cur = new Node(kv);if (parent->_kv.first < kv.first) parent->_right = cur;else parent->_left = cur;cur->_parent = parent;// 更新平衡因子while (parent){if (cur == parent->_left)parent->_bf--;elseparent->_bf++;if (parent->_bf == 0)  // 更新结束break;else if (parent->_bf == 1 || parent->_bf == -1){// 继续往上更新cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){// 此时不平衡,需要旋转处理if (parent->_bf == -2 && cur->_bf == -1) RotateR(parent);else if (parent->_bf == 2 && cur->_bf == 1) RotateL(parent);else if (parent->_bf == -2 && cur->_bf == 1) RotateLR(parent);else if (parent->_bf == 2 && cur->_bf == -1) RotateRL(parent);else assert(false);break;}else assert(false);}return true;}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 IsBalanced(){return _IsBalanced(_root);}int Height(){return _Height(_root);}private:void RotateR(Node* parent){Node* subL = parent->_left;  // 左子树Node* subLR = subL->_right;  // 左子树的右子树(b)parent->_left = subLR;if (subLR) subLR->_parent = parent;  // 注意 b 有可能为空,所以要判断一下Node* ppnode = parent->_parent;subL->_right = parent;parent->_parent = subL;// parent 有可能是整棵树的根,也可能是局部子树的根 // 如果是整棵树的根,要修改 _root // 如果是局部的根,指针要跟上一层链接if (parent == _root)  // 是整棵树的根{_root = subL;subL->_parent = nullptr;}else  // 是局部的根{if (ppnode->_left == parent) ppnode->_left = subL;else ppnode->_right = subL;subL->_parent = ppnode;}parent->_bf = subL->_bf = 0;}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL) subRL->_parent = parent;Node* ppnode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (ppnode->_left == parent) ppnode->_left = subR;else ppnode->_right = subR;subR->_parent = ppnode;}parent->_bf = subR->_bf = 0;}void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else if (bf == -1){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 1;}else if (bf == 1){subL->_bf = -1;subLR->_bf = 0;parent->_bf = 0;}else assert(false);}void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 1){subR->_bf = 0;subRL->_bf = 0;parent->_bf = -1;}else if (bf == -1){subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;}else assert(false);}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;}bool _IsBalanceTree(Node* root){// 空树也是 AVL 树 if (nullptr == root)return true;// 计算 pRoot 结点的平衡因子: 即 pRoot 左右子树的高度差 int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);int diff = rightHeight - leftHeight;// 如果计算出的平衡因子与 pRoot 的平衡因子不相等// 或者 pRoot 平衡因子的绝对值超过 1,则一定不是 AVL 树 if (abs(diff) >= 2){cout << root->_kv.first << "高度差异常" << endl;return false;}if (root->_bf != diff){cout << root->_kv.first << "平衡因子异常" << endl;return false;}// pRoot 的左和右如果都是 AVL 树,则该树一定是 AVL 树 return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);}private:Node* _root = nullptr;
};

下文链接

  • 【C++】红黑树(详解)

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

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

相关文章

shell编程-核心变量知识

文章目录shell简介如何学好shell初识shell什么是shell执行shell脚本常用的三种方式shell变量变量相关的配置文件变量的定义shell核心位置变量shell简介 为什么学习shell&#xff0c;shell的作用 面试题&#xff1a;给你一台主机你的操作流程是什么&#xff1f; 1.自动化安装操…

微电网调度(风、光、储能、电网交互)(MatlabPython代码实现)

赠读者&#xff1a;正在埋头科研的你&#xff0c;或许有时你会困惑于 “投入” 与 “回报” 的时差&#xff0c;会疲惫于 “未知” 与 “确定” 的博弈&#xff0c;但请记得&#xff1a;那些看似 “无用” 的试错&#xff0c;都是在为突破搭建阶梯&#xff1b;那些独自深耕的日…

CentOS 7 环境下安装 JDK 1.8 及解决 wget 命令缺失问题

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务) &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1;个人微信&a…

psql介绍(PostgreSQL命令行工具)(pgAdmin内置、DBeaver、Azure Data Studio)数据库命令行工具

文章目录**1. psql 的核心功能**- **交互式操作**&#xff1a;通过命令行直接与 PostgreSQL 服务器交互&#xff0c;执行 SQL 查询和管理命令。- **元命令支持**&#xff1a;提供以 \ 开头的特殊命令&#xff08;如 \l、\d、\connect&#xff09;&#xff0c;用于管理数据库对象…

设计模式9-责任链模式

定义 Chain of Responsibility Pattern&#xff1a;使多个对象都有机会处理请求&#xff0c;从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链&#xff0c; 并沿着这条链传递该请求&#xff0c;直到有对象处理它为止。 优势 解耦请求发送者与接收者&#…

使用JAVA制作minecraft红石和创造模式插件

这一次主要是红石和创造模式的新加入由于代码较长&#xff0c;所以呃这一段代码就直接劳烦各位呃插进之前的3.0版本里面!!!!!!!!!import org.lwjgl.*; import org.lwjgl.glfw.*; import org.lwjgl.opengl.*; import org.lwjgl.system.*;import java.nio.*; import java.util.*;…

Git 版本管理核心实践与问题解决手册

Git 的核心价值版本控制&#xff1a;完整记录所有修改历史&#xff0c;支持随时回退到任意历史版本团队协作&#xff1a;允许多开发者同时工作&#xff0c;有效避免代码冲突和覆盖问题高效分支&#xff1a;通过分支隔离功能开发与稳定主线&#xff0c;保持项目稳定性变更追溯&a…

hadoop安欣医院挂号看诊管理系统(代码+数据库+LW)

摘 要 随着信息技术的飞速发展&#xff0c;医疗服务行业正逐步向信息化、智能化转型。安欣医院挂号看诊管理系统正是基于这一背景开发的一款集挂号、看诊管理于一体的综合性系统。本系统采用Hadoop大数据处理技术&#xff0c;旨在提高医院挂号看诊的效率&#xff0c;优化医疗…

【PHP】数学/数字处理相关函数汇总,持续更新中~

目录 一、取整 二、向上取整 三、向下取整 四、四舍五入取整 五、四舍五入保留小数点 六、浮点数值 七、绝对值 八、生成随机数 九、数字格式化&#xff08;以千位分割&#xff09; 十、对除法结果取整 十一、返回除法的余数 十二、是否为数字或数字字符串 十三、…

防火墙技术(二):安全区域

安全区域和接口 默认情况下&#xff0c;报文在不同安全区域之间流动时受到控制&#xff0c;报文在同一个安全区域内流动时不受控制。但华为防火墙也支持对同一个安全区域内流动的报文控制&#xff0c;通过安全策略来实现防火墙通过接口来连接网络&#xff0c;将接口划分到安全区…

银河麒麟V10(Phytium,D2000/8 E8C, aarch64)开发Qt

搞了一台国产计算机&#xff0c;银河麒麟V10系统 首先查看系统构架 kylinkylin-pc:/data$ uname -m aarch64 是arm架构的&#xff0c;到 https://www.qt.io/download-qt-installer下载 qt-online-installer-linux-arm64-4.10.0.run

腾讯云 MCP 场景征集计划 | 你的方案,正在定义开发新范式

开发者的进阶正在从“写代码”走向“做场景”。MCP&#xff08;模型上下文协议&#xff09;让你以更低心智负担撬动云AI能力&#xff0c;把时间花在真正的业务价值上。腾讯云开发者MCP广场 正式启动「腾讯云 MCP 场景征集计划」&#xff0c;寻找最懂 MCP 的你&#xff1a;将真实…

21款m1 max升级到macOS 13——Ventura

macOS系统体验&#xff1a;之前入手的m1 max出厂版本的macOS系统是macOS Monterey&#xff0c;也就是macOS 12&#xff0c;用了一段时间后&#xff0c;其实也是很流畅的&#xff0c;无奈最近vscode上的某插件一直提醒我的macOS系统版本过低。索性升级了一下macOS系统了。macOS系…

PostgreSQL WAL机制深度解析与优化

PostgreSQL 的预写日志&#xff08;Write-Ahead Logging, WAL&#xff09; 是其事务持久化和数据完整性的核心机制&#xff0c;通过“先写日志&#xff0c;再写数据”的原则保障故障恢复能力。以下是深度解析&#xff1a;一、WAL 的核心目标 崩溃恢复&#xff08;Crash Recover…

三重积分的性质

文章目录前言几何意义性质先 1 后 2 投影法先 2 后 110.13前言 规律作息。 几何意义 三重积分&#xff0c;只要被积分函数是正的&#xff0c;那么&#xff0c;积分的结果就是质量。可能工作还是太累了&#xff0c;以后有时间可以买买彩票&#xff0c;碰碰运气。。。。 性质…

每日Java并发面试系列(5):基础篇(线程池的核心原理是什么、线程池大小设置为多少更合适、线程池哪几种类型?ThreadLocal为什么会导致内存泄漏?)

1. 什么是线程池&#xff1f;它的核心原理是什么&#xff1f;什么是线程池&#xff1f; 线程池是一种基于池化思想管理和使用线程的机制。它内部维护了多个线程&#xff0c;等待着分配由用户提交的并发执行的任务。这避免了频繁创建和销毁线程带来的开销&#xff0c;从而提高了…

京东商品详情API返回值应用实践

一、API核心功能京东商品详情API&#xff08;如jd.item.get或jd.union.open.goods.query&#xff09;是京东开放平台提供的核心接口&#xff0c;用于通过商品ID&#xff08;skuId&#xff09;或店铺ID检索指定商品的详细信息。该接口支持获取商品基础信息、价格、库存、规格参数…

学习python第14天

汇报一下秋招进度&#xff0c;字节一面完后9天都没给回复&#xff0c;大概率被挂了&#xff0c;但是官网还在流程中&#xff0c;我又没有HR联系方式&#xff0c;所以直接在平台上反馈了&#xff0c;要么赶紧给我过&#xff0c;要么赶紧给我挂&#xff0c;耽误时间。阿里国际一面…

监听nacos配置中心数据的变化

RefreshScope实现nacos配置中心数据的动态刷新。如果需要监听nacos配置中心数据的变化&#xff0c;并执行对应的业务逻辑&#xff0c;则可以使用NacosConfigListener注解。除了需要导入微服务和nacos配置中心的jar&#xff0c;还需要额外导入如下的jar&#xff1a;<dependen…

docker搭建Apisix和Apisix Dashboard

第一步&#xff1a;github下载源码 参考&#xff1a;https://apisix.apache.org/zh/docs/apisix/installation-guide/ git clone https://github.com/apache/apisix-docker.git cd apisix-docker/example第二步&#xff1a;添加Apisix Dashboard镜像 打开./apisix-docker/examp…