天天说手撕AVL树?你真的能撕下来吗?(详细解释+代码注释)

news2025/2/5 14:54:45

目录

前言

一、什么是AVL树?

二、模拟实现AVL树

2.1、定义AVL树

2.2、插入结点功能

2.2.1、先把值为val的结点插入到AVL树中

2.2.2、根据平衡因子来对树进行调整

2.3、AVL树的旋转

2.3.1、右单旋

2.3.2、左单旋

2.3.3、左右双旋

2.3.4、右左双旋

 2.3、验证AVL树

2.4、删除结点

三、性能分析


前言

为什么要引入AVL树?

        我们熟知的二叉搜索树虽然在原来二叉树的基础上已经缩短了查找的效率,但是若数据有序,或接近有序的二叉搜索树将会退化成一个单支树,查找的效率相当于在顺序表中搜索元素,有悖引入二叉搜索树的初衷了;这时候就出现了AVL树,可以很好的接近上述问题;怎么解决?请往下看~

接下来本文将用最简洁明了的话语、最详细的代码、最详细的注释,带你深入以及模拟实现一个AVL树;


一、什么是AVL树?

        当向二叉搜索树中插入新的结点后,如果能保证每一个结点的左右子树高度差(简称平衡因子)的绝对值不超过1(这里是对树的结点通过左右旋转后),那么他就是一颗AVL树。AVL树降低了树的高度,减少了平均搜索长度,提高了搜索效率;

        如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在O(log2 n) ,搜索时间复杂度O(log2 n)。

例如下图:(一颗AVL树)


二、模拟实现AVL树

        模拟实现AVL树就需要分别实现两个功能,插入和删除,其中,插入最难,因为涉及到左旋右旋调整树的平衡;这里如果能将插入功能实现,那么删除功能的实现就十分容易了;

2.1、定义AVL树

public class AVLTree {
    static class TreeNode {
        public int val;
        public int bf;//平衡因子
        TreeNode left;//左子树
        TreeNode rigth;//右子树
        TreeNode parent;//父亲结点

        public TreeNode(int val) {
            this.val = val;
        }
    }
    public TreeNode root;//根节点

2.2、插入结点功能

插入结点分两步:

1.先将结点插入到AVL树中(方法和二叉搜索树的插入结点的方式一样);

2.插入以后,根据平衡因子来对树进行调整;

2.2.1、先把值为val的结点插入到AVL树中

思路:

        首先看根结点是否为空,若根节点为空说明这棵树还是一个空树,那么就直接创建一个值为val的根节点;

        若根节点不为空,就用一个cur指针去遍历这个树的结点,并用一个parent指针记录cur的父亲结点,当cur指向的当前结点值大于val,那么就因该向树的左子树走,若小于val,就因该向树的右边走,直到cur为空时,通过比parent与val值,即可确定插入位置;

代码如下:

    public boolean insert(int val) {
        TreeNode node = new TreeNode(val);
        if(root == null) {
            root = node;
            return true;
        }
        TreeNode parent = null;
        TreeNode cur = root;
        while(cur != null) {
            if(cur.val > val) {
                //向左寻找
                parent = cur;
                cur = cur.left;
            } else if(cur.val == val) {
                //相等就说明插入失败
                return false;
            } else {
                //向右寻找
                parent = cur;
                cur = cur.right;
            }
        }
        //cur == null
        if(parent.val > val) {
            parent.left = node;
        } else {
            parent.right = node;
        }
        return true;
    }

2.2.2、根据平衡因子来对树进行调整

平衡因子等于什么?

这里规定为:右树的高度减左树的高度,一旦出现了平衡因子的绝对值大于1,说明这颗树就不平衡了,需要进行旋转使其平衡;

因此首先需要记录平衡因子——新增结点在右树,平衡因子++,新增结点在左树, 平衡因子--;

代码如下:

            if(cur == parent.right) {
                //新增结点在右树
                parent.bf++;
            } else {
                //新增结点在左树
                parent.bf--;
            }

先来看看为什么要调整?

假设树原本是如下样子:

之后,我插入了一个val值为10的结点,那么这颗树就不平衡了(不满足平衡因子了),为什么?看下图:

那么如何旋转呢?接下来先简单了解一下(后面会重点讲)

可以将不平衡的子树进行如下旋转:

