【数据结构】AVL树(图文解析 + 代码实现)

目录

1、AVL树的概念

2、AVL树结点的定义

3、AVL树的插入

4、AVL树的旋转

4.1 左单旋

4.2 右单旋

4.3 右左双旋

4.4 左右双旋

5、AVL树的验证

6、AVL树的性能

前面对map/multimap/set/multiset进行了简单的介绍,会大仙,这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。本章介绍的AVL树和下一章介绍的红黑树,就是平衡树

1、AVL树的概念

当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

为什么不将树调整为左右子树的高度相等呢?因为有些结点数量下(如2和4个结点),做不到左右子树高度相等,所以最好的平衡二叉树就是高度不超过1

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

上面这棵树的平衡因子是用右子树高度-左子树高度,这不是一定的,也可以反过来,并没有严格规定,这篇文章中的平衡因子都是用右子树高度-左子树高度的

因为AVL树每个结点的左右子树高度差不超过1,所以平衡因子只有在-1、0、1是才是正常的

2、AVL树结点的定义

我们这里实现的是KV模型的AVL树,为了与map对应,两个值是以pair的形式存储。并且每个结点中还要增加一个平衡因子。当插入新结点时,为了检查路径上是否出现异常的平衡因子,还要增加一个指向父亲结点的指针,另外原来还有指向左右孩子的指针,称为三叉链结构

template<class K,class V>
struct AVLTreeNode
{pair<K, V> _kv;// 三叉链AVLTreeNode* _left;AVLTreeNode* _right;AVLTreeNode* _parent;int _bf; // 平衡因子AVLTreeNode(const pair<K,V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
};

根结点的_parent置为nullptr 

3、AVL树的插入

向AVL树中插入一个新结点时,插入的操作是和二叉搜索树一致的,只是插入一个结点后,需要更新一下这个结点到这棵树的根节点路径上部分结点的平衡因子,若是平衡因子出现异常,则需要进行旋转操作。

插入结点,会影响部分祖先结点的平衡因子。因为这里的平衡因子 = 右子树高度 - 左子树高度

结点插入在左子树,其父亲的平衡因子--

结点插入在右子树,其父亲的平衡因子++

是否需要继续往上更新祖先,要看parent所在子树的高度是否发生了变化,此时有3种情况:

1. 更新后parent的平衡因子变成0

说明没插入时parent的平衡因子是-1/1,一边高一边低,插入后,变成两边一样高,parent所在子树的高度没有发生变化,所以不需要向上更新

2. 更新后parent的平衡因子变成-1/1

说明没插入时parent的平衡因子是0,两边一样高,插入后,变成一边高一边低,parent所在子树的高度变高了,所以需要继续向上更新

3. 更新后parent的平衡因子变成-2/2

说明没插入时parent的平衡因子是-1/1,插入结点插入在高的那一边,进一步加剧了parent所在子树的不平衡,已经违反规定,需要旋转处理

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){// 不平衡了,旋转处理}else{assert(false);}}return true;
}

4、AVL树的旋转

4.1 左单旋

当新结点插入到右子树的右侧,需要进行左单旋 ----- 右右:左单旋

这里面h>=0,表示a、b、c是高度为h的AVL子树

左单旋的标志就是出现异常的结点的平衡因子为2,并且其右子树的平衡因子为1,那么这个时候就进行左单旋。

左单旋步骤:

1. 将subRL变成parent的右子树

2. 将parent变成subR的左子树

3. 建立subR与parent的父亲结点parentParent的关系

4. 更新parent和subR的平衡因子 

完成代码时,除了要完成每个结点的链接关系,还需要注意重新链接后结点父亲的变化和平衡因子的变化。其中平衡因子只有30和60这两个结点有变化,因为要改变平衡因子,需要改变左右子树的高度,并且这两个结点的平衡因子都变成了0。注意,subRL是有可能为nullptr的,当h等于0时,所以修改其父亲结点时需要判断一下。

