当前位置: 首页 > news >正文

高阶数据结构——AVL树的实现(详细解答)

目录

1.AVL的概念

2.AVL树的实现

2.1 AVL树的插入

2.1.1 平衡因子的更新

2.1.2 AVL树的插入

2.2 旋转

2.2.1 旋转的原则

2.2.2 右单旋

2.2.3 左单旋

2.2.4 先左后右双旋转

2.2.5 先右后左双旋转(先左后右双旋转模型的镜像)

2.2.6 代码总结

2.3 旋转总结

2.4 AVL树的查找

 2.5 AVL树平衡检测


1.AVL的概念

AVL树是最先发明的自平衡二叉查找树,AVL是⼀颗空树,或者具备下列性质的二叉搜索树:它的 左右子树都是AVL树,且左右子树的高度差的绝对值不超过1。AVL树是⼀颗高度平衡搜索二叉树, 通过控制高度差去控制平衡。

AVL树得名于它的发明者G.M.Adelson-Velsky和E.M.Landis是两个前苏联的科学家,他们在1962 年的论文《Analgorithmfortheorganizationofinformation》中发表了它。

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

而如果说这个某个节点的平衡因子不是1/-1/0这三个中的一个,说明这个二叉树就不平衡了,需要进一步的将它调节平衡。那么如何调节平衡呢?咱们接下来会讲解的。

AVL树整体结点数量和分布和完全二叉树类似,高度可以控制在logN ,那么增删查改的效率也可 以控制在O(logN) ,相比二叉搜索树有了本质的提升。

2.AVL树的实现

先来看一下大体的AVL树的结构:

template<class k, class v>
struct AVLTreeNode
{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;
};

还是老样子,使用模板来AVL树的节点结构以及AVL树的结构。这里还得多定义一个东西,那就是平衡因子(风向标)。

2.1 AVL树的插入

在讲解插入之前,咱们先来讲解一下平衡因子的更新:

2.1.1 平衡因子的更新

更新原则:

平衡因子=右子树高度-左子树高度

• 只有子树高度变化才会影响当前结点平衡因子。

• 插入结点,会增加高度,所以新增结点在parent的右子树,parent的平衡因子++,新增结点在 parent的左子树,parent平衡因子--

• parent所在子树的高度是否变化决定了是否会继续往上更新。

更新停止条件:

• 更新后parent的平衡因子等于0,更新中parent的平衡因子变化为-1->0或者1->0,说明更新前 parent子树⼀边高⼀边低,新增的结点插入在低的那边,插入后parent所在的子树高度不变,不会 影响parent的父亲结点的平衡因子,更新结束。

• 更新后parent的平衡因子等于1或-1,更新前更新中parent的平衡因子变化为0->1或者0->-1,说 明更新前parent子树两边⼀样高,新增的插入结点后,parent所在的子树⼀边高⼀边低,parent所 在的子树符合平衡要求,但是高度增加了1,会影响parent的父亲结点的平衡因子,所以要继续向 上更新。(这里需要说明的是,你插入节点,不管插入parent的左子树,还是插入在parent的右子树,你只影响了parent的bf,但是对于parent的父节点的bf,不管你插入在parent的左边还是右边,都是一样的结果)。

• 更新后parent的平衡因子等于2或-2,更新前更新中parent的平衡因子变化为1->2或者-1->-2,说 明更新前parent子树⼀边高⼀边低,新增的插入结点在高的那边,parent所在的子树高的那边更⾼ 了,破坏了平衡,parent所在的子树不符合平衡要求,需要旋转处理,旋转的目标有两个:1、把 parent子树旋转平衡。2、降低parent子树的高度,恢复到插入结点以前的⾼度。所以旋转后也不 需要继续往上更新,插入结束。

• 不断更新,更新到根,跟的平衡因子是1或-1也停止了。

最坏更新到根节点为止。

2.1.2 AVL树的插入

1. 插入⼀个值按二叉搜索树规则进行插入。

2. 新增结点以后,只会影响祖先结点的高度,也就是可能会影响部分祖先结点的平衡因子,所以更新 从新增结点->根结点路径上的平衡因子,实际中最坏情况下要更新到根,有些情况更新到中间就可 以停止了,具体情况我们下面再详细分析。

3. 更新平衡因子过程中没有出现问题,则插入结束

4. 更新平衡因子过程中出现不平衡,对不平衡子树旋转,旋转后本质调平衡的同时,本质降低了子树 的高度,不会再影响上⼀层,所以插入结束。

这段话中,有一句:从发生不平衡的节点起,沿刚才回溯的路径取那个不平衡节点的下面两层的节点,从而进行判定。这个咱们马上就要讲了。

先看一下,插入部分,以及更新bf部分的代码:

bool Insert(const pair<k, v>& kv)
{if (_root == nullptr){Node* kk = new Node(kv);_root = kk;return true;}else{Node* parent = nullptr;Node* cur = _root;//循环找要插入的cur节点的位置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 if(parent->_kv.first>kv.first){parent->_left = cur;}cur->_parent = parent;//最后别忘了更新cur的父节点//这儿只是插入,一个一个的找应该插入的位置,虽然这里parent为倒第二个高度位置//cur为倒第一个高度位置,但是不慌,下面的更新bf,会找出不符合的parent//从而实现更新parent,所以现在不需要担心parent的位置//更新bfwhile (parent){//先对插入的节点的位置的分析,从而判断应该加bf,还是减去bfif (cur == parent->_left){parent->_bf--;}else if (cur == parent->_right){parent->_bf++;}//现在进行更新parent位置的更新,看看哪个地方不符合bf的数值if (parent->_bf == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = parent->_parent;//继续去找不符合bf数值的parent的位置}else if(parent->_bf==2||parent->_bf==-2){//现在问题有点棘手,需要进行旋转平衡二叉树}else{assert(false);//总共就这几种情况,如果说parent->_bf不在这几种情况中//大概率是出错了,直接断言报错即可。}}}return true;
}

这就是插入部分的大体的代码,那么有一些注意事项,我也全部放在了代码中,大家自行阅读。

2.2 旋转

2.2.1 旋转的原则

1. 保持搜索树的规则

2. 让旋转的树从不满足变平衡,其次降低旋转树的高度 旋转总共分为四种,左单旋/右单旋/左右双旋/右左双旋。 说明:下面的图中,有些结点我们给的是具体值,如10和5等结点,这里是为了方便讲解,实际中是什 么值都可以,只要大小关系符合搜索树的性质即可。 

2.2.2 右单旋

OK,咱们先来讲右单旋,右单旋,这个右,顾名思义,就是左边太高了(这个高,指的是树的高度太高了,大家可以理解为深,所以为了方便大家理解,在这里,树的高,我称为深,而对于视觉上的高,我称为高)。那么右边太高了,咱们是不是得把右边的往下压一压,这样才可以实现右单旋。

• 本图展示的是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为根的树左边太高了,需要 往右边旋转,控制两棵树的平衡

• 旋转核心步骤,因为5<b子树的值<10,将b变成10的左子树,10变成5的右子树,5变成这棵树新的根(以5为旋转轴,让10顺时针旋转成为5的右子树),符合搜索树的规则,控制了平衡,同时这棵的⾼度恢复到了插入之前的h+2,符合旋转原则。如果插入之前10整棵树的⼀个局部子树,旋转后不会再影响上⼀层,插入结束了。

那么咱们来看插入的代码:

//右单旋
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;//只要定义需要移动的的节点即可parent->_left = subLR;if (subLR)//如果该节点为空,那么就不需要连接了,也就是不需要更新父节点的位置了。{subLR->_parent = parent;}//这里得先提前记录一下原来的没动位置之前的parent的父节点,因为到了后面//parent的位置别修改了,则这个parentParent就找不到了Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;//父节点的位置也别忘了更新if (parent==_root)//判断parent是否为根节点{subL = _root;subL->_parent = nullptr;}else{if (parent == parentParent->_left)//如果parent不是根节点,就//判断一下原来parent是parentParent的左边还是右边,之后再连接。{parentParent->_left = subL;}else if (parent == parentParent->_right){parentParent->_right = subL;}subL->_parent = parentParent;//别忘了更新父节点的位置}parent->_bf = subL->_bf = 0;//最后别忘了更新parent与sunL的bf。
}

代码请配套上面的那个图片看,以及,咱们可以发现:这个右单旋parent与subL是不是同号呀。 

OK,下面来说另外一个问题,咱们图中的a,b,c,是啥呀?是一种抽象的表述。就是,这个a里面可能还有很多的自平衡二叉树,b里可能也有很多,c里可能也有很多。但是呢,不管你有多少的这个自平衡二叉树,只要你的某个节点的bf不对,那么你都要修改,而且修改模型,(就是修改的模型办法),都是这一种情况。所以,也可以理解为对底层的封装。下面的节点是怎样的,我不关心,因为不管下面的节点是多是少,就算没有,在插入节点后引发了某个节点的bf异常,我还是按这个方法进行旋转控制平衡就可以了。

那么既然都说到这里了,就来看一下,a,b,c的各个不同的情况的总结吧。

 

 

以上就是h等于0,1,2的场景的a,b,c的不同形态,你会发现,殊途同归,最终,还是按照那一种方法进行旋转的。 

2.2.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为根的树右边太高了,需要往 左边旋转,控制两棵树的平衡。

• 旋转核心步骤,,因为10<b子树的值<15,将b变成10的右子树,10变成15的左子树,15变成这棵树新的根(以15为旋转轴,让10逆时针方向旋转成为15的左子树),符合搜索树的规则,控制了平衡,同时这棵的高度恢复到了插入之前的h+2,符合旋转原则。如果插入之前10整棵树的⼀个局部子树,旋转后不会再影响上一层,插入结束了。