 以上只是一种新增结点的方式,但显然,实际情况就右很多种,因此分为以下几种情况:

情况一:平衡因子等于0,这种情况, 树以及平衡;

情况二:平衡因子等于1或-1,这种情况,树不一定平衡,还需要向上去看他的父亲结点是否平衡;

情况三:平衡因子等于2或-2, 这种情况,树一定不平衡,根据实际情况,降低高度;

写成代码就是以下情况:(还没有写如何处理)

            if(parent.bf == 0) {
                //这里说明以及平衡了,不需要在调整
                break;
            } else if(parent.bf == 1 || parent.bf == -1) {
                //再往上查看结点,有可能不平衡
                cur = parent;
                parent = cur.parent;
            } else {
                //一定不平衡
                if(parent.bf == 2) {//右树高,需要降低右树的高度
                    if(cur.bf == 1) {

                    } else {//cur.bf == -1

                    }
                } else {//parent.bf == -2  左树高,需要降低左树的高度
                    if(cur.bf == -1) {

                    } else {//cur.bf == 1

                    }
                }
            }

针对以上一些不平衡的情况,我们因该如何处理呢?旋转!(重难点!)

2.3、AVL树的旋转

2.3.1、右单旋

什么时候需要右单旋呢?

新结点插入较高左子树的左侧,原因如下图

        分析:若是插入 较高左子树的左侧 那么代码根据上图的平衡因子的变化,最终代码会来到下图这个位置

 那么现在问题就在于,如何进行右单旋,使其变成一颗平衡的树?

 如下这棵树:

 变换后可以得到:

这样左旋转就完了:

parent : 结点60;

subL : 结点30;

subLR:结点40;

代码如下:

private void rotateRight(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;
        parent.left = subLR;

        subL.right = parent;
        
        subLR.parent = parent;

        parent.parent = subL;
    }

怎么可能这么容易哦~

因为60这个结点不一定是根节点,如下:

所以,代码还需要分为以下情况:(60有父亲结点和没有父亲结点)

    //右单旋
    private void rotateRight(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;
        parent.left = subLR;

        subL.right = parent;

        subLR.parent = parent;

        //必须先记录parent的父亲
        TreeNode pParent = parent.parent;

        parent.parent = subL;
        //检查parent是否是根节点
        if(parent == root) {
            root = subL;
            root = null;
        } else {
            //不是根节点,判断parent是左子树还是右子树
            if(pParent.left == parent) {
                pParent.left = subL;
            } else {
                pParent.right = subL;
            }
            subL.parent = pParent;
        }
    }

 不光转化完了,还需要调节他的平衡因子,如下:

    //右单旋
    private void rotateRight(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;
        parent.left = subLR;

        subL.right = parent;

        subLR.parent = parent;

        //必须先记录parent的父亲
        TreeNode pParent = parent.parent;

        parent.parent = subL;
        //检查parent是否是根节点
        if(parent == root) {
            root = subL;
            root = null;
        } else {
            //不是根节点,判断parent是左子树还是右子树
            if(pParent.left == parent) {
                pParent.left = subL;
            } else {
                pParent.right = subL;
            }
            subL.parent = pParent;
        }
        subL.bf = 0;
        parent.bf = 0;
    }

 写到这里,以上代码还有一点小问题,就是subLR有可能是空,如下:

怎么解决呢?实际上如果subRL是null,那么不执行 subRL.parent = parent这行代码即可,那么此时,需要加上一个判断,如下:

    //右单旋
    private void rotateRight(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;
        parent.left = subLR;
        subL.right = parent;
        if(subLR != null) {
            subLR.parent = parent;
        }
        //必须先记录parent的父亲
        TreeNode pParent = parent.parent;
        parent.parent = subL;
        //检查parent是否是根节点
        if(parent == root) {
            root = subL;
            root = null;
        } else {
            //不是根节点,判断parent是左子树还是右子树
            if(pParent.left == parent) {
                pParent.left = subL;
            } else {
                pParent.right = subL;
            }
            subL.parent = pParent;
        }
        subL.bf = 0;
        parent.bf = 0;
    

这样右单旋就完成了~

2.3.2、左单旋

什么时候需要左单旋呢?

新结点插入较高右子树的右侧,原因如下图

代码上,和右单旋是差不多的,可以参照着右单旋来写:

    //左单旋
    private void rotateLeft(TreeNode parent) {
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;
        parent.right = subRL;

        subR.left = parent;

        if(subRL != null) {
            subRL.parent = parent;
        }
        //必须先记录parent的父亲
        TreeNode pParent = parent.parent;
        parent.parent = subR;
        //检查parent是否为根节点
        if(parent == root) {
            root = subR;
            root.parent = null;
        } else {
            //不是根节点需要判断parent是左子树还是右子树
            if(pParent.left == parent) {
                pParent.left = subR;
            } else {
                pParent.right = subR;
            }
            subR.parent = pParent;
        }
        subR.bf = 0;
        parent.bf = 0;
    }

由此通过左单旋,和右单旋也可以看出循环条件为parent != null,原因:一直在搜索不平衡因子是一个向上搜索的过程,直到parent为null,说明已经平衡因子已经调节到了到根节点(修改完了),因此代码就可以修改:

    public boolean insert(int val) {
        TreeNode node = new TreeNode(val);
        if(root == null) {
            root = node;
            return true;
        }
        TreeNode parent = null;
        TreeNode cur = root;
        while(cur != null) {
            if(cur.val > val) {
                //向左寻找
                parent = cur;
                cur = cur.left;
            } else if(cur.val == val) {
                //相等就说明插入失败
                return false;
            } else {
                //向右寻找
                parent = cur;
                cur = cur.right;
            }
        }
        //cur == null
        if(parent.val > val) {
            parent.left = node;
        } else {
            parent.right = node;
        }

        cur = node;
        //调整因子
        while(parent != null) {
            //新增结点在右树,平衡因子++,新增结点在左树, 平衡因子--
            if(cur == parent.right) {
                //新增结点在右树
                parent.bf++;
            } else {
                //新增结点在左树
                parent.bf--;
            }

            //检查平衡因子大小
            if(parent.bf == 0) {
                //这里说明以及平衡了,不需要在调整
                break;
            } else if(parent.bf == 1 || parent.bf == -1) {
                //再往上查看结点,有可能不平衡
                cur = parent;
                parent = cur.parent;
            } else {
                //一定不平衡
                if(parent.bf == 2) {//右树高,需要降低右树的高度
                    if(cur.bf == 1) {
                        //右旋
                        rotateLeft(parent);
                    } else {//cur.bf == -1

                    }
                } else {//parent.bf == -2  左树高,需要降低左树的高度
                    if(cur.bf == -1) {
                        //右旋
                        rotateRight(parent);
                    } else {//cur.bf == 1

                    }
                }
            }
        }
    }

这样左单旋就完成了~

2.3.3、左右双旋

什么时候需要右单旋呢?

新结点插入较高左子树的右侧(先左单旋再右单旋),原因如下图:

 像这样的一棵树如果只是进行简单的左旋或右旋,相当于旋转了个寂寞,如下:

那怎么办呢?不妨这样做~

第一步:可以先对30这颗子树进行左旋,如下:

第二步:再将90这个parent结点进行右旋,如下:

那么此时,他就是一个高度平衡的AVL树了~

左右双旋,实际上就是左单旋 + 右单旋,并且这部分代码之前就写好了,所以直接拿来用就可以,值得注意的一点是,要旋转的结点,这个可以自己画画图就明白了;

代码如下:

    //左右双旋
    private void rortateLR(TreeNode parent) {
        //选左旋后右旋
        rotateLeft(parent.left);//注意旋转的是parent的左子树
        rotateRight(parent);
        
    }

 这样就完了吗?

当然没有~这里还有平衡因子没有修改呢!

 这里平衡因子分两种情况,接下来,我将用一张图告诉你该如何解决!如下:

因此,代码如下:

    //左右双旋
    private void rortateLR(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;
        int bf = subLR.bf;
        //选左旋后右旋
        rotateLeft(parent.left);//注意旋转的是parent的左子树
        rotateRight(parent);
        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;
        } //bf == 0,这里本身就是平衡的,就不用修改
    }

这样左右双旋就完成辽~

2.3.4、右左双旋

什么时候需要右左双旋呢?

新结点插入较高右子树的左侧,原因如下图

这里代码和左右双旋差不多,分析思路也一样,所以这里就不展开啦,代码如下:


    //右左双旋
    private void rottateRL(TreeNode parent) {
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;
        int bf = subRL.bf;
        rotateRight(parent.right);
        rotateLeft(parent);
        if(bf == 1) {
            parent.bf = -1;
            subR.bf = 0;
            subRL.bf = 0;
        } else if(bf == -1) {
            subRL.bf = 0;
            parent.bf = 0;
            subR.bf = 1;
        }
    }

由以上的各种旋转,那么之前遗留的insert函数中的代码也就完整啦,如下:

    public boolean insert(int val) {
        TreeNode node = new TreeNode(val);
        if(root == null) {
            root = node;
            return true;
        }
        TreeNode parent = null;
        TreeNode cur = root;
        while(cur != null) {
            if(cur.val > val) {
                //向左寻找
                parent = cur;
                cur = cur.left;
            } else if(cur.val == val) {
                //相等就说明插入失败
                return false;
            } else {
                //向右寻找
                parent = cur;
                cur = cur.right;
            }
        }
        //cur == null
        if(parent.val > val) {
            parent.left = node;
        } else {
            parent.right = node;
        }
        node.parent = parent;/
        cur = node;
        //调整因子
        while(parent != null) {
            //新增结点在右树,平衡因子++,新增结点在左树, 平衡因子--
            if(cur == parent.right) {
                //新增结点在右树
                parent.bf++;
            } else {
                //新增结点在左树
                parent.bf--;
            }

            //检查平衡因子大小
            if(parent.bf == 0) {
                //这里说明以及平衡了,不需要在调整
                break;
            } else if(parent.bf == 1 || parent.bf == -1) {
                //再往上查看结点,有可能不平衡
                cur = parent;
                parent = cur.parent;
            } else {
                //一定不平衡
                if(parent.bf == 2) {//右树高,需要降低右树的高度
                    if(cur.bf == 1) {
                        //左旋
                        rotateLeft(parent);
                    } else {//cur.bf == -1
                        //右左双旋
                        rottateRL(parent);
                    }
                } else {//parent.bf == -2  左树高,需要降低左树的高度
                    if(cur.bf == -1) {
                        //右旋
                        rotateRight(parent);
                    } else {//cur.bf == 1
                        //左右双旋
                        rortateLR(parent);
                    }
                }
                //走完以上代码即哦平衡
                break;
            }
        }
        return true;
    }

 2.3、验证AVL树

验证一棵树是否为AVL树只需要验证他的以下两个性质即可~

1)、验证其为二叉搜索树

  • 如果中序遍历可得到一个有序的序列,就说明为二叉搜索树;

2)、验证其为平衡树

  • 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
  • 节点的平衡因子是否计算正确