void RotateL(Node* parent) // 左单旋
{Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentParent = parent->_parent;// 1. 将subRL变成parent的右子树parent->_right = subRL;if (subRL)subRL->_parent = parent;// 2. 将parent变成subR的左子树subR->_left = parent;parent->_parent = subR;// 3. 建立subR与parent的父亲结点parentParent的关系if (parentParent == nullptr) // 说明parent就是根结点{_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left)parentParent->_left = subR;elseparentParent->_right = subR;subR->_parent = parentParent;}// 4. 更新parent和subR的平衡因子 parent->_bf = subR->_bf = 0;
}

4.2 右单旋

当新插入的结点在左子树的左侧,需要进行右单旋 ----- 左左:右单旋

右单旋的标志就是出现异常的结点的平衡因子为-2,并且其左子树的平衡因子为-1,那么这个时候就进行左单旋。 

右单旋的步骤:

1. 将subLR变成parent的左子树

2. 将parent变成subL的右子树

3. 建立subL与parent的父亲结点parentParent的关系

4. 更新parent和subL的平衡因子 

void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;Node* parentParent = parent->_parent;// 1. 将subLR变成parent的左子树parent->_left = subLR;if (subLR)subLR->_parent = parent;// 2. 将parent变成subL的右子树subL->_right = parent;parent->_parent = subL;// 3. 建立subL与parent的父亲结点parentParent的关系if (parentParent == nullptr) // 说明parent就是根结点{_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent)parentParent->_left = subL;elseparentParent->_right = subL;subL->_parent = parentParent;}// 4. 更新parent和subL的平衡因子 parent->_bf = subL->_bf = 0;
}

4.3 右左双旋

当新插入的结点在右子树的左侧,需要先进行右单旋,再进行左单旋 ----- 右左:右左双旋

右左双旋的标志是出现异常的结点的平衡因子是2,其右子树的平衡因子是-1

第一种情况:在右子树的左子树的右子树插入(即c子树插入)

  第二种情况:在右子树的左子树的左子树插入(即b子树插入)

这两种情况都是先进行右单旋,然后进行左单旋,只是旋转完成后,个别结点的平衡因子不同。如何区分是在b插入还是c插入呢?

当h > 0时

1. 插入结点后,若右子树的左子树这个结点的平衡因子是1,则是在c插入的

2. 插入结点后,若右子树的左子树这个结点的平衡因子是-1,则是在b插入的

上面两幅图就是h > 0的两种情况的图

当h == 0 时

3. 插入之前连60这个结点都没有,60这个结点自己就是新增,此时60这个结点的平衡因子是0

右左单旋步骤:

1、计算subRL的平衡因子

2、对subR进行右单旋

3、对parent进行左单旋

4、更新parent、subR、subRL的平衡因子

void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;// 1、计算subRL的平衡因子int bf = subRL->_bf;// 2、对subR进行右单旋RotateR(subR);// 3、对parent进行左单旋RotateL(parent);// 4、更新parent、subR、subRL的平衡因子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);}
}

4.4 左右双旋

当新插入的结点在左子树的右侧,需要先进行左单旋,再进行右单旋 ----- 左右:左右双旋

左右双旋的标志是出现异常的结点的平衡因子是-2,其左子树的平衡因子是1

第一种情况:在左子树的右子树的左子树插入(即b子树插入)

第二种情况:在左子树的右子树的右子树插入(即c子树插入)

第三种情况:当h == 0时

左右单旋的步骤:

1. 计算subLR的平衡因子

2. 对subL进行左单旋

3. 对parent进行右单旋

4. 更新parent、subR、subRL的平衡因子

void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;// 1. 计算subLR的平衡因子int bf = subLR->_bf;// 2. 对subL进行左单旋RotateL(subL);// 3. 对parent进行右单旋RotateR(parent);// 4. 更新parent、subR、subRL的平衡因子if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else if (bf == 1){subL->_bf = -1;subLR->_bf = 0;parent->_bf = 0;}else if (bf == -1){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else{assert(false);}
}

会发现同号单旋,异号双旋

所以此时插入操作的代码为

bool Insert(const pair<K, V>& kv)
{// 若_root为空,直接让新插入的结点变成根if (_root == nullptr){_root = new Node(kv);return true;}// 若_root不为空,按二叉搜索树的规则找到插入的位置,同时也要记录父亲结点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 && parent->_right->_bf == 1){RotateL(parent);}else if (parent->_bf == -2 && parent->_left->_bf == -1){RotateR(parent);}else if (parent->_bf == 2 && parent->_right->_bf == -1){RotateRL(parent);}else{RotateLR(parent);}break; // 旋转完后,一定平衡了,所以可以跳出循环}else{assert(false);}}return true;
}