OK,其实左单旋的模型就是右单旋模型的一个镜像,这里也有抽象表示,但是其意义与右单旋的模型一致,前面已经讲过了。那么,咱们来看代码:

//左单旋
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL){subRL->_parent = parent;}Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parent == _root){subR = _root;subR->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subR;}else if (parent == parentParent->_right){parentParent->_right = subR;}subR->_parent = parentParent;}subR->_bf = parent->_bf = 0;
}

这个代码与右单旋很像很像,所以也不多赘述,还有就是,这个的parent与subR是不是也是同号呀。

2.2.4 先左后右双旋转

 

先观察这两个图片,先看第一种,看着熟悉不,没错,就是左边深,(右单旋),但是!,咱们的右单旋的模型的插入,是插入在哪里的呀?是插入在a位置的,就是纯粹的一边深(左边深),但是这个不是,这个图是插入在了b这个位置,已经不是纯粹的左边深了,属于左边深的右边深!那么对于这种情况,咱们如果说还用纯粹的一个右单旋来解决的话,你看看可以不?肯定不可以!所以,需要进行两次旋转。即对于5来说,右边深,需要进行左单旋,对于10来说,左边深,需要进行右单旋。那么此时,咱们的问题就解决了不是吗?

OK,咱们先来看图片,听我一点一点的细细的分析:

 

 我们将a/b/c子树抽象为高度h的AVL 子树进行分析,另外我们需要把b子树的细节进⼀步展开为8和左子树高度为h-1的e和f子树,因为 我们要对b的父亲5为旋转点进行左单旋,左单旋需要动b树中的左子树。b子树中新增结点的位置 不同,平衡因子更新的细节也不同,通过观察8的平衡因子不同,这里我们要分三个场景讨论。

• 场景1:h>=1时,8的bf为-1,新增结点插⼊在e子树,e子树高度从h-1并为h并不断更新8->5->10平衡因子, 引发旋转,其中8的平衡因子为-1,旋转后8和5平衡因子为0,10平衡因子为1。

• 场景2:h>=1时,8的bf为1,新增结点插⼊在f子树,f子树高度从h-1变为h并不断更新8->5->10平衡因子,引 发旋转,其中8的平衡因子为1,旋转后8和10平衡因⼦为0,5平衡因子为-1。

• 场景3:h==0时,8的bf为0,a/b/c都是空树,b自己就是⼀个新增结点,不断更新5->10平衡因子,引发旋 转,其中8的平衡因子为0,旋转后8和10和5平衡因子均为0。

旋转也是挺好旋转的,其实就行进行两个单个的旋转而已:现以8为旋转轴,以逆时针顺序将5旋转到8的左子树。后以8为旋转轴,以顺时针顺序将10旋转到8的右子树。

大家可以发现,这个的parent与subR是不是异号呀。

还有就是,这个先左后右旋转模型,不就是右单旋模型的插入地方改了一下嘛?

OK,咱们来看代码吧:

//左右双旋转,即左子树中的右子树,所以先旋转深的左子树,之后旋转深的右边的
void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;//定义这个的目的是,通过判断这个bf,判断这个插入,插在了哪里,是左边//还是右边,还是没有插入,分三种情况写出RotateL(subL);RotateR(parent);if (bf == -1)//左边插入{parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else if (bf == 1)//右边插入{parent->_bf = 0;subLR->_bf = 0;subL->_bf = -1;}else if (bf == 0)//没有插入{parent->_bf = subL->_bf = subLR->_bf = 0;}else{assert(false);//最后bf的情况以上都没有,肯定是false了,直接断言一下即可}
}

2.2.5 先右后左双旋转(先左后右双旋转模型的镜像)

• 跟左右双旋类似,下面我们将a/b/c子树抽象为高度h的AVL子树进行分析,另外我们需要把b子树的 细节进⼀步展开为12和左子树⾼度为h-1的e和f子树,因为我们要对b的父亲15为旋转点进行右单 旋,右单旋需要动b树中的右子树(那么就需要知道b树的具体结构)。b子树中新增结点的位置不同,平衡因子更新的细节也不同,通 过观察12的平衡因子不同,这⾥我们要分三个场景讨论。

• 场景1:h>=1时,12的bf为-1,新增结点插入在e子树,e子树高度从h-1变为h并不断更新12->15->10平衡因 子,引发旋转,其中12的平衡因子为-1,旋转后10和12平衡因子为0,15平衡因子为1。

• 场景2:h>=1时,12的bf为1,新增结点插入在f子树,f子树⾼度从h-1变为h并不断更新12->15->10平衡因子, 引发旋转,其中12的平衡因子为1,旋转后15和12平衡因子为0,10平衡因子为-1。

• 场景3:h==0时,12的bf为0,a/b/c都是空树,b自己就是⼀个新增结点,不断更新15->10平衡因子,引发旋 转,其中12的平衡因子为0,旋转后10和12和15平衡因子均为0 。