代码如下:

    //验证平衡
    public boolean isBalanced(TreeNode root) {
        if(root == null) {
            return true;
        }
        int leftH = heigh(root.left);
        int rightH = heigh(root.right);
        if(rightH - leftH != root.bf) {
            System.out.println("结点:" + root.val + "的平衡因子异常");
            return false;
        }
        return Math.abs(leftH - rightH) <= 1 &&
                isBalanced(root.left) &&
                isBalanced(root.right);
    }
    //求树的高度
    private int heigh(TreeNode root) {
        if(root == null) {
            return 0;
        }
        int leftH = heigh(root.left);
        int rightH = heigh(root.right);
        return leftH > rightH ? leftH + 1 : rightH + 1;
    }
    //验证顺序
    public void dfs(TreeNode root) {
        if(root == null) {
            return;
        }
        dfs(root.left);
        System.out.print("root = " + root.val + "; ");
        dfs(root.right);
    

2.4、删除结点

删除结点这里不需要掌握,感兴趣的可以自己实现一下;

如果要删除结点,实际上和二叉搜索树结点的删除思路相似,只不过需要进行对平衡因子的更新,并且若出现了不平衡需要进行左旋或者右旋;

代码如下:

    /**
     * 删除操作
     */
    public AVLNode<T> deleteNode(AVLNode T,T value){
        if(T==null){
            return null;
        }else{
            //往左走
            if((Integer)value<(Integer) T.data){
                T.lchild = deleteNode(T.lchild,value);  //函数返回新的根节点(所以要重新建立孩子与双亲间的联系)
                if(getHeight(T.rchild)-getHeight(T.lchild)>=2){ //删掉左子树的孩子结点,肯定是右边可能会高过左边的情况
                    if(getHeight(T.rchild.lchild)>getHeight(T.rchild.rchild)){  //左子树的 右子树比左子树的左子树要高
                        //RL旋转
                        T = doubleRotateWithRight(T);    //记住,因为旋转后,根节点会发生变化,一定要重新接收根结点
                    }else{
                        //RR
                        T = singleRotateWithLeft(T);
                    }
                }
                //往右走
            }else if((Integer)value>(Integer)T.data){
                T.rchild = deleteNode(T.rchild,value);
                if(getHeight(T.lchild)-getHeight(T.rchild)>=2){   //删掉右子树的孩子结点,肯定是左边可能会高过右边的情况
                    if(getHeight(T.lchild.rchild)>getHeight(T.lchild.lchild)){
                        //LR旋转
                        T = doubleRotateWithLeft(T);
                    }else{
                        //LL
                        T = singleRotateWithRight(T);
                    }
                }
            }else{
                //找到了要删除的结点
 
                //1. 没有左右孩子,删除的是叶子节点  (不用判断是否需要修复--旋转)
                if(T.lchild==null&&T.rchild==null){
                    T = null;
                    //2. 删除的结点只有左孩子或右孩子
                }else {
                    if(T.lchild!=null){
                        T = T.lchild;
                    }else if(T.rchild!=null){
                        T = T.rchild;
                    }else{
                        //3. 删除的结点左右孩子都有
                        T.data = find_min_value(T.rchild);  //找到最小节点,替换
                        T.rchild = deleteNode(T.rchild,(T)T.data);  //删除替换的最小的那个结点
 
                        //判断旋转
                        if(getHeight(T.lchild)-getHeight(T.rchild)>=2){
                            if(getHeight(T.lchild.rchild)-getHeight(T.lchild.lchild)>=2){
                                //LR
                                T = doubleRotateWithLeft(T);
                            }else{
                                //LL
                                T = singleRotateWithRight(T);
                            }
                        }
                    }
                }
            }
        }
        if(T!=null){
            //重新计算高度
            T.height = Max(getHeight(T.lchild),getHeight(T.rchild))+1;
        }
 
        //返回新的根节点
        return T;
 
    }
 
    /**
     * 找到最小的结点值
     */
    public T find_min_value(AVLNode T){
 
        if(T.lchild==null){
            return (T) T.data;
        }else{
            return find_min_value(T.lchild);
        }
    }
 
    /**
     * 用于比较两棵子树高度,比较哪边高 ,用于节点高度 = Max(T.lchild.height,T.rchild.height)+1
     */
    public int Max(int lHeight,int rHeight){
        if(lHeight>=rHeight){
            return lHeight;
        }else{
            return rHeight;
        }
    }
 
    /**
     * 获取结点高度,因为可能计算高度的时候,左右孩子结点很可能为空,如果不用这个方法判断的话,会导致nullPointerException
     */
    public int getHeight(AVLNode T){
        if(T==null){
            return -1;
        }else{
            return T.height;
        }
    }


三、性能分析

        AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 log2(N);

        AVL树不适合大量的插入和删除,因为要不断维持本身AVL树平衡的性质,但是他的适合查找数据,速度相当快;也就是说他的平衡说通过大量旋转换来的~

总结:

        如果需要 一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修 改,就不太适合。


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

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

相关文章

zabbix监控Gbase8a

部署​参考文章 部署zabbix [rootgba02 ~]# cat /etc/redhat-release CentOS Linux release 8.5.2111 [rootgba02 ~]# uname -a Linux gba02 4.18.0-348.el8.x86_64 #1 SMP Tue Oct 19 15:14:17 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux 安装nginx [rootgba02 ~]# systemctl…

漏洞扫描原理及X-Scan安装

漏洞扫描原理及X-Scan使用 漏洞扫描原理 1.概念&#xff1a; 漏洞扫描技术是建立在端口扫描技术的基础之上的&#xff0c;从对黑客的攻击行为的分析和收集的漏洞来看&#xff0c;绝大多数都是针对某一个特定的端口的&#xff0c;所以漏洞扫描技术以与端口扫描技术同样的思路来开…

外汇天眼:流动性风险加剧!欧洲天然气价格上限180欧,WTI原油回升

欧盟天然气价格上限协议达成、美欧原油期货持仓降至2015年以来的最低水平&#xff1b;能源价格流动性风险进一步加剧&#xff0c;WTI原油继续“寻底”。 每兆瓦时180欧元&#xff01;欧盟天然气价格上限协议达成 周一&#xff08;12月19日&#xff09;经历两个月谈判后&#x…

图解NumPy,这是理解数组最形象的一份教程了

本文用可视化的方式介绍了 NumPy 的功能和使用示例。NumPy 软件包是 Python 生态系统中数据分析、机器学习和科学计算的主力军。它极大地简化了向量和矩阵的操作处理。Python 的一些主要软件包&#xff08;如 scikit-learn、SciPy、pandas 和 tensorflow&#xff09;都以 NumPy…

MySQL使用MyCat实现分库分表

MySQL分库分表的实现方式有&#xff1a; shardingJDBC&#xff1a;基于AOP原理&#xff0c;在应用程序中对本地执行的SQL进行拦截&#xff0c;解析、改写、路由处理。需要自行编码配置实现&#xff0c;只支持java语言&#xff0c;性能较高。MyCat&#xff1a;数据库分库分表中…

我国盆栽行业发展向好 服务、信息、性价比高度结合的销售模式势在必行

盆栽系由中国传统的园林艺术变化而来&#xff0c;其定义是指栽在盆里的活体植物的总称。盆栽是温室花卉生产的主要方式之一&#xff0c;即便于控制各种生活条件&#xff0c;有利于花卉的促控栽培&#xff0c;还便于搬移&#xff0c;即可陈设于室内&#xff0c;又可布景于庭院。…

Metal每日分享,基于色温调整白平衡滤镜效果

本案例的目的是理解如何用Metal实现基于色温调整白平衡效果滤镜&#xff0c;主要就是消除或减轻日光下偏蓝和白炽灯下偏黄&#xff0c;简单讲把应该是白色的调成白色或接近白色&#xff0c;不使其严重偏色&#xff1b; Demo HarbethDemo地址iDay每日分享文档地址 实操代码 /…

如何多人配音?告诉你这几个实用的配音技巧

大家平常在刷短视频时&#xff0c;有没有刷到过一些搞笑配音视频呢&#xff1f;有些视频是由多人互动或者一人扮演多角而成的&#xff0c;这些创作者把当下比较热门的话题通过配音的方式呈现出来&#xff0c;或让我们捧腹大笑、或让我们引起思考。如果你也想通过这种方式来制作…

JVS低代码基础介绍

企业信息化底座 JVS是面向软件开发团队可以快速实现应用的基础开发脚手架&#xff0c;主要定位于企业信息化通用底座&#xff0c;采用微服务分布式框架&#xff0c;提供丰富的基础功能&#xff0c;集成众多业务引擎&#xff0c;它灵活性强&#xff0c;界面化配置对开发者友好&a…

Git仓库创建、代码更新、代码提交完整版(Gitee、Github、GitLab对比)

区别 Gitee、Github、GitLab三个都提供了 个人和团队存储、分享、发布和协同开发项目的中心化云存储功能 名称代码版块控制管理工具是否收费搭建环境联网/github git 公有仓库免费 私有仓库收费&#xff08;鼓励代码共享&#xff09; 企业私有需要搭建可离线&#xff08;分布…

在英特尔独立显卡上部署YOLOv5 v7.0版实时实例分割模型

作者&#xff1a;贾志刚 英特尔物联网创新大使  目录 1.1 YOLOv5实时实例分割模型简介 1.2 英特尔消费级锐炫™ A 系列显卡简介 1.3 在英特尔独立显卡上部署YOLOv5-seg模型的完整流程 1.3.1 搭建YOLOv5开发环境和OpenVINO部署环境 1.3.2 验证YOLOv5开发环境和OpenVINO部…

Spring Cloud之acos服务注册与Dubbo

Spring Cloud之acos服务注册与Dubbo nacos是springcloud的扩展&#xff0c;注册中心功能通过NacosDiscoveryClient 继承DiscoveryClient&#xff0c;在springcloud中&#xff0c;与Eureka可以无侵入的切换。注册中心可以手动剔除服务实例&#xff0c;通过消息通知客户端更新缓…

【推荐】数据湖技术及实践与案例资料汇总合集47篇

数据湖或hub的概念最初是由大数据厂商提出的&#xff0c;表面上看&#xff0c;数据都是承载在基于可向外扩展的HDFS廉价存储硬件之上的。但数据量越大&#xff0c;越需要各种不同种类的存储。最终&#xff0c;所有的企业数据都可以被认为是大数据&#xff0c;但并不是所有的企业…

30多条立马执行的站内SEO优化建议

站内SEO优化,讲来讲去很多年,但是国内很少有比较全面的Check List。国外的文章,一般都是教你如何优化一篇文章。而不是教你如何打造一个一个商业化的产品页。本篇文章是结合我这几年的独立站SEO项目经验,对站内SEO进行的一些总结,有30多条立马执行的站内SEO优化建议。 其实…

SpringBoot+Actuator+Prometheus+Grafana进行可视化应用监控

最终效果 actuator actuator是什么 看一看chatGPT怎么说&#xff1f; Spring Boot Actuator是Spring Boot的一个子项目&#xff0c;它为监视和管理Spring Boot应用程序提供了附加功能。它提供了一组端点&#xff0c;允许您监视和管理应用程序&#xff0c;如运行状况检查、度量…

[附源码]计算机毕业设计Python电影推荐网站(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

使用FastDeploy在英特尔CPU和独立显卡上端到端高效部署AI模型

目录 1.1 产业实践中部署AI模型的痛点 1.1.1 部署模型的典型流程 1.1.2 端到端的AI性能 1.1.3 部署模型的难点和痛点 1.2 FastDeploy简介 1.3 英特尔独立显卡简介 1.4 使用FastDeploy在英特尔CPU和独立显卡上部署模型的步骤 1.4.1 搭建FastDeploy开发环境 1.4.2 下载…

秒杀/抢购架构设计

1 秒杀业务分析 1.1 正常电子商务流程 &#xff08;1&#xff09;查询商品&#xff1b; &#xff08;2&#xff09;创建订单&#xff1b; &#xff08;3&#xff09;扣减库存&#xff1b; &#xff08;4&#xff09;更新订单&#xff1b; &#xff08;5&#xff09;付款&…

Odoo丨手把手教你灵活控制表单明细的创建与删除!

文章目录一、前言二、表单明细的创建与删除1.根据主表字段带出明细信息2.根据主表字段控制明细表的添加和删除功能三、总结一、前言 我们知道&#xff0c;在Odoo页面上&#xff0c;控制的最小单位是字段&#xff0c;可通过Odoo提供的属性&#xff0c;控制字段的必填、只读、隐…

Spring Cloud 应用 Proxyless Mesh 模式探索与实践

作者&#xff1a;十眠 Service Mesh 简介 Service Mesh 早已不是一个新兴的概念&#xff0c;目前已经有许多关于 Service Mesh 的探索以及实践。 2016 年可以说是 Service Mesh 的元年&#xff0c;Buoyant 公司 CEO William Morgan 率先发布 Linkerd &#xff0c;成为业界首…