AVL树、红黑树
数据结构、算法总述:数据结构/算法 C/C++-CSDN博客
AVL树
定义
- 空二叉树是一个 AVL 树
- 如果 T 是一棵 AVL 树,那么其左右子树也是 AVL 树,并且
,h 是其左右子树的高度
- 树高为
平衡因子:右子树高度 - 左子树高度
创建节点
由于 AVL 树的相关操作需要获取节点高度,因此我们需要为节点类添加 height
变量:
/* AVL 树节点类 */
struct TreeNode {int val{}; // 节点值int height = 0; // 节点高度TreeNode *left{}; // 左子节点TreeNode *right{}; // 右子节点TreeNode() = default;explicit TreeNode(int x) : val(x){}
};
“节点高度”是指从该节点到它的最远叶节点的距离,即所经过的“边”的数量。需要特别注意的是,叶节点的高度为 0 ,而空节点的高度为 −1 。我们将创建两个工具函数,分别用于获取和更新节点的高度:
/* 获取节点高度 */
int height(TreeNode *node) {// 空节点高度为 -1 ,叶节点高度为 0return node == nullptr ? -1 : node->height;
}/* 更新节点高度 */
void updateHeight(TreeNode *node) {// 节点高度等于最高子树高度 + 1node->height = max(height(node->left), height(node->right)) + 1;
}
节点的平衡因子(balance factor)定义为节点左子树的高度减去右子树的高度,同时规定空节点的平衡因子为 0 。我们同样将获取节点平衡因子的功能封装成函数,方便后续使用:
/* 获取平衡因子 */
int balanceFactor(TreeNode *node) {// 空节点平衡因子为 0if (node == nullptr)return 0;// 节点平衡因子 = 左子树高度 - 右子树高度return height(node->left) - height(node->right);
}
旋转
我们将平衡因子绝对值 >1 的节点称为“失衡节点”。根据节点失衡情况的不同,旋转操作分为四种:右旋、左旋、先右旋后左旋、先左旋后右旋。
右旋
/* 右旋操作 */
TreeNode *rightRotate(TreeNode *node) {TreeNode *child = node->left;TreeNode *grandChild = child->right;// 以 child 为原点,将 node 向右旋转child->right = node;node->left = grandChild;// 更新节点高度updateHeight(node);updateHeight(child);// 返回旋转后子树的根节点return child;
}
左旋
/* 左旋操作 */
TreeNode *leftRotate(TreeNode *node) {TreeNode *child = node->right;TreeNode *grandChild = child->left;// 以 child 为原点,将 node 向左旋转child->left = node;node->right = grandChild;// 更新节点高度updateHeight(node);updateHeight(child);// 返回旋转后子树的根节点return child;
}
先左旋后右旋
仅使用左旋或右旋都无法使子树恢复平衡。此时需要先对 child
执行“左旋”,再对 node
执行“右旋”。
先右旋后左旋
旋转的选择
判断失衡节点的平衡因子以及较高一侧子节点的平衡因子的正负号,来确定旋转方式:
失衡节点的平衡因子 | 子节点的平衡因子 | 旋转方式 |
---|---|---|
>1 | ≥0 | 右旋 |
>1 | <0 | 先左旋后右旋 |
<-1 | ≤0 | 左旋 |
<-1 | >0 | 先右旋后左旋 |
/* 执行旋转操作,使该子树重新恢复平衡 */
TreeNode *rotate(TreeNode *node) {// 获取节点 node 的平衡因子int _balanceFactor = balanceFactor(node);// 左偏树if (_balanceFactor > 1) {if (balanceFactor(node->left) >= 0) {// 右旋return rightRotate(node);} else {// 先左旋后右旋node->left = leftRotate(node->left);return rightRotate(node);}}// 右偏树if (_balanceFactor < -1) {if (balanceFactor(node->right) <= 0) {// 左旋return leftRotate(node);} else {// 先右旋后左旋node->right = rightRotate(node->right);return leftRotate(node);}}// 平衡树,无须旋转,直接返回return node;
}
基础操作
插入
在 AVL 树中插入节点后,从该节点到根节点的路径上可能会出现一系列失衡节点。因此,我们需要从这个节点开始,自底向上执行旋转操作,使所有失衡节点恢复平衡。
/* 插入节点 */
void insert(int val) {root = insertHelper(root, val);
}/* 递归插入节点(辅助方法) */
TreeNode *insertHelper(TreeNode *node, int val) {if (node == nullptr)return new TreeNode(val);/* 1. 查找插入位置并插入节点 */if (val < node->val)node->left = insertHelper(node->left, val);else if (val > node->val)node->right = insertHelper(node->right, val);elsereturn node; // 重复节点不插入,直接返回updateHeight(node); // 更新节点高度/* 2. 执行旋转操作,使该子树重新恢复平衡 */node = rotate(node);// 返回子树的根节点return node;
}
删除
在二叉搜索树的删除节点方法的基础上,需要从底至顶执行旋转操作,使所有失衡节点恢复平衡。
/* 删除节点 */
void remove(int val) {root = removeHelper(root, val);
}/* 递归删除节点(辅助方法) */
TreeNode *removeHelper(TreeNode *node, int val) {if (node == nullptr)return nullptr;/* 1. 查找节点并删除 */if (val < node->val)node->left = removeHelper(node->left, val);else if (val > node->val)node->right = removeHelper(node->right, val);else {if (node->left == nullptr || node->right == nullptr) {TreeNode *child = node->left != nullptr ? node->left : node->right;// 子节点数量 = 0 ,直接删除 node 并返回if (child == nullptr) {delete node;return nullptr;}// 子节点数量 = 1 ,直接删除 nodeelse {delete node;node = child;}} else {// 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点TreeNode *temp = node->right;while (temp->left != nullptr) {temp = temp->left;}int tempVal = temp->val;node->right = removeHelper(node->right, temp->val);node->val = tempVal;}}updateHeight(node); // 更新节点高度/* 2. 执行旋转操作,使该子树重新恢复平衡 */node = rotate(node);// 返回子树的根节点return node;
}
查找
与二叉搜索树一致
AVL树总代码
AVLTree.h
#include <iostream>
#include <assert.h>
using namespace std;template<class K, class V>
struct AVLTreeNode
{AVLTreeNode* _left;AVLTreeNode* _right;AVLTreeNode* _parent;int _bf; // balance factorpair<K, V> _kv;AVLTreeNode(const pair<K, V>& kv): _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0), _kv(kv){}
};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);}Node* cur = _root;Node* parent = nullptr;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->_left = cur;elseparent->_right = cur;cur->_parent = parent;while (parent)//最坏情况一直更新到root,此时parent为nullptr{if (cur == parent->_left)//更新平衡因子parent->_bf--;elseparent->_bf++;if (parent->_bf == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1)//向上查找,检测是否出现不平衡{cur = cur->_parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){//旋转if (parent->_bf == 2 && cur->_bf == 1)//2,1说明右边子树深度阶梯式增加,所以往左旋转RotateL(parent);//左单旋else if (parent->_bf == -2 && cur->_bf == -1)//-2,-1说明左边子树深度阶梯式增加,所以往右旋转RotateR(parent);//右单旋else if (parent->_bf == -2 && cur->_bf == 1)//-2, 1说明左边子树中间深,先左单旋提高中间节点,再右单旋提高中间节点RotateLR(parent);else if (parent->_bf == 2 && cur->_bf == -1)//2, -1说明右边子树中间深,先右单旋提高中间节点,再左单旋提高中间节点RotateRL(parent);elseassert(false);break;}else //说明插入前AVL出现了问题,直接报错{assert(false);}}return true;}//左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (ppNode->_left == parent)ppNode->_left = subR;elseppNode->_right = subR;subR->_parent = ppNode;}parent->_bf = 0;subR->_bf = 0;}//右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent)ppNode->_left = subL;elseppNode->_right = subL;subL->_parent = ppNode;}parent->_bf = 0;subL->_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 == -1){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (bf == 1){subLR->_bf = 0;subL->_bf = -1;parent->_bf = 0;}else if (bf == 0){subLR->_bf = 0;subL->_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 == -1){subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 1){subR->_bf = 0;subRL->_bf = 0;parent->_bf = -1;}else if (bf == 0){subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;}else{assert(false);}}//中序void InOrder(){_InOrder(_root);cout << "end" << endl;}private://中序void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " - ";_InOrder(root->_right);}Node* _root = nullptr;
};
应用
- 组织和存储大型数据,适用于高频查找、低频增删的场景。
红黑树
红黑树是一种自平衡的二叉搜索树。每个节点额外存储了一个 color 字段 ("RED" or "BLACK"),用于确保树在插入和删除时保持平衡
性质
- 节点为红色或黑色
- 根节点必须为黑色
- NIL 节点(空叶子节点)为黑色
- 红色节点的子节点为黑色
- 从根节点到 NIL 节点的每条路径上的黑色节点数量相同
创建节点
enum Colour
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;pair<K, V> _kv;Colour _col;
};
_left
:左子树_right
:右子树_parent
:父节点_kv
:节点存储的值_col
:该节点的颜色
构造函数
RBTreeNode(const pair<K, V>& kv): _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED)//初始化为红节点
{}
红黑树本体,类中只存储根节点_root
template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
private:Node* _root = nullptr;
}
插入
以二叉搜索树插入逻辑为基础:
bool Insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;//保持根为黑节点}Node* cur = _root;Node* parent = nullptr;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->_left = cur;elseparent->_right = cur;cur->_parent = parent;//调整红黑树//......//......//......return true;
}
代码分析:
if (_root == nullptr) {_root = new Node(kv);_root->_col = BLACK;//保持根为黑节点 }
插入节点时,根节点
_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;} }
找到合适的插入位置,当
key
大于当前节点cur->_kv.first < kv.first
,那么cur
就向左寻找,反之向右寻找。如果当前节点值等于key
,那么说明该节点已经存在,返回false
代表插入失败。当我们的cur
为空指针,说明已经找到了插入的节点,此时跳出循环进行插入。
cur = new Node(kv);if (parent->_kv.first > kv.first)parent->_left = cur; elseparent->_right = cur;cur->_parent = parent;
到达此处,说明前面已经找到插入的位置了,而
parent
节点就是插入位置的父亲节点。根据key
的大小,来判断插入到左边还是右边,插入完成后,再让新节点的_parent
指向parent
。
分情况调整红黑树
对于红黑树的插入,我们需要关注新节点的父亲parent
,祖父grandfather
,叔叔uncle
三个节点:
1.根据parent节点的颜色,来判断是否需要调整
parent节点为黑色
新插入的节点默认为红色,所以新插入节点不会影响路径上黑色节点的数目,而
parent
是黑节点,我们也没有出现连续的红色节点,所以这种情况无需任何调整,直接插入就可以。
parent节点为红色
如果父亲节点为红色,我们就会出现连续的红色节点,这时我们就需要进行调整了
当parent
为红色,我们就需要再根据uncle
的颜色,将插入分类两类:uncle
为红色
以及uncle为黑色
注意:由于parent
是红色节点,此时的grandfather
一定是黑色节点
2.根据uncle节点的颜色,来判断如何调整
uncle节点为红色
当uncle
节点为红色,此时需要进行变色
由于新插入了红色的cur节点,此时parent与cur出现了连续的红色节点,于是我们将parent改为黑色。但是此时以parent为根的所有路径就会多出一个黑节点,于是把grandfather变为红色,来抵消这个新增的黑节点。但是此时以uncle为根的路径又会少一个黑节点,于是把uncle变黑
但是我们将grandfather
变为了红色,这有可能会影响到上一层节点
grandfather
变红之后,可能出现两个红色节点相连的情况,所以我们要写一个while循环,来反复向上检查。
while (parent && parent->_col == RED)//只有parent为红,才更新 (parent可能不存在)
{Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;//uncle存在且为红节点if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else//uncle为黑节点 {//其它处理}}else{Node* uncle = grandfather->_left;//uncle存在且为红节点if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else//uncle为黑节点 {//其它处理}}
}_root->_col = BLACK;//在循环内部不判断root情况,统一处理
代码分析:
while (parent && parent->_col == RED)
用于检测
cur
的parent
的颜色,通过我们前面的推导,如果parent
为红色才需要调整,因此进入循环的条件之一是parent
为红色。另外的parent
有可能为NIL
,此时我们要避免访问空指针,所以空指针也不能进循环
if (parent == grandfather->_left) { } else { }
检测parent 节点是grandfather的左子树还是右子树,这将涉及到如何找uncle以及下一种情况的调整,此时我们要分类讨论:
当parent == grandfather->_left成立,那么uncle就是grandfather的右子树:Node* uncle = grandfather->_right;,反之就是左子树
if (uncle && uncle->_col == RED) {parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent; }
找到
uncle
后,如果uncle
是红色,那么直接进行变色操作,把parent
和uncle
的颜色变为黑色,grandfather
变为红色。
随后由于我们的变色操作可能会影响上一层,此时调整节点,进入下一次while
循环
_root->_col = BLACK;
在先前的while循环中,有可能出现对
_root
节点的操作,导致_root
的颜色改变,而_root
需要保持黑色。如果我们在循环内部,每一次都检测_root
有点麻烦了,于是我们直接在每一次调整完节点后,把_root
强行矫正为黑色
uncle节点为黑色
又因为红黑树中,NIL
也算作黑色节点,所以uncle
为黑色分为以下两种情况:
uncle
为空指针uncle
不为空指针
如果
uncle
为空指针,那么cur
一定是新插入的节点。
因为如果cur不是新插入的节点,那么cur和parent一定有一个原先是黑色节点,不然会出现连续的红色节点。但是如果cur和parent有一个是黑色节点,那么grandfather的左子树就比右子树多出一个黑节点,这就违背了红黑树规则。无论怎样,原先的树都不可能符合规则,所以cur一定是新插入的节点,破坏了规则。
如果
uncle
不为空指针,那么cur
一定是从黑色节点变成的红色节点(不是新插入的)。
因为如果uncle存在,那么grandfather的右子树就存在一个黑节点,而parent是红节点,所以cur和parent的右子树中都至少有一个黑节点,才能保证每一条路径黑节点数目相同。因此cur原先一定是黑节点,是因为cur下层插入了新节点,然后通过while循环向上走,影响到了当前层。
对于这种uncle为黑色的情况,我们需要通过旋转+变色来维持红黑树。
旋转又分单旋和双旋(同AVL树)
当
cur
与parent
的关系和parent
与grandfather
的关系一致时,需要进行单旋
当
cur
与parent
的关系和parent
与grandfather
的关系不一致时,需要进行双旋
当parent == grandfather->_left
else//uncle为黑节点 (旋转)
{if (cur == parent->_left){RotateR(grandfather);//右单旋parent->_col = BLACK;//变色grandfather->_col = RED;//变色}else{RotateL(parent);//左右双旋 - 左单旋RotateR(grandfather);//左右双旋 - 右单旋cur->_col = BLACK;//变色grandfather->_col = RED;//变色}break;//旋转后一定平衡
}
当parent == grandfather->_right
else//uncle为黑节点 (旋转)
{if (cur == parent->_right){RotateL(grandfather);//左单旋parent->_col = BLACK;//变色grandfather->_col = RED;//变色}else{RotateR(parent);//右左双旋 - 右单旋RotateL(grandfather);//右左双旋 - 左单旋cur->_col = BLACK;//变色grandfather->_col = RED;//变色}break;//旋转后一定平衡
}
insert
总代码
bool Insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;//保持根为黑节点}Node* cur = _root;Node* parent = nullptr;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->_left = cur;elseparent->_right = cur;cur->_parent = parent;while (parent && parent->_col == RED)//只有parent为红,才更新 (parent可能不存在){Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;//uncle存在且为红节点if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else//uncle不存在或为黑节点 (旋转){if (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;//旋转后一定平衡}}else{Node* uncle = grandfather->_left;//uncle存在且为红节点if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else//uncle不存在或为黑节点 (旋转){if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;//旋转后一定平衡}}}_root->_col = BLACK;//在循环内部不判断root情况,统一处理return true;
}
红黑树总代码
RBTree.h
#include <iostream>
#include <assert.h>
using namespace std;enum Colour
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;pair<K, V> _kv;Colour _col;RBTreeNode(const pair<K, V>& kv): _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;//保持根为黑节点}Node* cur = _root;Node* parent = nullptr;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->_left = cur;elseparent->_right = cur;cur->_parent = parent;while (parent && parent->_col == RED)//只有parent为红,才更新 (parent可能不存在){Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;//uncle存在且为红节点if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else//uncle不存在或为黑节点 (旋转){if (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;//旋转后一定平衡}}else{Node* uncle = grandfather->_left;//uncle存在且为红节点if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else//uncle不存在或为黑节点 (旋转){if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;//旋转后一定平衡}}}_root->_col = BLACK;//在循环内部不判断root情况,统一处理return true;}//左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (ppNode->_left == parent)ppNode->_left = subR;elseppNode->_right = subR;subR->_parent = ppNode;}}//右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent)ppNode->_left = subL;elseppNode->_right = subL;subL->_parent = ppNode;}}size_t Size(){return _Size(_root);}size_t _Size(Node* root){if (root == nullptr)return 0;;return _Size(root->_left) + _Size(root->_right) + 1;}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(){_InOrder(_root);cout << "end" << endl;}int Height(){return _Height(_root);}private://中序void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " - ";_InOrder(root->_right);}//求高度int _Height(Node* root){if (root == nullptr)return 0;return max(Height(root->_left), Height(root->_right)) + 1;}Node* _root = nullptr;
};
应用
- 作为map&set的底层
相关文章:

AVL树、红黑树
数据结构、算法总述:数据结构/算法 C/C-CSDN博客 AVL树 定义 空二叉树是一个 AVL 树如果 T 是一棵 AVL 树,那么其左右子树也是 AVL 树,并且 ,h 是其左右子树的高度树高为 平衡因子:右子树高度 - 左子树高度 创建节点…...

Vscode编辑器 js 输入log自动补全
最近换了新电脑,新下载了Vscode,记录一下设置项。 Vscode 版本 想要的效果 js文件中输入log(点击tab键),自动补全为 console.log() Vscode 文件》首选项》设置 搜索:snippets Emmet: Show Suggestions…...

structured concurrency
1. 基于 c executions的异步实现 - 从理论到实践 - 知乎 (zhihu.com)...

【免费】在线识别通用验证码接口
模块优势价格5元1000次,每天免费100次api文档支持 使用量小的完全够用了 <?phpfunction Post_base64($base64_str){$url http://api.95man.com:8888/api/Http/Recog?Taken41******QK&imgtype1&len0 ; $fields array( ImgBase64>$base64_str); $ch…...

如何通过汽车制造供应商协同平台,提高供应链的效率与稳定性?
汽车制造供应商协同是指在汽车制造过程中,整车制造商与其零部件供应商之间建立的一种紧密合作的关系。这种协同关系旨在优化整个供应链的效率,降低成本,提高产品质量,加快创新速度,并最终提升整个汽车产业的竞争力。以…...

使用LangChain创建简易聊天机器人
LangChain 是什么 就是一个框架或者说是一个工具,用来写 AI 应用。对,没有错!AI小白也可以,有手就行! LangChain有几个核心模块:Models、Prompts、Chains、Indexes、Memory、Agents。 这篇主要介绍Models、…...

研究生学习---找工作
规划 研一~研二上学期完成小论文,实习,秋招 竞赛:kaggle? 面试题一般简单且为原题,笔试题目很难,不会出原题 项目 找工作软件...

偶然发现了Python的一个BUG。。。
一般情况下,dict(id1, **{id: 1})这句代码应该报TypeError。但如果在捕获了其他异常的情况下,再来执行这句代码,却是会报KeyError,如下图: Python3.10和Python3.9也能复现该情况,正当我摩拳踩掌,…...

36. 有效的数独 - 力扣(LeetCode)
基础知识要求: Java:方法、for循环、if判断、数组 Python: 方法、for循环、if判断、列表、集合 题目: 请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。 数字 1-9 在每一…...

开源收银系统在服装连锁店中发挥的重要作用
在当今竞争激烈的零售市场中,服装连锁店面临着日益复杂的经营环境和多样化的消费需求。在这样的背景下,开源收银系统成为了服装连锁店管理的关键利器。该系统不仅提供了高效的收银功能,还涵盖了进销存管理、会员管理、门店补货等多方面功能&a…...
代码随想录三刷day51
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、力扣200. 岛屿数量二、力扣695. 岛屿的最大面积三、力扣1020. 飞地的数量四、力扣130. 被围绕的区域 前言 依然是从地图周边出发,将周边空格相邻…...
基于python+Django的二维码生成算法设计与实现
博主介绍: 大家好,本人精通Java、Python、C#、C、C编程语言,同时也熟练掌握微信小程序、Php和Android等技术,能够为大家提供全方位的技术支持和交流。 我有丰富的成品Java、Python、C#毕设项目经验,能够为学生提供各类…...

pytorch 2.0 多线程并行,导致GPU利用100%,卡住
背景: 程序中有pytorch模型两个,yolov5,crnn。 之前无论是pth格式,还是TRT格式,并行的都没有问题。 最近发现,多线程ThreadPoolExecutor(max_workers2)调用的时候,即单个进程内处理一张图像&a…...
后端开发面经系列 -- 阿里C++二面面经
阿里C二面面经 公众号:阿Q技术站 来源:https://www.nowcoder.com/feed/main/detail/fc4a48403b534aafa6a6bce14b542c4e?sourceSSRsearch 1、智能指针? std::shared_ptr: 原理:std::shared_ptr是基于引用计数的智能指…...

【Image captioning】In Defense of Grid Features for Visual Question Answering实现流程
In Defense of Grid Features for Visual Question Answering实现流程 网格特征预训练代码 这是该论文的特征预训练代码发布: @InProceedings{jiang2020defense,title={In Defense of Grid Features for Visual Question Answering},author={Jiang, Huaizu and Misra, Ishan…...

MySQL用SQL取三列中最大的数据值
1、有如下数据: ABC000097.0600330.72330.720069.650027.8827.85086.92086.92219.42219.4219.41 需要展示为如下形式: ABC结果列0000097.06097.060330.72330.72330.7200669.65009.6527.8827.85027.8886.92086.9286.92219.42219.4219.41219.42 解决办…...

【Mac】如何解决打开PD虚拟机后Mac无法上网的问题?
问题描述 部分用户在运行Parallels Desktop并打开Windows 11后,发现Windows上网没有问题,但是Mac主机不能访问带域名的网站,而访问带IP的网站没问题,退出Parallels虚拟机以后,Mac网络又恢复正常。 解决办法 退出 Pa…...

【NodeMCU实时天气时钟温湿度项目 7】和风天气API返回JSON数据信息的解压缩实现——ArduinoUZlib功能库
今天是第七专题,主要内容是:导入ArduinoUZlib功能库,借助该库把从【和风天气】官网返回的经过Gzip压缩的JSON数据,进行解压缩和t解析,在串口监视器上输出解析后的JSON信息。 如您需要了解其它专题的内容,请…...
leetcode题目9
回文数 简单 给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。 回文数:是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 思路 对于数字进行反转&a…...
CNAME记录
CNAME记录 维基百科,自由的百科全书 (重定向自CNAME) 真实名称记录(英语:Canonical Name Record),即CNAME记录,是域名系统(DNS)的一种记录。CNAME记录用于…...

【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...

什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...

从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障
关键领域软件测试的"安全密码":Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力,从金融交易到交通管控,这些关乎国计民生的关键领域…...
0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化
是不是受够了安装了oracle database之后sqlplus的简陋,无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话,配置.bahs_profile后也能解决上下翻页这些,但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可,…...
React父子组件通信:Props怎么用?如何从父组件向子组件传递数据?
系列回顾: 在上一篇《React核心概念:State是什么?》中,我们学习了如何使用useState让一个组件拥有自己的内部数据(State),并通过一个计数器案例,实现了组件的自我更新。这很棒&#…...

高保真组件库:开关
一:制作关状态 拖入一个矩形作为关闭的底色:44 x 22,填充灰色CCCCCC,圆角23,边框宽度0,文本为”关“,右对齐,边距2,2,6,2,文本颜色白色FFFFFF。 拖拽一个椭圆,尺寸18 x 18,边框为0。3. 全选转为动态面板状态1命名为”关“。 二:制作开状态 复制关状态并命名为”开…...

Linux入门课的思维导图
耗时两周,终于把慕课网上的Linux的基础入门课实操、总结完了! 第一次以Blog的形式做学习记录,过程很有意思,但也很耗时。 课程时长5h,涉及到很多专有名词,要去逐个查找,以前接触过的概念因为时…...