那么大家看着这个模型熟悉不?肯定熟悉,咱们的左单旋不就是这个模型吗?但是!但是!不同!,因为左单旋的模型的插入是插入在a的,就是纯粹的一边深(右边深),但是这个是插在了b上,就是右边深的左边深,所以要先进行右单旋,再进行左单旋,其实就是两次简单的旋转而已。

先以12为旋转轴,用顺时针的方向将15旋转成为12的右子树。再以12为旋转轴,用逆时针的方向将10旋转成为12的左子树。

//右左双旋
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 = 0;parent->_bf = -1;subRL->_bf = 0;}else if (bf == -1){subR->_bf = 1;parent->_bf = 0;subRL->_bf = 0;}else if (bf == 0){subR->_bf = 0;parent->_bf = 0;subRL->_bf = 0;}else{assert(false);}
}

那么通过这个也可以发现,其实,parent与subR是异号的。

2.2.6 代码总结

OK,那么来说,bf不正常阶段的代码咱们已经全部实现,现在就放到那个if判断语句中,下面就看全部的插入代码吧:

bool Insert(const pair<k, v>& kv)
{if (_root == nullptr){Node* kk = new Node(kv);_root = kk;return true;}else{Node* parent = nullptr;Node* cur = _root;//循环找要插入的cur节点的位置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 if(parent->_kv.first>kv.first){parent->_left = cur;}cur->_parent = parent;//最后别忘了更新cur的父节点//这儿只是插入,一个一个的找应该插入的位置,虽然这里parent为倒第二个高度位置//cur为倒第一个高度位置,但是不慌,下面的更新bf,会找出不符合的parent//从而实现更新parent,所以现在不需要担心parent的位置//更新bfwhile (parent){//先对插入的节点的位置的分析,从而判断应该加bf,还是减去bfif (cur == parent->_left){parent->_bf--;}else if (cur == parent->_right){parent->_bf++;}//现在进行更新parent位置的更新,看看哪个地方不符合bf的数值if (parent->_bf == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = parent->_parent;//继续去找不符合bf数值的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);}break;}else{assert(false);//总共就这几种情况,如果说parent->_bf不在这几种情况中//大概率是出错了,直接断言报错即可。}}}return true;
}

下面来看一副从空树开始建立的自平衡二叉树的过程吧:

 

2.3 旋转总结

其实,旋转总共就4中模型,并且,殊途同归,不管下面的节点怎样,始终都是这四种模型。

1.先左后右双旋转模型就是右单旋模型的插入地方改了一下。先右后左双旋转模型就是左单旋模型的插入地方改了一下。 

 2.如果parent的bf为2,说明右子树深,左边高:

2.1 如果subR的bf为1,(同号)说明插入的节点插入在了subR的右边,就是纯粹的一边深了,只需要调用右单旋即可。

2.2 如果subR的bf为-1,(异号)说明插入的节点插入在了subR的左边,那就不是纯粹的一边深了,属于右边深的左边深,应该先进行右单旋,再进行左单旋。

 3.如果parent的bf为-2,说明左子树深,右边高:

2.1 如果subL的bf为1,(异号)说明插入的节点插入在了subL的右边,那就不是纯粹的一边深了,属于左边深的右边深,应该先进行左单旋,再进行右单旋。

2.2 如果subL的bf为-1,(同号)说明插入的节点插入在了subL的左边,那就是纯粹的一边深,只需要进行一个左单旋即可。

2.4 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;}
//搜索效率为O(logN)

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

还有一个AVL树的删除,在这里作者就不做过多的阐述了,大家有兴趣的可以自行去研究。

OK,本篇完.................. 

 

相关文章:

高阶数据结构——AVL树的实现(详细解答)

目录 1.AVL的概念 2.AVL树的实现 2.1 AVL树的插入 2.1.1 平衡因子的更新 2.1.2 AVL树的插入 2.2 旋转 2.2.1 旋转的原则 2.2.2 右单旋 2.2.3 左单旋 2.2.4 先左后右双旋转 2.2.5 先右后左双旋转&#xff08;先左后右双旋转模型的镜像&#xff09; 2.2.6 代码总结 2…...

工作流引擎-01-Activiti 是领先的轻量级、以 Java 为中心的开源 BPMN 引擎,支持现实世界的流程自动化需求

前言 大家好&#xff0c;我是老马。 最近想设计一款审批系统&#xff0c;于是了解一下关于流程引擎的知识。 下面是一些的流程引擎相关资料。 工作流引擎系列 工作流引擎-00-流程引擎概览 工作流引擎-01-Activiti 是领先的轻量级、以 Java 为中心的开源 BPMN 引擎&#x…...

自定义geojson生成物体的样式

在上节我们学习了如何在cesium中导入geojson数据,本节我们来学习如何让它变得更加炫酷. // 加载GeoJSON数据 // 使用Cesium的GeoJsonDataSource加载指定URL的地理数据 Cesium.GeoJsonDataSource.load("https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json&quo…...

在tensorflow源码环境里,编译出独立的jni.so,避免依赖libtensorflowlite.so,从而实现apk体积最小化