注意,旋转完成之后,这颗子树的父亲结点的平衡因子一定是0,所以可以直接跳出循环,不需要继续向上更新平衡因子

5、AVL树的验证

在前面,我们已经实现了插入操作,那么按照这个插入函数获得的真的是一颗AVL树吗?

所以,我们需要进行验证。验证只需要计算每个结点的左右树高,判断左右树高的差值小于2即可

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 (root == nullptr) 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 || root->_bf != diff)return false;// pRoot的左和右如果都是AVL树,则该树一定是AVL树return _IsBalanceTree(root->_left) && _IsBalanceTree(root -> _right);
}

这两个函数需要写在AVL树内部,因为用到了_root

6、AVL树的性能

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

template<class K,class V>
struct AVLTreeNode
{pair<K, V> _kv;// 三叉链AVLTreeNode* _left;AVLTreeNode* _right;AVLTreeNode* _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:AVLTree() = default;~AVLTree(){Destory(_root);_root = nullptr;}AVLTree(const AVLTree<K,V>& kv){_root = Copy(kv._root);}AVLTree operator=(AVLTree<K, V> kv){swap(_root, kv._root);}bool Insert(const pair<K, V>& kv){// 若_root为空,直接让新插入的结点变成根if (_root == nullptr){_root = new Node(kv);return true;}// 若_root不为空,按二叉搜索树的规则找到插入的位置,同时也要记录父亲结点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 && parent->_right->_bf == 1){RotateL(parent);}else if (parent->_bf == -2 && parent->_left->_bf == -1){RotateR(parent);}else if (parent->_bf == 2 && parent->_right->_bf == -1){RotateRL(parent);}else{RotateLR(parent);}break; // 旋转完后,一定平衡了,所以可以跳出循环}else{assert(false);}}return true;}void InOrder(){_InOrder(_root);}bool IsBalanceTree(){return _IsBalanceTree(_root);}int Height(){return _Height(_root);}int Size(){_Size(_root);}
private:int _Size(Node* root){return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;}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 (root == nullptr) 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 || root->_bf != diff)return false;// pRoot的左和右如果都是AVL树,则该树一定是AVL树return _IsBalanceTree(root->_left) && _IsBalanceTree(root -> _right);}void RotateL(Node* parent) // 左单旋{Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentParent = parent->_parent;// 1. 将subRL变成parent的右子树parent->_right = subRL;if (subRL)subRL->_parent = parent;// 2. 将parent变成subR的左子树subR->_left = parent;parent->_parent = subR;// 3. 建立subR与parent的父亲结点parentParent的关系if (parentParent == nullptr) // 说明parent就是根结点{_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left)parentParent->_left = subR;elseparentParent->_right = subR;subR->_parent = parentParent;}// 4. 更新parent和subR的平衡因子?parent->_bf = subR->_bf = 0;}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;Node* parentParent = parent->_parent;// 1. 将subLR变成parent的左子树parent->_left = subLR;if (subLR)subLR->_parent = parent;// 2. 将parent变成subL的右子树subL->_right = parent;parent->_parent = subL;// 3.?建立subL与parent的父亲结点parentParent的关系if (parentParent == nullptr) // 说明parent就是根结点{_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent)parentParent->_left = subL;elseparentParent->_right = subL;subL->_parent = parentParent;}// 4.更新parent和subL的平衡因子parent->_bf = subL->_bf = 0;}void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;// 1、计算subRL的平衡因子int bf = subRL->_bf;// 2、对subR进行右单旋RotateR(subR);// 3、对parent进行左单旋RotateL(parent);// 4、更新parent、subR、subRL的平衡因子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);}}void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;// 1. 计算subLR的平衡因子int bf = subLR->_bf;// 2. 对subL进行左单旋RotateL(subL);// 3. 对parent进行右单旋RotateR(parent);// 4.?更新parent、subR、subRL的平衡因子if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else if (bf == 1){subL->_bf = -1;subLR->_bf = 0;parent->_bf = 0;}else if (bf == -1){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else{assert(false);}}void Destory(Node* root){if (root == nullptr) return;Destory(root->_left);Destory(root->_right);delete root;}Node* Copy(Node* root){if (root == nullptr) return nullptr;Node* newNode = new Node(root->_kv);newNode->_left = Copy(root->_left);newNode->_right = Copy(root->_right);return newNode;}void _InOrder(Node* _root){if (_root == nullptr) return;_InOrder(_root->_left);cout << _root->_kv.first << "--" << _root->_kv.second << "--" << _root->_bf << endl;_InOrder(_root->_right);}Node* _root;
};

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

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

