AVL树性质和实现

news2024/7/7 16:57:21

AVL树

AVL是两名俄罗斯数学家的名字,以此纪念

与二叉搜索树的区别

AVL树在二叉搜索树的基础上增加了新的限制:需要时刻保证每个树中每个结点的左右子树高度之差的绝对值不超过1

因此,当向树中插入新结点后,即可降低树的高度,从而减少平均搜索长度

平衡因子

为了能方便处理平衡二叉搜索树的限制条件,通常会引入平衡因子的概念

某一节点的平衡因子=其右子树高度-其左子树高度

在AVL树中,并不是一定需要平衡因子的,有些代码的AVL树就没有平衡因子。

这里引入平衡因子只是更方便的去判断树是否平衡了

AVL树的效率推算

image-20231013200846180

我们知道树的增删查改的效率是与树的高度有关的

假如AVL树是满二叉树,此时:2h-1=N

假如AVL树不是满二叉树,设最底层的节点个数为X,此时:2h-X=N。X的范围为[1,最后一层结点树-1]

此时上述两种情况都可以得出树的高度的数量级为logN,因此增删查改的时间复杂度为O(logN)

AVL树的节点设计

首先,与二叉搜索树相比,每个节点多了一个平衡因子(balance factor)

其次,为了满足一定需求,还多了个_parent指针,指向节点的父亲

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

这是三叉链image-20231018232522035

AVL树的框架设计

template<class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
    //……
private:
	Node* _root=nullptr;
};

AVL树的插入

在面试时几乎不会让你手撕AVL树的插入。重要的是了解思想。

但让你手撕一个旋转是有可能的

插入的逻辑