需要在APP里使用tensorflow lite来运行PC端训练的model.tlite&#xff0c;又想apk的体积最小&#xff0c;尝试了如下方法&#xff1a; 1. 在gradle里配置 implementation("org.tensorflow:tensorflow-lite:2.16.1") 这样会引入tensorflow.jar&#xff0c;最终apk的…...

springboot框架 集成海康ISUP-SDK 并实现 协议透传给设备下发指令!

最近有一个需求 需要通过springboot框架 来和 海康的摄像头设备进行通信&#xff0c;就研究了一下 海康的官方ISUP-SDK 文档对接。这个sdk 主要实现了 第三方快速集成海康的设备。 海康的文档地址&#xff1a; https://open.hikvision.com/docs/docId?productId5cda567cf47ae8…...

【移动应用安全】Android系统安全与保护机制

Android系统安全与保护机制是一个多层次、多维度的防御体系&#xff0c;其安全架构与系统层级紧密耦合。以下是对各层级安全机制的扩展分析&#xff1a; Linux内核层&#xff08;Linux Kernel&#xff09;安全机制 强制访问控制&#xff08;MAC&#xff09; 通过SELinux&#…...

Spring Boot中如何使用RabbitMQ?

前面已经了解了怎么使用RabbitMQ的JDK原生客户端&#xff0c;现在我们来了解Spring Boot中如何使用RabbitMQ&#xff0c;在学习之前&#xff0c;先做好准备工作&#xff1a; 1. 添加依赖 在Spring Boot中使用RabbitMQ&#xff0c;需要使用如下依赖&#xff1a; <dependenc…...

kotlin 将一个list按条件分为两个list(partition )

前言 在安卓开发过程中&#xff0c;我们经常需要将一个列表按照特定条件拆分为两个子列表。随着对语言的深入理解&#xff0c;我发现了一些更高效、更简洁的实现方式&#xff0c;现在与大家分享。 实现 传统Java实现 假设我们有以下列表需要处理&#xff1a; List<Stri…...

R语言学习--Day04--数据分析技巧

在清洗完数据&#xff0c;在对数据分析前&#xff0c;我们要懂得先梳理一下我们的逻辑&#xff0c;即数据是什么形式的&#xff0c;要进行哪种分析&#xff0c;有可能呈现什么特点&#xff0c;进而再想怎么处理数据去画图可以最大程度地凸显我们要的特点。 一般来讲&#xff0…...

企业终端设备的安全管控

企业终端设备的安全管控是信息安全体系中的重要环节&#xff0c;涉及从设备准入到数据防护的全生命周期管理。 以下是一套系统化的解决方案&#xff0c;涵盖技术、管理和人员三个维度&#xff1a; 一、终端设备全生命周期管控 设备准入控制 802.1X网络认证&#xff1a;对接企业…...

桥梁凝冰在线监测装置:科技守护道路安全的新防线

在交通基础设施安全领域&#xff0c;桥梁凝冰问题始终是冬季道路管理的重点挑战。传统人工巡检方式存在时效性差、覆盖范围有限等缺陷&#xff0c;而桥梁凝冰在线监测装置的普及应用&#xff0c;正为这一难题提供智能化解决方案。 一、装置工作原理 桥梁凝冰在线监测装置通过多…...

【开源】一个基于 Vue3 和 Electron 开发的第三方网易云音乐客户端,具有与官方客户端相似的界面布局

&#x1f3b5; XCMusic&#xff1a;高颜值第三方网易云音乐客户端 &#x1f3b6; &#x1f4cd; 项目亮点 XCMusic 是一款基于Vue3Electron开发的开源、跨平台网易云音乐客户端。 此音乐播放器基于 Electron 开发&#xff0c;旨在为用户提供简洁、美观、兼容多平台的音乐体验。…...

Android 中拖拽从一个组件到另外一个组件的写法(跨容器拖拽)

在 Android 中&#xff0c;拖拽一个图片&#xff08;例如 ImageView&#xff09;到另一个组件&#xff08;如 LinearLayout、FrameLayout 等容器&#xff09;涉及以下步骤&#xff1a; 准备工作 源组件&#xff1a;你从哪里开始拖动&#xff08;如 ImageView&#xff09;。 目…...

MATLAB实现GAN用于图像分类

生成对抗网络&#xff08;GAN&#xff09;是一种强大的生成模型&#xff0c;由生成器&#xff08;Generator&#xff09;和判别器&#xff08;Discriminator&#xff09;组成。生成器用于生成图像&#xff0c;判别器用于判断图像是真实的还是生成的。在MATLAB中实现GAN用于图像…...

武汉副市长李湛莅临指导 珈和展会精彩亮相引《武汉电视台》深度报道 以硬核科技赋能农业强链新范式获政府媒体“双重点赞”

