【数据结构】手撕AVL树(万字详解)
目录
- AVL树的概念
- 为啥要有AVL树?
- 概念
- AVL树节点的定义
- AVL树的插入
- AVL树的旋转
- 左单旋
- 右单旋
- 左右双旋
- 右左双旋
- AVL树的查找
- AVL树的验证
- end
AVL树的概念
为啥要有AVL树?
在上一章节的二叉搜索树中,我们在插入节点的操作中。有可能一直往一边插入节点,这就导致我们的二叉搜索树退化出一颗单支树,那么他的查找效率就和链表是一样的了。
概念
-
因此对于上面的问题,两位俄罗斯的数学家G.M.A delson-Velskii和E.M.Landis在1962年发明了解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
-
AVL树可以是一棵空树,也可以是具有以下性质的一棵二叉搜索树:
- 树的左右子树都是AVL树。
- 树的左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/01)。
-
如果一颗二叉搜索树的高度是平衡的,那么他就是AVL树,AVL树保持了两边高度的平衡,那么查找效率就是o(logN)
-
注意的是我们AVL树是不能让两边高度之差是0的,只有满二叉树才能保证。
AVL树节点的定义
我们这里直接实现KV模型的AVL树,为了方便后续的操作,这里将AVL树中的结点定义为三叉链结构(也就是多了一个parent,为什么后面讲解),并在每个结点当中引入平衡因子(右子树高度-左子树高度)。除此之外,还需编写一个构造新结点的构造函数,由于新构造结点的左右子树均为空树,于是将新构造结点的平衡因子初始设置为0即可。
template<class K, class V>
struct AVLTreeNode
{//三叉链AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;//存储的键值对pair<K, V> _kv;//平衡因子(balance factor)int _bf; //右子树高度-左子树高度//构造函数AVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}
};
AVL树的插入
- AVL树的插入有以下三个步骤
- 按照二叉搜索树的插入方式,先搜索插入位置
- 找到插入位置后,把节点插入到该为位置中。
- 从现在位置开始向上更新平衡因子,如果出现不平衡,需要旋转。
因为AVL树本身就是一棵二叉搜索树,因此寻找结点的插入位置是非常简单的,按照二叉搜索树的插入规则:
- 待插入结点的key值比当前结点小就插入到该结点的左子树。
- 待插入结点的key值比当前结点大就插入到该结点的右子树。
- 待插入结点的key值与当前结点的key值相等就插入失败。
与二叉搜索树的不同点是,AVL树每次插入一个新节点就需要向上更新平衡因子,因为插入的节点会影响上面子树的平衡因子
由于一个结点的平衡因子是否需要更新,是取决于该结点的左右子树的高度是否发生了变化,因此插入一个结点后,该结点的祖先结点的平衡因子可能需要更新。
- 所以我们插入结点后需要倒着往上更新平衡因子,更新规则如下:
- 新增结点在parent的右边,parent的平衡因子++.
- 新增结点在parent的右边,parent的平衡因子+ +
- 这也是我们为啥要使用parent来实现三叉链的原因,因为我们需要向上更新平衡因子,判断这个子树是否需要旋转来保持左右高度平衡。
每更新完一个结点的平衡因子后,都需要进行以下判断:
- 如果parent的平衡因子等于-1或者1,表明还需要继续往上更新平衡因子。
- 如果parent的平衡因子等于0,表明无需继续往上更新平衡因子了。
- 如果parent的平衡因子等于-2或者2,表明此时以parent结点为根结点的子树已经不平衡了,需要进行旋转处理。
判断理由的说明:
- 1/-1,只有从0经过–/++的操作才能变成-1/1, 比如以parent为根节点的子树,开始并没有左右孩子,往左边插入新节点就是-1,往右边插入新节点就是1,这样的插入操作导致parent为根节点的子树高度发生了变化,这个时候parent也可能是别人的孩子节点,所以需要向上更新平衡因子,检查他的父节点的平衡因子做出对应的操作
- 平衡因子为0,这种情况以parent为根节点的子树一定是一边的高度高和一边的高度矮,并且插入的新节点一定是插入到矮的那边,导致左右高度相等,那么根节点的左右子树高度相等,平衡因子是左右高度子树之差,那么相互抵消,就是0
- 平衡因子是-2或2,此时parent结点的左右子树高度之差的绝对值已经超过1了,不满足AVL树的要求,因此需要进行旋转处理。
若是在更新平衡因子的过程当中,出现了平衡因子为-2/2的结点,这时我们需要对以该结点为根结点的树进行旋转处理,而旋转处理分为四种,在进行分类之前我们首先需要进行以下分析:
- 我们将插入的新节点称为cur, 他的父节点称为parent,我们插入成功后,第一个更新的平衡因子的节点就是parent这个节点的平衡因子,现在如果我们的parent平衡因子是-1/1需要继续往上更新parent的parent的平衡因子,那么如果想要往上更新找到parent的parent节点就需要执行下面的语句
cur = parent;
parent = parent->_parent;
- 这里我想说明一点,如果parent的平衡因子是-2/2,那么cur(他的孩子节点)一定是-1/1而不会是0.
- 证明一下:假设cur的平衡因子是0,那么他一定是一个新增节点,因为只有新增节点才能会往上更新parent的平衡因子。反之,如果这里不是新增节点而是之前向上更新的节点把他平衡因子更新为0,那么在cur哪里就已经结束了,我们说过平衡因子为0就代表以当前节点为根节点的子树已经平衡直接break, 所以当前节点的父节点不可能是-2/2,因为当前节点就break了,不会更新到-2/2
- 而如果当前节点是一个新增节点的话,他的父节点的平衡因子只能是-1/0/1其中的一种。在新增结点插入前,其父结点的状态有以下两种可能:
- 其父结点是一个左右子树均为空的叶子结点,其平衡因子是0,新增结点插入后其平衡因子更新为-1/1。
- 其父结点是一个左子树或右子树为空的结点,其平衡因子是-1/1,新增结点插入到其父结点的空子树当中,使得其父结点左右子树当中较矮的一棵子树增高了,新增结点后其平衡因子更新为0。
- 综上所述,当parent的平衡因子为-2/2时,cur的平衡因子必定是-1/1而不会是0。
根据此结论,我们可以将旋转处理分为以下四类:
- 当parent的平衡因子是-2的,cur的平衡因子是-1的时候,需要右单旋。
- 当parent的平衡因子是2,cur的平衡因子是1的时候,需要左单旋
- 当parent的平衡因子是-2,cur的平衡因子是1的时候,需要左右单旋
- 当parent对的平衡因子是2, cur的平衡因子是01的时候,需要右左单旋
- 并且,在进行旋转处理后就无需继续往上更新平衡因子了,因为旋转后树的高度变为插入之前了,即树的高度没有发生变化,也就不会影响其父结点的平衡因子了。具体原因请看后面的旋转讲解。
AVL树的旋转
左单旋
- 旋转示意图
- 左单旋的步骤如下
- 让subR的左子树作为parent的右子树
- 让parent作为subR的左子树。
- 让subR作为整个子树的根。
- 更新平衡因子。
- 左单旋后满足二叉搜索树的性质
- subRL本身就比parent大,因此可以作为parent的右子树
- parent及其左子树本身就比subR的值小,因此可以作为subR的左子树。
- 平衡因子的更新如下:
- 可以看到,经过左单旋后,树的高度变为插入之前了,即树的高度没有发生变化,所以左单旋后无需继续往上更新平衡因子。
- 这里我再说一个问题。我们的subRL子树有没有情况是为空的?
- 代码如下
//左单旋
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentParent = parent->_parent;//1、建立subR和parent之间的关系parent->_parent = subR;subR->_left = parent;//2、建立parent和subRL之间的关系parent->_right = subRL;if (subRL)subRL->_parent = parent;//3、建立parentParent和subR之间的关系if (parentParent == nullptr) //如果说parent是祖宗节点,那么他的父节点一定是空的,subR更新为祖宗节点{_root = subR;subR->_parent = nullptr; //subR的_parent指向需改变}else //如果说不是祖宗节点,那么parent一定是某棵子树的跟节点判断parent是他的父节点的左孩子还是右孩子,并更新subR。{if (parent == parentParent->_left){parentParent->_left = subR;}else //parent == parentParent->_right{parentParent->_right = subR;}subR->_parent = parentParent;}//4、更新平衡因子subR->_bf = parent->_bf = 0;
}
右单旋
-
右单旋的步骤如下:
- 让subL的右子树作为parent的左子树
- 让parent作为subL的右子树
- 让subL作为整个子树的根
- 更新平衡因子
-
右单旋后满足二叉搜索树的性质
- subL的右子树本身就比parent这颗子树要小,因此可以作为parent的左子树。
- parent及其右子树当中结点的值本身就比subL的值大,因此可以作为subL的右子树。
-
平衡因子的更新如下
-
可以看到,经过右单旋后,树的高度变为插入之前了,即树的高度没有发生变化,所以右单旋后无需继续往上更新平衡因子。
//右单旋
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;Node* parentParent = parent->_parent;//1、建立subL和parent之间的关系subL->_right = parent;parent->_parent = subL;//2、建立parent和subLR之间的关系parent->_left = subLR;if (subLR)subLR->_parent = parent;//3、建立parentParent和subL之间的关系if (parentParent == nullptr){_root = subL;_root->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subL;}else //parent == parentParent->_right{parentParent->_right = subL;}subL->_parent = parentParent;}//4、更新平衡因子subL->_bf = parent->_bf = 0;
}
左右双旋
- 左右双旋,顾名思义就是先左单旋,然后右单旋,最后需要右单旋就是因为他是左子树的高度太高了。那为啥在右单旋之前需要左单旋呢?就是因为我们左子树的高度并不是单纯的左子树高,他的子树中是右子树高了,所以需要右单旋。(综合图来看)
-
左右双旋后满足二叉搜索树的性质
1. subLR中的左子树本身就比subL要大,所以可以作为subL的右子树。
2. subLR中的右子树本身就比parent要小,所以可以作为parent的左子树
3. 经过步骤1/2后,subL中的值都比subLR的要小,parent中的值都比subLR要大,所以subL和parent分别可以作为subLR的左右子树。’ -
具体的旋转示意图如下:
-
左右双旋后,平衡因子的更新随着subLR原始平衡因子的不同分为以下三种情况:
1、当subLR原始平衡因子是-1时(其实就是新增节点插入到右子树高的那个子树的左子树中),左右双旋后parent、subL、subLR的平衡因子分别更新为1、0、0。
2、当subLR原始平衡因子是1时(其实就是新增节点插入到右子树高的那个子树(subLR)的右子树中),左右双旋后parent、subL、subLR的平衡因子分别更新为0、-1、0。
3、当subLR原始平衡因子是0时,左右双旋后parent、subL、subLR的平衡因子分别更新为0、0、0。
- 可以看到,经过左右双旋后,树的高度变为插入之前了,即树的高度没有发生变化,所以左右双旋后无需继续往上更新平衡因子。
- 左右双旋的步骤如下
- 以subL为轴左旋,之所以需要左旋以subL为根节点的子树就是因为以parent为根节点的子树的左边高度是subL这颗子树来支撑,但是subL的高度却是主要是右边高,所以需要左旋subL把subL为根的这颗子树变成单纯的左边高,parent这颗子树才能单纯的左边高。
- 以parent为旋转点进行右单旋。(这个时候parent为根节点的子树已经变成单纯的左边高)
- 更新平衡因子。
代码如下:
//左右双旋
void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf; //subLR不可能为nullptr,因为subL的平衡因子是1//1、以subL为旋转点进行左单旋RotateL(subL);//2、以parent为旋转点进行右单旋RotateR(parent);//3、更新平衡因子if (bf == 1){subLR->_bf = 0;subL->_bf = -1;parent->_bf = 0;}else if (bf == -1){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (bf == 0){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 0;}else{assert(false); //在旋转前树的平衡因子就有问题}
}
右左双旋
- 具体看图
- 我们再举一个具体的例子来说
- 右左双旋的步骤如下
- 以subR为旋转点进行右单旋(把parent为根的子树变成单纯的右边高)
- 以parent为旋转点进行左单旋 (这个时候就是单纯的右边高了,可以进行左单旋)
- 更新平衡因子
- 右左双旋后满足二叉搜索树的性质:
右左双旋后,实际上就是让subRL的左子树和右子树,分别作为parent和subR的右子树和左子树,再让parent和subR分别作为subRL的左右子树,最后让subRL作为整个子树的根(结合图理解)。
- subRL的左子树本来就比parent的值要大,可以作为parent的右子树
- subRL的右子树本来就比subR的值要小,因此可以作为subR的左子树
- 经过步骤1/2后, parent子树比subRL的值要小, subRL要比subR的值要小,所以subRL可以作为整颗树的根节点,parent和subR作为他的左子树和右子树。
右左双旋后,平衡因子的更新随着subLR原始平衡因子的不同分为以下三种情况:
- 1、当subRL原始平衡因子是1时,左右双旋后parent、subR、subRL的平衡因子分别更新为-1、0、0。
- 2、当subRL原始平衡因子是-1时,左右双旋后parent、subR、subRL的平衡因子分别更新为0、1、0。
- 3、当subRL原始平衡因子是0时,左右双旋后parent、subR、subRL的平衡因子分别更新为0、0、0。
- 可以看到,经过右左双旋后,树的高度变为插入之前了,即树的高度没有发生变化,所以右左双旋后无需继续往上更新平衡因子。
//右左双旋
void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;//1、以subR为轴进行右单旋RotateR(subR);//2、以parent为轴进行左单旋RotateL(parent);//3、更新平衡因子if (bf == 1){subRL->_bf = 0;parent->_bf = -1;subR->_bf = 0;}else if (bf == -1){subRL->_bf = 0;parent->_bf = 0;subR->_bf = 1;}else if (bf == 0){subRL->_bf = 0;parent->_bf = 0;subR->_bf = 0;}else{assert(false); //在旋转前树的平衡因子就有问题}
}
AVL树的查找
- AVL树的查找函数与二叉搜索树的查找方式一模一样,逻辑如下:
- 若树为空树,则查找失败,返回nullptr。
- 若key值小于当前结点的值,则应该在该结点的左子树当中进行查找。
- 若key值大于当前结点的值,则应该在该结点的右子树当中进行查找。
- 若key值等于当前结点的值,则查找成功,返回对应结点。
代码如下:
//查找函数
Node* Find(const K& key)
{Node* cur = _root;while (cur){if (key < cur->_kv.first) //key值小于该结点的值{cur = cur->_left; //在该结点的左子树当中查找}else if (key > cur->_kv.first) //key值大于该结点的值{cur = cur->_right; //在该结点的右子树当中查找}else //找到了目标结点{return cur; //返回该结点}}return nullptr; //查找失败
}
AVL树的验证
AVL树是在二叉搜索树的基础上加了平衡的性质,如果说我们要验证一棵树是否是AVL树。首先我们需要验证他是否是一颗二叉搜索树。
- 二叉搜索树的一个特性是中序遍历下来是升序的,所以我们可以写一个中序遍历来验证是否是二叉搜索树
//中序遍历
void Inorder()
{_Inorder(_root);
}
//中序遍历子函数
void _Inorder(Node* root)
{if (root == nullptr)return;_Inorder(root->_left);cout << root->_kv.first << " ";_Inorder(root->_right);
}
- 但中序有序只能证明是二叉搜索树,要证明二叉树是AVL树还需验证二叉树的平衡性,在该过程中我们可以顺便检查每个结点当中平衡因子是否正确
- 采用后序遍历,变量步骤如下:
- 从叶子结点处开始计算每课子树的高度。(每棵子树的高度 = 左右子树中高度的较大值 + 1)
- 先判断左子树是否是AVL树。
- 再判断右子树是否是AVL树
- 如果左右子树都是AVL树,那么把左右子树的高度返回上一层,继续判断上一层的子树是否是平衡二叉树,直到判断到根为止。(若判断过程中,某一棵子树不是平衡二叉树,则该树也就不是平衡二叉树了)
int Height(){return _Height(_root);}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(){return _IsBalanceTree(_root);}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);}
end
希望对大家有帮助,如果方便的话给博主点个赞哦,会让博主更有动力的。
相关文章:
【数据结构】手撕AVL树(万字详解)
目录 AVL树的概念为啥要有AVL树?概念 AVL树节点的定义AVL树的插入AVL树的旋转左单旋右单旋左右双旋右左双旋 AVL树的查找AVL树的验证end AVL树的概念 为啥要有AVL树? 在上一章节的二叉搜索树中,我们在插入节点的操作中。有可能一直往一边插…...
Chrome代理IP配置教程常见方式附问题解答
在网络隐私保护和跨境业务场景中,为浏览器配置代理IP已成为刚需。无论是访问地域限制内容、保障数据安全,还是管理多账号业务,掌握Chrome代理配置技巧都至关重要。本文详解三种主流代理设置方式,助你快速实现精准流量管控。 方式一…...
SpringBoot + Shiro + JWT 实现认证与授权完整方案实现
SpringBoot Shiro JWT 实现认证与授权完整方案 下面博主将详细介绍如何使用 SpringBoot 整合 Shiro 和 JWT 实现安全的认证授权系统,包含核心代码实现和最佳实践。 一、技术栈组成 技术组件- 作用版本要求SpringBoot基础框架2.7.xApache Shiro认证和授权核心1.…...
深入解析VPN技术原理:安全网络的护航者
在当今信息化迅速发展的时代,虚拟私人网络(VPN)技术成为了我们在互联网时代保护隐私和数据安全的重要工具。VPN通过为用户与网络之间建立一条加密的安全通道,确保了通讯的私密性与完整性。本文将深入解析VPN的技术原理、工作机制以…...
OceanBase 的系统变量、配置项和用户变量有何差异
在继续阅读本文之前,大家不妨先思考一下,数据库中“系统变量”、“用户变量”以及“配置项”这三者之间有何不同。如果感到有些模糊,那么本文将是您理清这些概念的好帮手。 很多用户在使用OceanBase数据库中的“配置项”和“系统变量”&#…...
ReinboT:通过强化学习增强机器人视觉-语言操控能力
25年5月来自浙大和西湖大学的论文“ReinboT: Amplifying Robot Visual-Language Manipulation with Reinforcement Learning”。 视觉-语言-动作 (VLA) 模型通过模仿学习在一般机器人决策任务中展现出巨大潜力。然而,训练数据的质量参差不齐通常会限制这些模型的性…...
MySQL联表查询:多表关联与嵌套查询指南
引言 各位数据库爱好者们好!今天我们要挑战MySQL查询技能的高阶关卡——复杂查询 🚀。在真实业务场景中,数据往往分散在多个表中,就像拼图的各个碎片,只有掌握了多表查询的"拼图技巧",才能将它们…...
QBasic 一款古老的编程语言在现代学习中的价值(附程序)
QBasic(Quick Beginner’s All-purpose Symbolic Instruction Code)是微软公司于 1991 年推出的一款简单易学的编程语言,作为BASIC语言的变种,它曾广泛应用于教育领域和初学者编程入门。尽管在当今Python、Java等现代编程语言主导…...
基于Backtrader库的均线策略实现与回测
本文将通过Python语言和强大的Backtrader库,详细介绍如何实现一个基于均线的简单交易策略,并进行历史数据的回测。将一步步构建这个策略,从数据获取、策略定义到回测结果分析,帮助你深入理解并掌握这一过程。 一、环境配置与库安装 1.1 安装必要的Python库 确保你已经安…...
Elasticsearch 分词与字段类型(keyword vs. text)面试题
Elasticsearch 分词与字段类型(keyword vs. text)面试题 🔍 目录 基础概念底层存储查询影响多字段聚合与排序分词器实战排查总结基础概念 💡 问题1:Elasticsearch 中的 keyword 和 text 类型有什么区别? 👉 查看参考答案 对比项keywordtext分词(Analysis)❌ 不进…...
Java 后端给前端传Long值,精度丢失的问题与解决
为什么后端 Long 类型 ID 要转为 String? 在前后端分离的开发中,Java 后端通常使用 Long 类型作为主键 ID(如雪花算法生成的 ID)。但如果直接将 Long 返回给前端,可能会导致前端精度丢失的问题,特别是在 J…...
【C++】 —— 笔试刷题day_29
一、排序子序列 题目解析 一个数组的连续子序列,如果这个子序列是非递增或者非递减的;这个连续的子序列就是排序子序列。 现在给定一个数组,然后然我们判断这个子序列可以划分成多少个排序子序列。 例如:1 2 3 2 2 1 可以划分成 …...
高光谱遥感图像处理之数据分类的fcm算法
基于模糊C均值聚类(FCM)的高光谱遥感图像分类MATLAB实现示例 %% FCM高光谱图像分类示例 clc; clear; close all;%% 数据加载与预处理 % 加载示例数据(此处使用公开数据集Indian Pines的简化版) load(indian_pines.mat); % 包含变…...
衡量 5G 和未来网络的安全性
大家读完觉得有帮助记得关注和点赞!!! 抽象 在当今日益互联和快节奏的数字生态系统中,移动网络(如 5G)和未来几代(如 6G)发挥着关键作用,必须被视为关键基础设施。确保其…...
【Vite】前端开发服务器的配置
定义一些开发服务器的行为和代理规则 服务器的基本配置 server: {host: true, // 监听所有网络地址port: 8081, // 使用8081端口open: true, // 启动时自动打开浏览器cors: true // 启用CORS跨域支持 } 代理配置 proxy: {/api: {target: https://…...
文章记单词 | 第85篇(六级)
一,单词释义 verb /vɜːrb/- n. 动词wave /weɪv/- v. 挥手;波动;挥舞 /n. 波浪;波;挥手add /d/- v. 增加;添加;补充说liberal /ˈlɪbərəl/- adj. 自由的;开明的;慷…...
通过实例讲解螺旋模型
目录 一、螺旋模型的核心概念 二、螺旋模型在电子商城系统开发中的应用示例 第 1 次螺旋:项目启动与风险初探...
(面试)Android各版本新特性
Android 6.0 (Marshmallow, API 23) 运行时权限管理:用户可在应用运行时动态授予或拒绝权限,取代安装时统一授权4。Doze模式与应用待机:优化后台耗电,延长设备续航5。指纹识别支持:原生API支持指纹身份验证。 Android…...
如何有效的开展接口自动化测试?
🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 一、简介 接口自动化测试是指使用自动化测试工具和脚本对软件系统中的接口进行测试的过程。其目的是在软件开发过程中,通过对接口的自动化测试来提高测…...
当 PyIceberg 和 DuckDB 遇见 AWS S3 Tables:打造 Serverless 数据湖“开源梦幻组合”
引言 在一些大数据分析场景比如电商大数据营销中,我们需要快速分析存储海量用户行为数据(如浏览、加购、下单),以进行用户行为分析,优化营销策略。传统方法依赖 Spark/Presto 集群或 Redshift 查询 S3 上的 Parquet/O…...
泰迪杯特等奖案例深度解析:基于MSER-CNN的商品图片字符检测与识别系统设计
(第四届泰迪杯数据挖掘挑战赛特等奖案例全流程拆解) 一、案例背景与核心挑战 1.1 行业痛点与场景需求 在电商平台中,商品图片常包含促销文字(如“3折起”“限时秒杀”),但部分商家采用隐蔽文字误导消费者(如“起”字极小或位于边角)。传统人工审核效率低(日均处理量…...
开发工具指南
后端运维场用工具 工具文档简介1panel安装指南运维管理面板网盘功能介绍网盘jenkins可以通过1panel 进行安装jpom辅助安装文档后端项目发布工具...
将 Element UI 表格元素导出为 Excel 文件(处理了多级表头和固定列导出的问题)
import { saveAs } from file-saver import XLSX from xlsx /*** 将 Element UI 表格元素导出为 Excel 文件* param {HTMLElement} el - 要导出的 Element UI 表格的 DOM 元素* param {string} filename - 导出的 Excel 文件的文件名(不包含扩展名)*/ ex…...
图像对比度调整(局域拉普拉斯滤波)
一、背景介绍 之前刷对比度相关调整算法,找到效果不错,使用局域拉普拉斯做图像对比度调整,尝试复现和整理了下相关代码。 二、实现流程 1、基本原理 对输入图像进行高斯金字塔拆分,对每层的每个像素都针对性处理,生产…...
【控制波形如何COPY并无痛使用】
控制波形如何COPY并无痛使用 波形分析思路概况记录波形 波形分析 通过逻辑分析仪可以解析到设备的控制波形,在一些对于电机控制类的设备上显得尤为重要。通过分析不同波形,将PWM的波形存储到程序中得以实现,并建立合理的数据结构。 思路概…...
CSDN-2024《AGP-Net: Adaptive Graph Prior Network for Image Denoising》
推荐深蓝学院的《深度神经网络加速:cuDNN 与 TensorRT》,课程面向就业,细致讲解CUDA运算的理论支撑与实践,学完可以系统化掌握CUDA基础编程知识以及TensorRT实战,并且能够利用GPU开发高性能、高并发的软件系统…...
使用IDEA开发Spark Maven应用程序【超详细教程】
一、创建项目 创建maven项目 二、修改pom.xml文件 创建好项目后,在pom.xml中改为: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w…...
深入探索MCP通信:构建高效的MCP Client
在现代软件开发中,高效的通信机制是构建复杂系统的关键。MCP(Model-Controller-Proxy)架构作为一种新兴的开发模式,提供了强大的工具来实现客户端与服务器之间的高效通信。本文将通过实际代码示例,详细探讨如何使用MCP…...
【第76例】IPD流程实战:华为业务流程架构BPA进化的4个阶段
目录 简介 第一个阶段,业务流程架构BPA1.0 第二个阶段,业务流程架构BPA2.0 BPA3.0、4.0 作者简介 简介 不管业务是复杂还是简单,企业内外的所有事情、所有业务都最终会归于流程。 甚至是大家经常说的所谓的各种方法论,具体的落脚点还是在流程上。 比如把大象放进冰…...
25-05-16计算机网络学习笔记Day1
深入剖析计算机网络:今日学习笔记总结 本系列博客源自作者在大二期末复习计算机网络时所记录笔记,看的视频资料是B站湖科大教书匠的计算机网络微课堂,每篇博客结尾附书写笔记(字丑见谅哈哈) 视频链接地址 一、计算机网络基础概念 …...
车道线检测----CLRNet
继续更新本系列,本文CLRNet,文章主要目的是弄懂论文关键部分,希望对文章细节有一个深刻的理解,有帮助的话,请收藏支持。 CLRNet:用于车道检测的跨层精炼网络 摘要 车道在智能车辆的视觉导航系统中至关重要…...
maven和npm区别是什么
这是一个很容易搞糊涂新手的问题,反正我刚开始从课堂的知识转向项目网站开发时,被这些问题弄得晕头转向,摸不着头脑,学的糊里糊涂,所以,写了这么久代码,也总结一下,为后来者传授下经…...
【SpringBoot】从零开始全面解析SpringMVC (二)
本篇博客给大家带来的是SpringBoot的知识点, 本篇是SpringBoot入门, 介绍SpringMVC相关知识. 🐎文章专栏: JavaEE进阶 🚀若有问题 评论区见 👉gitee链接: 薯条不要番茄酱 ❤ 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条…...
Python连接redis
第一步安装redis Releases microsoftarchive/redis 安装时勾上所有能勾上的选项下一步即可 在CMD中pip install redis 安装redis pip install redis -i https://pypi.tuna.tsinghua.edu.cn/simple 配置redis 在redis安装目录下找到 修改 line 57 bind 0.0.0.0 line…...
Unity3D Overdraw性能优化详解
前端 在 Unity3D 开发中,Overdraw(过度绘制) 是一个常见的性能问题,尤其在移动端设备上可能导致严重的帧率下降。以下是关于 Overdraw 的详细解析和优化方法: 对惹,这里有一个游戏开发交流小组࿰…...
uni-app 中适配 App 平台
文章目录 前言✅ 1. App 使用的 Runtime 架构:**WebView 原生容器(plus runtime)**📌 技术栈核心: ✅ 2. WebView Native 的通信机制详解(JSBridge)📤 Web → Native 调用…...
[CSS3]属性增强1
字体图标 使用字体图标可以实现简洁的图标效果, 字体图标展示的是图标, 本质是字体, 适合简单, 颜色单一的图标 优势 灵活性: 灵活的修改样式, 比如尺寸, 颜色等轻量级: 体积小, 渲染快, 降低服务器请求次数兼容性: 几乎兼容所有主流浏览器使用方便: 下载字体包使用字体图标…...
【Python+flask+mysql】网易云数据可视化分析(全网首发)
网易云数据可视化分析 项目概述 网易云数据可视化分析系统是一个基于Flask框架开发的Web应用,旨在对网易云音乐平台的用户、歌曲、专辑、歌单等数据进行全面的可视化分析。该系统通过直观的图表、表格和词云等形式,展示网易云音乐的数据分布特征&#…...
项目版本管理和Git分支管理方案
文章目录 一、团队协作1.项目团队与职责2.项目时间线与里程碑3.风险评估与应对措施4.跨团队同步会议(定期)跨团队同步会议(双周) 5.版本升级决策树6.边界明确与路标制定a.功能边界划分b.项目路标制定b1、项目路标制定核心要素b2. 路标表格模板…...
Java 21 + Spring Boot 3.5:AI驱动的高性能框架实战
简介 在微服务架构日益普及的今天,如何构建一个既高性能又具备AI驱动能力的后端系统成为开发者关注的焦点。本篇文章将深入探讨Java 21与Spring Boot 3.5的结合,展示如何通过Vector API和JIT优化实现单线程性能提升30%,并利用飞算JavaAI生成智能重试机制和超时控制代码,解…...
【MySQL】索引太多会怎样?
在 MySQL 中,虽然索引可以显著提高查询效率,但过多的索引(如超过 5-6 个)会带来以下弊端: 1. 存储空间占用增加 每个索引都需要额外的磁盘空间存储索引树(BTree)。对于大表来说,多个…...
Flask 是否使用类似 Spring Boot 的核心注解机制
Flask 和 Spring Boot 架构风格不同:Spring Boot 是“注解驱动的全家桶框架”,而 Flask 是“微核心 + 显式扩展的 Python 微框架”。因此: ❌ Flask 没有类似 Spring Boot 的“核心注解机制”(如 @SpringBootApplication),而是使用函数装饰器(decorator)作为核心语法特…...
学习threejs,使用Physijs物理引擎,各种constraint约束限制
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️Physijs 物理引擎1.1.1 ☘️…...
城市排水管网流量监测系统解决方案
一、方案背景 随着工业的不断发展和城市人口的急剧增加,工业废水和城市污水的排放量也大量增加。目前,我国已成为世界上污水排放量大、增加速度快的国家之一。然而,总体而言污水处理能力较低,有相当部分未经处理的污水直接或间接排…...
redis数据结构-11(了解 Redis 持久性选项:RDB 和 AOF)
了解 Redis 持久性选项:RDB 和 AOF Redis 提供了多个持久性选项,以确保数据持久性并防止在服务器发生故障或重启时丢失数据。了解这些选项对于为您的特定使用案例选择正确的策略、平衡性能和数据安全至关重要。本章节将深入探讨 Redis 中的两种主要持久…...
掌握 Kotlin Android 单元测试:MockK 框架深度实践指南
掌握 Kotlin Android 单元测试:MockK 框架深度实践指南 在 Android 开发中,单元测试是保障代码质量的核心手段。但面对复杂的依赖关系和 Kotlin 语言特性,传统 Mock 框架常显得力不从心。本文将带你深入 MockK —— 一款专为 Kotlin 设计的 …...
2025/5/16
第一题 A. 例题4.1.2 潜水 题目描述 在马其顿王国的ohide湖里举行了一次潜水比赛。 其中一个项目是从高山上跳下水,再潜水达到终点。 这是一个团体项目,一支队伍由n人组成。在潜水时必须使用氧气瓶,但是每只队伍只有一个氧气瓶。 最多两…...
Detected for tasks ‘compileDebugJavaWithJavac‘ (17) and ‘kspDebugKotlin‘ (21).
1.报错 在导入Android源码的时候出现以下错误:Inconsistent JVM-target compatibility detected for tasks compileDebugJavaWithJavac (17) and kspDebugKotlin (21).。 Execution failed for task :feature-repository:kspDebugKotlin. > Inconsistent JVM-ta…...
嵌入式单片机中STM32F1演示寄存器控制方法
该文以STM32F103C8T6为示例,演示如何使用操作寄存器的方法点亮(关闭LED灯),并讲解了如何调试,以及使用宏定义。 第一:操作寄存器点亮LED灯。 (1)首先我们的目的是操作板子上的LED2灯,对其实现点亮和关闭操作。打开STM32F103C8T6的原理图,找到LED2的位置。 可以看到…...
【带文档】网上点餐系统 springboot + vue 全栈项目实战(源码+数据库+万字说明文档)
📌 一、项目概括 本系统共包含三个角色: 管理员:系统运营管理者 用户:点餐消费用户 美食店:上传菜品与处理订单的店铺账号 通过对这三类角色的权限与业务分工设计,系统实现了点餐流程的全链路数字化&a…...