AVL树是在二叉搜索树的基础上引入了平衡因子,因此AVL树的插入分为两个步骤:

  • 按照二叉搜索树的方式插入新节点(多了个链接_parent
  • 插入后,更新节点的平衡因子

首先注意:新插入的节点只会对其父亲和其祖先的平衡因子造成影响

插入节点的_parent的平衡因子是一定需要调整的;在插入之前其_parent的平衡因子有三个可能值1、-1、0,插入节点后:

  • 如果插入到_parent的左侧,则_parent的平衡因子–即可
  • 如果插入到_parent的右侧,则_parent的平衡因子++即可

此时:_parent的平衡因子可能有三大种情况:

  • 如果_parent的平衡因子为0,说明插入之前的平衡因子为正负1,插入后被调整成0,满足AVL树的性质,插入成功,插入结束
  • 如果_parent的平衡因子为±1,说明插入之前的平衡因子为0,此时树的高度增加,那么就需要继续向上更新祖先的平衡因子,直至某一祖先的平衡因子为0或者更新到根节点,才算插入成功,停止更新,插入结束。
  • 如果_parent的平衡因子为±2,此时违反了AVL树的性质,需要进行旋转处理。处理完成则算插入成功,插入结束
更新平衡因子

最坏的情况是一直更新到根,如下图:image-20231017154509542

因此在更新平衡因子时,我们的循环条件为:while(parent)

因为只有根节点的_parent为空。所以当更新完根节点的平衡因子后,循环结束

while (parent)
{
    if (cur == parent->_left)//节点插入在父亲左边
    {
        parent->_bf--;
    }
    else if (cur == parent->_right)//节点插入在父亲右边
    {
        parent->_bf++;
    }

    //进一步判断祖先节点的平衡因子
    if (parent->_bf == 0)//父亲的平衡因子为0,循环结束
    {
        break;
    }
    else if (parent->_bf == 1 || parent->_bf == -1)//父亲的平衡因子为1或-1,则需要继续向上调整
    {
        cur = parent;
        parent = cur->_parent;
    }
    else if (parent->_bf == 2 || parent->_bf == -2)//父亲的平衡因子为2或-2,则需要旋转,且选择完后树一定平衡,故结束循环
    {
        //左单旋
        if (parent->_bf == 2 && cur->_bf == 1)
        {
            RotateL(parent);
        }
        //右单旋
        if (parent->_bf == -2 && cur->_bf == -1)
        {
            RotateR(parent);
        }
        //右左双旋
        if (parent->_bf == 2 && cur->_bf == -1)
        {
            RotateRL(parent);
        }
        //左右双旋
        if (parent->_bf == -2 &&	 cur->_bf == 1)
        {
            RotateLR(parent);
        }

        break;
    }
    else//其他情况,此时说明在插入之前树就已经不是平衡树了
    {
        assert(false);
    }
}

最后一个else中的assert(false)看似是无用的,因为parent不可能是绝对值大于2的。但是代码都是人写的,不可排除一开始的树就是有问题的。因此这句代码很重要

AVL树的旋转

根据节点插入位置的不同,AVL树的旋转分为四种:

  • 新节点插入较高左子树的左侧–左左:右单旋
  • 新节点插入较高右子树的右侧–右右:左单旋
  • 新节点插入较高左子树的右侧—左右:先左单旋再右单旋(左右双旋)
  • 新节点插入较高右子树的左侧—右左:先右单旋再左单旋(右左双旋)

何时使用何种旋转:

假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑:

  1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR

    • 当pSubR的平衡因子为1时,执行左单旋

    • 当pSubR的平衡因子为-1时,执行右左双旋

  2. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL

    • 当pSubL的平衡因子为-1是,执行右单旋

    • 当pSubL的平衡因子为1时,执行左右双旋

旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新

右单旋

image-20231019154016105

上图在插入前,AVL树是平衡的。新节点插入到30的左子树(注意:此处不是左孩子)中,30左子树增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层,即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点的平衡因子即可

在旋转过程中,有以下几种情况需要考虑:

  1. cur节点的右孩子可能存在,也可能不存在

  2. parent可能是根节点,也可能是子树

    如果是根节点,旋转完成后,要更新根节点

    如果是子树,可能是某个节点的左子树,也可能是右子树

这两点是所有旋转情况都需要考虑的

右单旋的核心操作:把cur的右孩子给到parent的左,再把parent给到cur的右

代码:

void RotateR(Node* parent)//parent的平衡因子绝对值为2
{
    Node* cur = parent->_left;
    Node* curright = cur->_right;

    parent->_left = curright;//把cur的右孩子给到parent的左
    if (curright)//如果cur的右孩子存在,则更新其父亲为parent
    {
        curright->_parent = parent;
    }
    cur->_right = parent;//再把parent给到cur的右

    Node* ppnode = parent->_parent;//记录parent的原父亲节点,用于对cur的父亲进行更新
    parent->_parent = cur;//更新parent的父亲

    //对cur的父亲进行更新
    if (parent == _root)//parent即为根节点
    {
        _root = cur;
        cur->_parent = nullptr;//那么cur作为新的根,其父亲为空
    }
    else//parent是子树
    {
        if (ppnode->_left == parent)
        {
            ppnode->_left = cur;
        }
        else
        {
            ppnode->_right = cur;
        }
        cur->_parent = ppnode;
    }

    cur->_bf = parent->_bf = 0;//更新完后,平衡因子一定为0
}
左单旋

image-20231019153929844

左单旋的核心操作与右单旋的核心操作正好是镜像的

左单旋的核心操作:把cur的左给parent的右,再把parent给到cur的左

代码:

void RotateL(Node* parent)
{
    Node* cur = parent->_right;
    Node* curleft = cur->_left;

    parent->_right = curleft;//把cur的左给parent的右
    if (curleft)//如果cur的左存在,则更新其父亲
    {
        curleft->_parent = parent;
    } 
    cur->_left = parent;//再把parent给到cur的左

    Node* ppnode = parent->_parent;//记录parent的原父亲节点,用于对cur的父亲进行更新

    parent->_parent = cur;

    //对cur的父亲进行更新
    if (parent == _root)
    {
        _root = cur;
        cur->_parent = nullptr;
    }
    else//说明左旋的部分只是某棵树的局部
    {
        if (ppnode->_left == parent)
        {
            ppnode->_left = cur;
        }
        else
        {
            ppnode->_right = cur;
        }
        cur->_parent = ppnode;
    }

    cur->_bf = parent->_bf = 0;
}
右左双旋

image-20231019155022899

先对90进行右单旋,再对30进行左单旋

两次旋转。第一次旋转使其变成单纯的右边高,第二次旋转对应的左单旋

双旋代码难的不是旋转,而是平衡因子的更新

从图中可以看到:最终60成了根,60的左孩子给了parent的右边,60的右孩子给了cur的左边

因此平衡因子的更新分为两种情况:

  • h == 0:那么60则作为新插入的节点,此时60的bf == 0,那么parent和cur的bf也一定为0
  • h>=0:
    • 假如新结点插入在60的左边,即60的bf == -1。那么最终parent的bf == 0,cur的bf == 1
    • 假如新结点插入在60的右边,即60的bf == 1。那么最终parent的bf == -1,cur的bf == 0
    • 而60作为根节点最终bf一定为0

代码:

void RotateRL(Node* parent)
{
    Node* cur = parent->_right;
    Node* curleft = cur->_left;
    int bf = curleft->_bf;

    //先右旋再左旋
    RotateR(cur);
    RotateL(parent);

    //更新平衡因子
    if (bf == 0)
    {
        parent->_bf = 0;
        cur->_bf = 0;
        curleft->_bf = 0;
    }
    else if (bf == 1)
    {
        parent->_bf = -1;
        cur->_bf = 0;
        curleft->_bf = 0;
    }
    else if (bf == -1)
    {
        parent->_bf = 0;
        cur->_bf = 1;
        curleft->_bf = 0;
    }
    else
    {
        assert(false);
    }
}
左右双旋

image-20231019160543333

与右左双旋是镜像的,不再赘述

代码:

void RotateLR(Node* parent)
{
    Node* cur = parent->_left;
    Node* curright = cur->_right;
    int bf = curright->_bf;

    //先左旋再右旋
    RotateL(cur);
    RotateR(parent);

    //更新平衡因子
    if (bf == 0)
    {
        parent->_bf = 0;
        cur->_bf = 0;
        curright->_bf = 0;
    }
    else if (bf == 1)
    {
        parent->_bf = 0;
        cur->_bf = -1;
        curright->_bf = 0;
    }
    else if (bf == -1)
    {
        parent->_bf = 1;
        cur->_bf = 0;
        curright->_bf = 0;
    }
    else
    {
        assert(false);
    }
}
AVL树插入的完整代码
bool Insert(const pair<K, V>& kv)
{
    //先帮助插入节点找到正确位置
    if (_root == nullptr)//树为空
    {
        _root = new Node(kv);
        return true;
    }

    Node* cur = _root;
    Node* parent = nullptr;
    while (cur)
    {
        //插入节点大于根节点
        if (kv.first > cur->_kv.first)
        {
            parent = cur;
            cur = cur->_right;
        }
        else if (kv.first < cur->_kv.first)//插入节点小于根节点
        {
            parent = cur;
            cur = cur->_left;
        }
        else//所插入节点已经存在
        {
            return false;
        }
    }

    cur = new Node(kv);
    if (cur->_kv.first < parent->_kv.first)
    {
        parent->_left = cur;
    }
    else
    {
        parent->_right = cur;
    }

    cur->_parent = parent;

    //控制平衡因子
    while (parent)//
    {
        if (cur == parent->_left)//节点插入在父亲左边
        {
            parent->_bf--;
        }
        else if (cur == parent->_right)//节点插入在父亲右边
        {
            parent->_bf++;
        }

        //进一步判断祖先节点的平衡因子
        if (parent->_bf == 0)//父亲的平衡因子为0,循环结束
        {
            break;
        }
        else if (parent->_bf == 1 || parent->_bf == -1)//父亲的平衡因子为1或-1,则需要继续向上调整
        {
            cur = parent;
            parent = cur->_parent;
        }
        else if (parent->_bf == 2 || parent->_bf == -2)//父亲的平衡因子为2或-2,则需要旋转,且选择完后树一定平衡,故结束循环
        {
            //左单旋
            if (parent->_bf == 2 && cur->_bf == 1)
            {
                RotateL(parent);
            }
            //右单旋
            if (parent->_bf == -2 && cur->_bf == -1)
            {
                RotateR(parent);
            }
            //右左双旋
            if (parent->_bf == 2 && cur->_bf == -1)
            {
                RotateRL(parent);
            }
            //左右双旋
            if (parent->_bf == -2 &&	 cur->_bf == 1)
            {
                RotateLR(parent);
            }

            break;
        }
        else//其他情况,此时说明在插入之前树就已经不是平衡树了
        {
            assert(false);
        }
    }

}

//左单旋
void RotateL(Node* parent)
{
    Node* cur = parent->_right;
    Node* curleft = cur->_left;

    parent->_right = curleft;
    if (curleft)
    {
        curleft->_parent = parent;
    } 
    cur->_left = parent;

    Node* ppnode = parent->_parent;//记录parent的原父亲节点

    parent->_parent = cur;

    //对cur的父亲进行更新
    if (parent == _root)
    {
        _root = cur;
        cur->_parent = nullptr;
    }
    else//说明左旋的部分只是某棵树的局部
    {
        if (ppnode->_left == parent)
        {
            ppnode->_left = cur;
        }
        else
        {
            ppnode->_right = cur;
        }
        cur->_parent = ppnode;
    }

    cur->_bf = parent->_bf = 0;
}

//右单旋
void RotateR(Node* parent)
{
    Node* cur = parent->_left;
    Node* curright = cur->_right;

    //让右节点接到parent的左边,再将parent接到cur的右边
    parent->_left = curright;
    if (curright)
    {
        curright->_parent = parent;
    }
    cur->_right = parent;

    Node* ppnode = parent->_parent;//记录parent的原父亲节点
    parent->_parent = cur;

    //对cur的父亲进行更新
    if (parent == _root)
    {
        _root = cur;
        cur->_parent = nullptr;
    }
    else//说明右旋的部分只是某棵树的局部
    {
        if (ppnode->_left == parent)
        {
            ppnode->_left = cur;
        }
        else
        {
            ppnode->_right = cur;
        }
        cur->_parent = ppnode;
    }

    cur->_bf = parent->_bf = 0;
}

//右左双旋
void RotateRL(Node* parent)
{
    Node* cur = parent->_right;
    Node* curleft = cur->_left;
    int bf = curleft->_bf;

    //先右旋再左旋
    RotateR(cur);
    RotateL(parent);

    //更新平衡因子
    if (bf == 0)
    {
        parent->_bf = 0;
        cur->_bf = 0;
        curleft->_bf = 0;
    }
    else if (bf == 1)
    {
        parent->_bf = -1;
        cur->_bf = 0;
        curleft->_bf = 0;
    }
    else if (bf == -1)
    {
        parent->_bf = 0;
        cur->_bf = 1;
        curleft->_bf = 0;
    }
    else
    {
        assert(false);
    }
}

//左右双旋
void RotateLR(Node* parent)
{
    Node* cur = parent->_left;
    Node* curright = cur->_right;
    int bf = curright->_bf;

    //先左旋再右旋
    RotateL(cur);
    RotateR(parent);

    //更新平衡因子
    if (bf == 0)
    {
        parent->_bf = 0;
        cur->_bf = 0;
        curright->_bf = 0;
    }
    else if (bf == 1)
    {
        parent->_bf = 1;
        cur->_bf = -1;
        curright->_bf = 0;
    }
    else if (bf == -1)
    {
        parent->_bf = 1;
        cur->_bf = 0;
        curright->_bf = 0;
    }
    else
    {
        assert(false);
    }
}

AVL树的验证

验证AVL树分为两步:

  • 验证其为二叉搜索树:如果中序遍历历可得到一个有序的序列,就说明为二叉搜索树(这里就不详细介绍了,详情可以看二叉搜索树那里)

  • 验证其为平衡树:

    • 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
    • 节点的平衡因子是否计算正确
    int TreeHeight(Node* root)
    {
        if (root == nullptr)
            return 0;
    
        int leftHeight = TreeHeight(root->_left);
        int rightHeight = TreeHeight(root->_right);
    
        return rightHeight > leftHeight ? rightHeight + 1: leftHeight + 1;
    }
    
    bool IsBalance()//两个IsBalance构成重载
    {
        return IsBalance(_root);
    }
    
    bool IsBalance(Node* root)
    {
        if (root == nullptr)
            return true;
    
        int leftHeight = TreeHeight(root->_left);
        int rightHeight = TreeHeight(root->_right);
    
        if (rightHeight - leftHeight != root->_bf)
        {
            cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;
            return false;
        }
    
        return abs(rightHeight - leftHeight) < 2
            && IsBalance(root->_left)
            && IsBalance(root->_right);
    }
    

    这里给出一个调试技巧:

    image-20231018182647226假如我运行出现了下面的情况

    image-20231018182657070

    我们现在知道在插入11的时候出了问题,那么就可以针对e=11时进行调试

    但如果现在有100个值,我在第99个值才出现问题,那是不是需要按F10按99次呢?

    两种方法,:

    • 一个是利用条件断点

      image-20231018183053645

    • 还一个是我们自己写代码让它停到想停的地方

      image-20231018183141199

      这里的int x=0;是随便写的,目的是能让断点在这里停下来。因为断点打在空行上是停不住的

      当然,我停在断点处不是为了调int x=0;,而是为了调下面

AVL树的删除

了解即可

image-20231019171629540

总结

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

但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。

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

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

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

相关文章

数据结构:AVL树的实现和全部图解

文章目录 为什么要有AVL树什么是AVL树AVL树的实现元素的插入平衡因子的更新AVL树的旋转 AVL树的检查完整实现 本篇总结的是AVL树中的全部内容&#xff0c;配有详细的图解过程 为什么要有AVL树 前面对map/multimap/set/multiset进行了简单的介绍&#xff0c;在其文档介绍中发现…

计算机毕业设计java+springboot+vue的旅游攻略平台

项目介绍 本系统结合计算机系统的结构、概念、模型、原理、方法&#xff0c;在计算机各种优势的情况下&#xff0c;采用JAVA语言&#xff0c;结合SpringBoot框架与Vue框架以及MYSQL数据库设计并实现的。员工管理系统主要包括个人中心、用户管理、攻略管理、审核信息管理、积分…

Go 接口-契约介绍

Go 接口-契约介绍 文章目录 Go 接口-契约介绍一、接口基本介绍1.1 接口类型介绍1.2 为什么要使用接口1.3 面向接口编程1.4 接口的定义 二、空接口2.1 空接口的定义2.2 空接口的应用2.2.1 空接口作为函数的参数2.2.2 空接口作为map的值 2.3 接口类型变量2.4 类型断言 三、尽量定…

Day22力扣打卡

打卡记录 替换子串得到平衡字符串&#xff08;滑动窗口&#xff09; 链接 由于是以后统计替换的子串&#xff0c;不可以直接使用hash表统计的每个次数大于 n / 4 的字符&#xff0c;再将其次数减去平衡数来得到答案&#xff0c;根据字符串的连贯性&#xff0c;使用 滑动窗口 …

MySQL 8.0 如何修改密码安全策略!!!

目录 安全策略参数和常见等级:1.Mysql8.X常见安全策略参数指定密码的强度验证等级validate_password.policy 取值&#xff1a; 解决步骤1.登录mysql2.修改安全策略(1)语法如下:(2)修改完可以看一下&#xff1a; 3.改完密码策略&#xff0c;就可以根据自己修改的策略&#xff0c…

pytorch复现_UNet

什么是UNet U-Net由收缩路径和扩张路径组成。收缩路径是一系列卷积层和汇集层&#xff0c;其中要素地图的分辨率逐渐降低。扩展路径是一系列上采样层和卷积层&#xff0c;其中特征地图的分辨率逐渐增加。 在扩展路径中的每一步&#xff0c;来自收缩路径的对应特征地图与当前特征…

什么是分治算法?

分治算法(divide and conquer algorithm)是指把大问题分割成多个小问 题&#xff0c;然后把每个小问题分割成多个更小的问题&#xff0c;直到问题的规模小到能够 轻易解决。这种算法很适合用递归实现&#xff0c;因为把问题分割成多个与自身相 似的小问题正对应递归情况&#x…

Java —— 类和对象(一)

目录 1. 面向对象的初步认知 1.1 什么是面向对象 1.2 面向对象与面向过程 2. 类定义和使用 2.1 认识类 2.2 类的定义格式 3. 类的实例化(如何产生对象) 3.1 什么是实例化 3.2 访问对象的成员 3.3 类和对象的说明 4. this引用 4.1 为什么要有this引用 4.2 什么是this引用 4.3 th…

无线发射芯片解决方案在智能家居中的应用

随着物联网的发展&#xff0c;智能家居已经成为一个热门话题。智能家居利用无线技术来实现设备之间的互联互通&#xff0c;提供更智能、更便利的生活体验。无线发射芯片解决方案在智能家居中扮演着关键的角色&#xff0c;它们为智能家居设备之间的通信提供了稳定、高效的连接&a…

stm32f103+HC-SR04+ssd1306实现超声波测距

&#x1f64c;秋名山码民的主页 &#x1f602;oi退役选手&#xff0c;Java、大数据、单片机、IoT均有所涉猎&#xff0c;热爱技术&#xff0c;技术无罪 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; 获取源码&#xff0c;添加WX 目录 前言HC…

【江协科技-用0.96寸OLED播放知名艺人打篮球视频】

Python进行视频图像处理&#xff0c;通过串口发送给stm32&#xff0c;stm32接收数据&#xff0c;刷新OLED进行显示。 步骤&#xff1a; 1.按照接线图连接好硬件 2.把Keil工程的代码下载到STM32中 3.运行Python代码&#xff0c;通过串口把处理后的数据发送给STM32进行显示 …

Spark 新特性+核心回顾

Spark 新特性核心 本文来自 B站 黑马程序员 - Spark教程 &#xff1a;原地址 1. 掌握Spark的Shuffle流程 1.1 Spark Shuffle Map和Reduce 在Shuffle过程中&#xff0c;提供数据的称之为Map端&#xff08;Shuffle Write&#xff09;接收数据的称之为Reduce端&#xff08;Sh…

Leetcode刷题详解——组合

1. 题目链接&#xff1a;77. 组合 2. 题目描述&#xff1a; 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;n 4, k 2 输出&#xff1a; [[2,4],[3,4],[2,3],[1,2],[1,3],[…

vue3拖拽排序——vuedraggable

文章目录 安装代码效果拖拽前拖拽时拖拽后 vue3 的拖拽排序博主用的是 vuedraggable 安装 安装 npm i vuedraggable4.1.0 --save 引用 import Draggable from vuedraggable;代码 html <van-checkbox-group v-model"dataMap.newsActionChecked"><van-cell…

LazyVim: 将 Neovim 升级为完整 IDE | 开源日报 No.67

curl/curl Stars: 31.5k License: NOASSERTION Curl 是一个命令行工具&#xff0c;用于通过 URL 语法传输数据。 核心优势和关键特点包括&#xff1a; 可在命令行中方便地进行数据传输支持多种协议 (HTTP、FTP 等)提供丰富的选项和参数来满足不同需求 kubernetes/ingress-n…

项目中登录验证码怎么做才合理

唠嗑部分 今天我们来聊聊项目实战中登录验证码如何做比较合理&#xff0c;首先我们聊以下几个问题 1、登录时验证码校验是否必要&#xff1f; 答案当然是很有必要的&#xff0c;因为用户登录行为会直接影响数据库&#xff0c;如果没有某些防范措施&#xff0c;有恶意用户暴力…

NOIP2023模拟12联测33 A. 构造

NOIP2023模拟12联测33 A. 构造 文章目录 NOIP2023模拟12联测33 A. 构造题目大意思路code 题目大意 构造题 思路 想一种构造方法&#xff0c;使得 y y y 能够凑成尽可能多的答案 第一行 x y r y ⋯ r xyry \cdots r xyry⋯r 第二行 r y x y ⋯ x ryxy \cdots x ryxy⋯x …

基于SSM的出租车管理系统

基于SSM的出租车管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringSpringMVCMyBatis工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 登录界面 管理员界面 驾驶员界面 摘要 基于SSM&#xff08;Spring、Spring MVC、My…

软考 -- 计算机学习(3)

文章目录 一、软件测试基础1.1 基本概念1.2 软件测试模型1.3 软件测试的分类 二、基于规格说明的测试技术(黑盒)2.1 重要的测试方法1. 等价类划分法2. 边界值法3. 判定表法4. 因果图法 2.2 其他测试方法 三、基于结构的测试技术(白盒)3.1 静态测试3.2 动态测试 一、软件测试基础…

Vue Vuex模块化编码

正常写vuex的index的时候如果数据太多很麻烦&#xff0c;如有的模块是管理用户信息或修改课程等这两个是不同一个种类的&#xff0c;如果代码太多会造成混乱&#xff0c;这时候可以使用模块化管理 原始写法 如果功能模块太多很乱 import Vue from vue import Vuex from vuex …