为充分响应“双循环”新发展格局&#xff0c;深化区域产业协作、推动供需精准对接&#xff0c;进一步促进经济高质量发展&#xff0c;5月16日-18日&#xff0c;由武汉市经济和信息化局主办的2025年产业链供需对接&#xff08;绍兴&#xff09;推广活动在绍兴国际会展中心举办。…...

matlab慕课学习3.4

于20250319 3.4用for语句实现循环结构 3.4.1什么是循环结构 循环结构又称重复结构&#xff0c;是利用计算机运算速度快以及能进行逻辑控制的特点来重复执行某些操作。 3.4.2for语句 for 循环变量表达式1&#xff1a;表达式2&#xff1a;表达式3 循环体语句 end 说明&…...

matlab编写的BM3D图像去噪方法

BM3D&#xff08;Block-Matching and 3D Filtering&#xff09;是一种基于块匹配和三维滤波的图像去噪方法&#xff0c;广泛应用于图像处理领域。它通过在图像中寻找相似的块&#xff0c;并将这些块堆叠成三维数组进行滤波处理&#xff0c;从而有效地去除噪声&#xff0c;同时保…...

当科技邂逅浪漫:在Codigger的世界里,遇见“爱”

520&#xff0c;一个充满爱意的日子&#xff0c;人们用各种方式表达对彼此的深情。而在科技的世界里&#xff0c;我们也正经历着一场特别的邂逅——Codigger&#xff0c;一个分布式操作系统的诞生&#xff0c;正在以它独特的方式&#xff0c;重新定义我们与技术的关系。 Codigg…...

深入理解 Python 中的几种方法:实例方法、类方法、静态方法与特殊方法

前置阅读&#xff0c;了解什么是类属性、实例属性&#xff0c;对于理解类方法、实例方法会有帮助&#xff1a;Python 中的类属性与实例属性详解 0、总体介绍 在 Python 中&#xff0c;方法&#xff08;method&#xff09; 是定义在类&#xff08;class&#xff09;内部的函数&…...

游戏开发实战(二):Python复刻「崩坏星穹铁道」嗷呜嗷呜事务所---源码级解析该小游戏背后的算法与设计模式【纯原创】

文章目录 奇美拉和队列奇美拉被动技能多对多观察者关系实现自定义元类奇美拉基类 管理奇美拉的队列奇美拉队列类心得体会扩展 规则定义工作相关奇美拉相关 奇美拉属性 在本篇博文&#xff0c;我将介绍本项目的整体框架&#xff0c;以及“编码规则”&#xff0c;这些规则保证了本…...

Python实战:打造一个功能完整的单位转换器(长度/温度/货币)

&#x1f4da; 文章导读 在本文中&#xff0c;我将为大家介绍如何使用Python开发一个实用的单位转换器。这个项目不仅适合Python初学者练手&#xff0c;也能帮助你更好地理解Python的基础语法和函数设计。 &#x1f50d; 主要特性 ✅ 支持多种长度单位互转&#xff08;米、千…...

嵌入式学习笔记 D24 :系统编程之i/o操作

系统编程基本概念及一般组成文件的常见i/o操作 一、系统编程基本概念及一般组成 系统编程属于应用程序编程&#xff0c;即在操作系统运行成功的基础上执行程序。其一般包含以下四部分&#xff1a; 1&#xff09;文件&#xff1a;存储在存储设备上的相关信息集合&#xff0c;是…...

利用朴素贝叶斯对UCI 的 mushroom 数据集进行分类

朴素贝叶斯&#xff08;Naive Bayes&#xff09;是一种基于贝叶斯定理的简单而有效的分类算法&#xff0c;特别适合处理文本分类和多类别分类问题。UCI的Mushroom数据集是一个经典的分类数据集&#xff0c;包含蘑菇的特征和类别&#xff08;可食用或有毒&#xff09;。 1. 数据…...

Index-AniSora模型论文速读:基于人工反馈的动漫视频生成

Aligning Anime Video Generation with Human Feedback 一、引言 论文开头指出&#xff0c;尽管视频生成模型不断涌现&#xff0c;但动漫视频生成面临动漫数据稀缺和运动模式异常的挑战&#xff0c;导致生成视频存在运动失真和闪烁伪影等问题&#xff0c;难以满足人类偏好。现…...

FineBI 和 Axure工具比较——数据分析VS原型设计

FineBI和Axure是两款定位截然不同的工具&#xff0c;分别服务于数据分析和原型设计领域。以下从核心功能、应用场景、操作门槛等维度进行对比分析&#xff1a; 一、核心功能对比 FineBI 作为商业智能&#xff08;BI&#xff09;工具&#xff0c;聚焦于数据整合、清洗、分析及可…...

跟踪AI峰会,给自己提出的两个问题。

踪红杉AI峰会全纪录&#xff1a;AI打开万亿美元市场&#xff0c;卖的不是工具&#xff0c;而是收益。 原文链接&#xff1a; 红杉AI峰会全记录&#xff1a;AI打开万亿美元市场&#xff0c;卖的不是工具&#xff0c;而是收益&#xff08;全文&#xff09;_腾讯新闻 自己的学习…...