相关文章

【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !

目录 C语言指针精讲1. 什么是指针&#xff1f;1.1 指针的内存模型1.1.1 指针演示输出 1.2 指针运算1.2.1 指针算术运算输出1.2.2 指针与数组的关系输出 1.3 指针类型1.3.1 不同类型的指针示例输出1.3.2 void 指针输出 1.4 指针与内存管理动态内存分配输出 1.5 指针与内存泄漏1.…

vue 两个页面切换, 再回到当前页,还是离开前的数据

1、要保证页面的name 和 建路由的大小写一致 2、页面不用生命周期--activated 调接口刷新

在 Kubernetes 中设置 Pod 优先级及其调度策略详解

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

【论文解读】大模型算法发展

一、简要介绍 论文研究了自深度学习出现以来&#xff0c;预训练语言模型的算法的改进速度。使用Wikitext和Penn Treebank上超过200个语言模型评估的数据集(2012-2023年)&#xff0c;论文发现达到设定性能阈值所需的计算大约每8个月减半一次&#xff0c;95%置信区间约为5到14个月…

雪花算法 集群uid重复问题 uid-generator-spring-boot-starter

1、在生成环境 在某个业务使用该插件生成uid,由于业务整合了 mybatis-plus模块 2、该业务是分部署集群部署以及使用的多线程获取uid&#xff0c;使用中发现唯一建冲突&#xff0c;生成的uid有重复。 然后查看日志发现 workerId 始终为0 怀疑是生成workerId出了问题。 查看跟…

开发日志:windows修复SSL漏洞CVE-2016-2183(3389端口)

漏洞危害&#xff1a; 具有足够资源的中间人攻击者可利用此漏洞,通过“birthday”攻击检测会在固定密码与已知纯文本之间泄露 XOR 的冲突,进而泄露密码文本(例如安全 HTTPS Cookie),并可能导致劫持经认证的会话。 参见《支持SSL 64位块大小的密码套件(SWEET32)-修复方案》 参考…

主流树模型讲解、行列抽样、特征重要性梳理总结

本文旨在总结一下常见树模型的行、列抽样特点以及特征重要性的计算方式&#xff0c;也会带着过一遍算法基本原理&#xff0c;一些细节很容易忘记啊。 主要是分类和回归两类任务&#xff0c;相信能搜索这篇文章的你&#xff0c;应该对树模型有一定的了解。 可以搜索 总结 &…

老鼠后五毒也来凑热闹!网红食品惊现「壁虎头」,胖东来已下架…

上周&#xff0c;老鼠有点忙&#xff0c;比如其连续被曝出&#xff0c;出现在了方便面知名品牌的调料包、知名连锁餐饮品牌的黄焖鸡饭中。‍‍‍‍‍‍‍‍‍‍‍‍‍‍ 在小柴「被「添加」进方便面、黄焖鸡饭&#xff1f;老鼠最近忙疯了……」这篇文章的评论区&#xff0c;柴油…

计算机视觉与面部识别:技术、应用与未来发展

引言 在当今数字化时代&#xff0c;计算机视觉技术迅速发展&#xff0c;成为人工智能领域的一个重要分支。计算机视觉旨在让机器理解和解释视觉信息&#xff0c;模拟人类的视觉系统。它在各行各业中发挥着重要作用&#xff0c;从自动驾驶汽车到智能监控系统&#xff0c;再到医疗…

Python:对常见报错导致的崩溃的处理