分布式ID生成器:原理、对比与WorkerID实战

一、为什么需要分布式ID&#xff1f; 在微服务架构下&#xff0c;单机自增ID无法满足跨服务唯一性需求&#xff0c;且存在&#xff1a; • 单点瓶颈&#xff1a;数据库自增ID依赖单表写入 • 全局唯一性&#xff1a;跨服务生成可能重复 • 扩展性差&#xff1a;分库分表后ID规…...

AR 开启昆虫学习新视界,解锁奇妙微观宇宙

在传统昆虫学习中&#xff0c;课堂教学是主要方式&#xff0c;老师通过板书、PPT 传授知识&#xff0c;但学生被动接受&#xff0c;书本静态图片无法展现昆虫真实比例、立体形态&#xff0c;学生难以直观感受复杂身体结构。博物馆的昆虫标本也是学习途径&#xff0c;不过标本放…...

WPF技巧-常用的Converter集合(更新ing)

文章目录 [toc]&#x1f9e9; 示例 1&#xff1a;BooleanToVisibilityConverter&#x1f9e9; 示例 2&#xff1a;InvertedBooleanToVisibilityConverter&#x1f9e9; 示例 3&#xff1a;StringToColorConverter&#x1f9e9; 示例 4&#xff1a;StringToBrushConverter&#…...

PostGIS栅格数据类型解析【raster】

PostGIS 栅格数据类型解析&#xff1a;结构、转换与应用 一、栅格数据类型概述 在 PostGIS 中&#xff0c;raster 是用于存储和处理栅格数据的核心类型&#xff0c;支持从多种格式&#xff08;如 JPEG、GeoTIFF、PNG、DEM&#xff09;导入的数据。每个栅格由一个或多个波段&a…...

985,成立人工智能学院

5月17日&#xff0c;北京理工大学AI变革与科教创新论坛暨人工智能学院成立大会在中关村校区举行。 北京理工大学校长姜澜介绍了学校近年来高质量发展取得的成绩。他表示&#xff0c;北京理工大学对人工智能高度重视、提前布局&#xff0c;具备扎实基础。学校将通过“一零一一”…...

使用 ARCore 和 Kotlin 开发 Android 增强现实应用入门指南

环境准备 1. 工具与设备要求 Android Studio&#xff1a;Arctic Fox 或更高版本设备&#xff1a;支持 ARCore 的 Android 设备&#xff08;查看支持列表&#xff09;依赖库&#xff1a;// build.gradle (Module级) dependencies {implementation com.google.ar:core:1.35.0im…...

房贷利率计算前端小程序

利率计算前端小程序 视图效果展示如下&#xff1a; 在这里插入代码片 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0&qu…...

剧本杀小程序:指尖上的沉浸式推理宇宙

在推理热潮席卷社交圈的当下&#xff0c;你是否渴望随时随地开启一场烧脑又刺激的冒险&#xff1f;我们的剧本杀小程序&#xff0c;就是你掌心的“推理魔法盒”&#xff0c;一键解锁无限精彩&#xff01; 海量剧本库&#xff0c;满足多元口味&#xff1a;小程序汇聚了从古风权…...

shp2pgsql 导入 Shp 到 PostGIS 空间数据库

前言 ❝ shp2pgsql是PostGIS自带的命令行工具&#xff0c;用于将Shapefile文件声称SQL脚本导入到PostGIS空间数据库。 1. 安装 PostGIS 通过Application Stack Builder或者下载单独的PostGIS包进行安装。而shp2pgsql则是与PostGIS工具集成在一起&#xff0c;无需单独下载。该命…...

什么是 ERP、MES、PLM,生产制造中如何应用

生产制造领域数字化转型加速背景下&#xff0c;ERP、MES、PLM 系统的应用成为企业提升竞争力的关键。然而&#xff0c;部分企业因对三者功能认知模糊、系统搭配不当、实施流程缺失&#xff0c;导致生产计划混乱、库存失衡、质量管控失效等问题频发。明晰系统功能定位与协同逻辑…...

Android Edge-to-Edge

Android Edge-to-Edge显示&#xff1a;开发者综合指南 一、什么是Android Edge-to-Edge ​ Android Edge-to-Edge是一种先进的用户界面&#xff08;UI&#xff09;设计理念&#xff0c;旨在最大化利用设备的显示区域。它允许应用程序的内容延伸至屏幕的各个边缘&#xff0c;包…...

Java期末总复习 编程题(偏基础)

71. ①编写一个含 2 个属性的类&#xff0c;并为其设计有参构造方法&#xff0c;再设计一个用于显示属性值的方法。②编写该类的一个子类&#xff0c;除继承父类的 2 个属性外再增加一个属性&#xff0c;并创建有参构造方法对 3个属性初始化&#xff0c;重写显示属性的方法用于…...

进阶知识:自动化框架开发之有参的函数装饰器@wraps()和无参之间的对比

进阶知识&#xff1a;自动化框架开发之有参的函数装饰器wraps() 一、核心代码解析 1.1 有参装饰器结构 def func_3(argTrue): # 外层接收参数def inner_func(func): # 中间层接收被装饰函数wraps(func) # 保留元信息def wrap_func(*args, **kwargs): …...

es疑惑解读

好的&#xff0c;没问题。下面是我们对话中关于 Elasticsearch 数据库的知识点汇总&#xff0c;以问答对的形式呈现&#xff0c;希望能成为一个清晰的教程。 Elasticsearch 基础与 CRUD 操作 Q1: 我有 pymysql 的使用经验&#xff0c;想学习 Elasticsearch (ES) 的增删改查&am…...

Elasticsearch面试题带答案

Elasticsearch面试题带答案 Elasticsearch面试题及答案【最新版】Elasticsearch高级面试题大全(2025版),发现网上很多Elasticsearch面试题及答案整理都没有答案,所以花了很长时间搜集,本套Elasticsearch面试题大全,Elasticsearch面试题大汇总,有大量经典的Elasticsearch面…...

Linux 的 TCP 网络编程 -- 回显服务器,翻译服务器

目录 1. 相关函数介绍 1.1 listen() 1.2 accept() 1.3 connect() 2. TCP 回显服务器 2.1 Common.hpp 2.2 InetAddr.hpp 2.3 TcpClient.cc 2.4 TcpServer.hpp 2.5 TcpServer.cc 2.6 demo 测试 3. TCP 翻译服务器 3.1 demo 测试 1. 相关函数介绍 其中一些函数在之前…...

差动讯号(2):奇模与偶模

我们经常在探讨差动对时经常听到差模&#xff08;Differential mode&#xff09;与共模&#xff08;Common mode&#xff09;&#xff0c;究竟什么是差模&#xff1f; 什么是共模&#xff1f; 这一切就要从奇模&#xff08;Odd mode&#xff09;与偶模&#xff08;Even mode&am…...

口腔牙科小程序源码介绍

基于ThinkPHP、FastAdmin以及UniApp开发的口腔牙科小程序源码&#xff0c;专为口腔牙科行业设计&#xff0c;旨在提供一个便捷、高效的线上服务平台。 从技术层面看&#xff0c;这套源码结合了ThinkPHP的强大后端功能、FastAdmin的快速开发特性以及UniApp的跨平台优势&#xf…...

云计算与大数据进阶 | 27、存储系统如何突破容量天花板?可扩展架构的核心技术与实践—— 分布式、弹性扩展、高可用的底层逻辑(上)

数据中心里&#xff0c;存储系统是至关重要的组成部分。由于相关硬件组件与存储操作系统的多样性和复杂性&#xff0c;如何在保证存储稳定、安全、可靠的同时&#xff0c;实现灵活扩展和自服务&#xff0c;一直是困扰数据中心全面云化的难题。 简单来说&#xff0c;现在的难题…...

企业级物理服务器选型指南 - 网络架构优化篇

在分布式系统架构中&#xff0c;物理服务器的网络质量直接影响业务连续性。本文将通过真实场景演示如何选择符合业务特性的物理服务器。 一、网络拓扑设计原则 当企业需要覆盖多地域用户时&#xff0c;建议采用混合组网方案&#xff1a; # 网络质量检测脚本&#xff08;Pytho…...

可视化图解算法42:寻找峰值

牛客网 面试笔试TOP101 | LeetCode 162. 寻找峰值 1. 题目 描述 给定一个长度为n的数组nums&#xff0c;请你找到峰值并返回其索引。数组可能包含多个峰值&#xff0c;在这种情况下&#xff0c;返回任何一个所在位置即可。 1.峰值元素是指其值严格大…...

java每日精进 5.20【MyBatis 联表分页查询】

1. MyBatis XML 实现分页查询 1.1 实现方式 MyBatis XML 是一种传统的 MyBatis 使用方式&#xff0c;通过在 XML 文件中编写 SQL 语句&#xff0c;并结合 Mapper 接口和 Service 层实现分页查询。分页需要手动编写两条 SQL 语句&#xff1a;一条查询分页数据列表&#xff0c;…...

瀚高安全版4.5.8/4.5.9字符串默认按字节存储导致数据无法写入(APP)

文章目录 环境文档用途详细信息 环境 系统平台&#xff1a;Linux x86-64 Red Hat Enterprise Linux 7 版本&#xff1a;4.5 文档用途 解决安全版4.5.8/4.5.9字符串默认使用字节存储导致插入时提示数据超长。 详细信息 使用sysdba用户执行&#xff0c;重载配置或重启数据库…...

python新手学习笔记①

本笔记是根据Bilibili里的【3小时超快速入门Python | 动画教学【2025新版】【自学Python教程】【零基础Python】【计算机二级Python】【Python期末速成】】 https://www.bilibili.com/video/BV1Jgf6YvE8e/这个视频合集制作的代码笔记&#xff01; 1.字符串连接 运行结果 2.…...