Python的注释&#xff1a; mac用cmd/即可 # 注释内容 代码正常运行会报以0退出&#xff0c;如果是1&#xff0c;则表示代码崩溃 age int(input(Age: )) print(age) 如果输入非数字&#xff0c;程序会崩溃&#xff0c;也就是破坏了程序&#xff0c;终止运行 解决方案&#xf…

贪心算法(三) ---cmp_to_key, 力扣452,力扣179

目录 cmp_to_key 比较函数 键函数 cmp_to_key 的作用 使用 cmp_to_key 代码解释 力扣452 ---射气球 题目 分析 代码 力扣179 ---最大数 题目 分析 代码 cmp_to_key 在Python中&#xff0c;cmp_to_key 是一个函数&#xff0c;它将一个比较函数转换成一个键函数…

Problems retrieving the embeddings data form OpenAI API Batch embedding job

题意&#xff1a;从OpenAI API批量嵌入作业中检索嵌入数据时遇到问题 问题背景&#xff1a; I have to embed over 300,000 products description for a multi-classification project. I split the descriptions onto chunks of 34,337 descriptions to be under the Batch e…

Nginx优化、防盗链

目录 Nginx优化 隐藏版本信息 网站缓存 日志切割 超时时间 更改进程数 网页压缩 防盗链 在使用源码软件包安装过Nginx服务&#xff0c;具体步骤看上一篇文章 功能模块位置 在Nginx的解压目录下的auto目录内的options文件可以查看Nginx可以安装的功能模块 [rootlocal…

数据结构初阶-单链表

链表的结构非常多样&#xff0c;以下情况组合起来就有8种&#xff08;2 x 2 x 2&#xff09;链表结构&#xff1a; 而我们主要要熟悉的单链表与双向链表的全称分别为&#xff1a;不带头单向不循环链表&#xff0c;带头双向循环链表&#xff0c;当我们对这两种链表熟悉后&#x…

重生之我们在ES顶端相遇第5章-常用字段类型

思维导图 前置 在第4章&#xff0c;我们提到了 keyword&#xff08;一笔带过&#xff09;。在本章&#xff0c;我们将介绍 ES 的字段类型。全面的带大家了解 ES 各个字段类型的使用场景。 字段类型 ES 支持以下字段类型&#xff08;仅介绍开发中常用&#xff0c;更多内容请自…

AI App Store-AI用户评价-多维度打分对比pk-AI社区

C端用户、创作者、AI达人们在选择众多国内外AI厂商的服务时候往往感到一头雾水&#xff0c;那么多功能接近的AI应用(智能对话类、文档总结类、文生图、AI搜索引擎) 究竟在不同用户需求场景下表现怎么样。大部分人如果有需求都会所有平台都尝试一遍&#xff0c;比如一个博主生成…

C++自定义字典树结构

代码 #include <iostream> using namespace std;class TrieNode { public:char data;TrieNode* children[26];bool isTerminal;TrieNode(char ch){data ch;for (int i 0; i < 26; i){children[i] NULL;}isTerminal false;} }; class Trie { public:TrieNode* ro…

基于区块链技术的中药饮片代煎配送服务与监管平台

业务背景 近年来&#xff0c;随着公众对中医药青睐有加&#xff0c;中药代煎服务作为中医药现代化的重要一环&#xff0c;在全国各地蓬勃兴起。鉴于传统煎煮方式的繁琐耗时&#xff0c;医疗机构纷纷转向与第三方中药饮片企业合作&#xff0c;采用集中代煎模式。这些第三方煎药中…

Proactor模型

文章目录 概述1. 异步I/O操作2. 事件通知3. 事件处理函数4. 事件循环5. 多线程支持6. 非阻塞I/O7. 可扩展性8. 错误处理9. 资源管理10. 编程复杂性11. 应用场景流程图 结论 概述 Proactor模型是一种基于异步I/O操作的事件驱动编程模型&#xff0c;主要用于处理并发的I/O事件&a…

冒泡排序(数组作为函数参数)

什么是冒泡排序&#xff1f; 冒泡排序&#xff08;Bubble Sort&#xff09;也是一种简单直观的排序算法。它重复地走访过要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换&#xff0c;…