c++ 实现 AVL 树

news2024/11/29 4:38:32

在这里插入图片描述

AVL 树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家 G.M.Adelson-Velskii 和 E.M.Landis 在 1962 年发明了一种解决上述问题的方法:

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

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

  • 它的左右子树都是AVL树。

  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)。

    在这里插入图片描述

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

AVL 树的基本结构

AVL 树的实现方式有很多,我们这里选择一种较为简单的方式:引入平衡因子来维护 AVL 树的高度。因为在实现 AVL 树的时候,涉及调整树的高度,此过程需要频繁的通过子节点找到父节点,所以 AVL 树的节点需要定义成三叉链式存储。

AVL 树存储的数据我们定义成 key-val 键值对的形式,因为 map 和 set 存储的数据就是键值对的形式,而 map 和 set 又和 AVL 树有一定的关系。所以我们只需要模仿着做就行啦!

#pragma once

template<class K, class V>
struct AVLTreeNode
{
    pair<K, v> _kv;
    AVLTreeNode<K, V>* _parent;
    AVLTreeNode<K, V>* _left;
    AVLTreeNode<K, V>* _right;
    int _bf; //平衡因子

    AVLTreeNode(const pair<K, V>& kv)
    :_kv(kv)
    ,_parent(nullptr)
    ,_left(nullptr)
    ,_right(nullptr)
    {}
};

template<class K, class V>
class AVLTree
{
public:
    typedef AVLTreeNode<K, V> Node;
public:
    AVLTree()
        :_root(nullptr)
    {}

private:
    Node* _root;
};

这里解释一下 pair 是个啥?

pair 是 C++ 标准库里面的一个模板类,类中维护了两个变量。通过对象可以直接访问维护的两个变量。第一个通过 对象.first 访问;第二个通过 对象.second 访问。你姐可以看作是 C 语言的结构体,结构体中有两个变量,仅此而已!

在这里插入图片描述

bool insert(const pair<K, V>& kv)

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。

那么 AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式插入新节点。
  2. 调整节点的平衡因子。

一个节点的平衡因子可以有两种计算方式:

  • 该节点左子树的高度减去右子树的高度。
  • 该节点右子树的高度减去左子树的高度。

我们选择使用第一种方式哈,用左子树的高度减去右子树的高度来计算一个节点的平衡因子。其实用哪种都无所谓的。

废话不多说直接看插入节点的时候有哪些情况:

插入节点之后无需旋转

AVL 树为什么需要旋转?因为 AVL 树中的每个节点的平衡因子只能是 0,1,或者 -1。一旦我们插入一个新的节点,向上更新平衡因子的过程中,发现某个节点的平衡因子不满足这个要求,那么这就不是一颗 AVL 树了,就必须进行旋转,使得整棵树重新满足 AVL 树的性质。

插入一个节点之后,一定会涉及到平衡因子的更新,那么我们就得弄清楚下面这两个问题:

问题一:如何更新平衡因子?

这个问题比较简单哈,根据我们实现的 AVL 树平衡因子的定义:左子树的高度减去右子树的高度。那么如果新插入 的节点是父节点的左孩子,那么父节点的平衡因子就要加一,如果新插入的节点是父节点的右孩子,那么父节点的平衡因子就要减一。

问题二:什么时候不用继续向上更新平衡因子了呢?

当向上更新平衡因子的过程中,如果父节点的平衡因子为 0 了,就不需要继续更新平衡因子了!这是为什么呢?这就得弄清楚为什么插入一个节点需要向上更新平衡因子了!需要更新平衡因子是因为插入一个新节点之后,改变了父节点某颗子树的高度,父节点的某一颗子树的高度改变了,那么当然就需要更新平衡因子啦!因为平衡因子是用左子树的高度减去右子树的高度嘛。相反,如果更新平衡因子的过程中发现,并没有改变父节点的某颗子树的高度,当然就不必继续向上更新平衡因子啦!

表现在平衡因子上就是:父节点的平衡因子由 -1 或者 1 变成了 0。父节点的平衡因子由 -1 或者 1 变为 0,说明插入一个新节点之后,父节点的左右子树高度平衡了。但是以父节点为根节点的树整体高度并没有发生变化,因此以父节点作为一颗左子树或者右子树向上更新平衡因子已经是无意义的行为了。

在这里插入图片描述

在上面的插入例子中,我们观察到,更新平衡因子的过程中,如果 parent 的平衡因子更新之后为 0,再向上更新平衡因子已经没有意义了!原因最开始已经讲得很清楚啦!

插入节点之后进行左单旋

插入的过程要是都像上面那样只需要更新更新平衡因子就好了,但是那是不可能的!当我们插入新节点向上更新平衡因子的过程中 parent 的平衡因子出现了 -2 或者 2 的情况,那么就无法单纯通过更新平衡因子来实现插入了。就需要旋转节点使得整棵树再次满足 AVL 树的性质啦!

在这里插入图片描述

在上面的例子中插入一个新的节点 17 在向上更新平衡因子的过程中发现 parent 的平衡因子为 -2,这不满足 AVL 树的性质,需要通过旋转来解决。

我们观察这种情况:11,15,17 三个节点呈现一条直线,并且是从左上角到右下角的直线,这个时候就我们要以 parent 为旋转点进行左单旋。

表现在平衡因子上:parent 的平衡因子为 -2,cur 的平衡因子为 -1,进行左单旋。

左单旋的关键步骤:

parent->_right = cur->_left;
cur->left = parent;

在这里插入图片描述

在上面的例子中,5 链接到 15 显然不是必须的,如果旋转之前父节点的 _parent 就是 nullptr,则不会有这一步。旋转完成后将 _parent 和 _cur的平衡因子改成 0,就完成了新节点的插入。至于为什么可以直接改成 0,等用抽象图来讲解AVL树的插入时会说明原因。

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

    parent->_right = curLeft; //左单旋的核心步骤之一

    if (curLeft) //如果curLeft不为空才需要向上链接,我们讲解AVL树的插入。curLeft 全为空,就没有进行链接
        curLeft->_parent = parent;

    cur->_left = parent; //左单旋的核心操作步骤之二

    //记录parent的父节点,方便后续进行判断,确定链接的方式
    Node* ppnode = parent->_parent;

    parent->_parent = cur;


    //处理一下特殊情况, 还可以使用 ppnode 是不是等于 nullptr 来判断
    if (parent == _root) //parent 就是旋转之前的根节点,那么就需要更新根节点
    {
        _root = cur;
        cur->_parent = nullptr;
    }
    else //如果不是的话,就需要判断parent位于其父节点的左侧还是右侧,进行对应的链接即可
    {
        if (ppnode->_left == parent)
        {
            ppnode->_left = cur; //parent位于ppnode的左侧
        }
        else
        {
            ppnode->_right = cur; //parent 位于ppnode 的右侧
        }
        cur->_parent = ppnode;
    }

    //更新平衡因子
    cur->_bf = parent->_bf = 0;
}

插入节点之后进行右单旋

右单旋和左单旋相差不大呢!右单旋看起来就是一条从右上角到左下角的直线。

表现在平衡因子上:parent 的平衡因子为 2,cur 的平衡因子为 1,进行右单旋。

在这里插入图片描述

右单旋的操作与左单旋同样也相差不大呢!对称的嘛。

parent->_left = cur->_right;
cur->_right = parent;

在这里插入图片描述

同左单旋,5 链接到 1 也不是必须的,如果旋转之前父节点的 _parent 就是 nullptr,就不会有这一步。但是需要更新根节点为 cur,并让 cur 的 _parent 指向空。如果父节点的parent不为空就需要判断 parent 是的 parent 的父节点的左孩子还是右孩子,对 cur 进行不同方式的链接。右单旋后将 cur 与 parent 的平衡因子变为0。至于为什么后面讲。

void RotateR(Node* parent)
{
    Node* cur = parent->_left;
    Node* curRight = cur->_right;

    parent->_left = curRight; //右单旋的核心操作之一

    //判断一下 curRight 是不是 nullptr,只有不是 nullptr 才需要向上链接 parent
    if (curRight)
        curRight->_parent = parent;


    //提前记录一下 parent 的父节点
    Node* ppnode = parent->_parent;

    cur->_right = parent; //右单旋的核心步骤之二


    parent->_parent = cur; //向上链接父节点

    //判断parent是不是根节点, 判断方式依然是有两种哈,一种是判断ppnode是不是等于nullptr
    //                                          另一种是判断 parent 是不是等于 _root
    if (ppnode == nullptr)
    {
        _root = cur;
        cur->_parent = nullptr; //记得将根节点的父节点置为nullptr
    }
    else
    {
        //如果不是根节点那么就需要你判断 parent 处于 ppnode 的什么位置
        if (ppnode->_left == parent)
        {
            //位于左侧就链接左侧
            ppnode->_left = cur;
        }
        else
        {
            //位于右侧当然就链接右侧啦
            ppnode->_right = cur;
        }
        cur->_parent = ppnode;
    }

    //更新平衡因子
    cur->_bf = parent->_bf = 0;
}

插入节点之后进行左右双旋

其实只要弄清楚一个旋转,其他的旋转就很好理解了!我么来看看需要进行左右双旋的插入例子:

在这里插入图片描述

我们看到需要左右双旋的形状就是一个折线,从右上到左下再到右下。

表现在平衡因子上:parent 的平衡因子为 2,cur 的平衡因子为 -1,进行左右双旋。

这种情况单旋是解决不了问题的,需要分两步操作:

  • 以 cur 为旋转点进行左单旋。
  • 以 parent 为旋转点进行右单旋。

在这里插入图片描述

双旋更新平衡因子的策略会在 AVL 抽象图讲解中说明。

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

    int curRightBf = curRight->_bf; //我们先记录一下 curRightBf 的平衡因子,因为左单旋会修改平衡因子嘛, 还可以用来判断新插入的节点位于 curRight 的位置

    RotateL(cur); //以 cur 为旋转点进行左单旋
    RotateR(parent); //以 parent 为旋转点进行右单旋

    //更新平衡因子,我们需要判断新插入的节点位于他的左侧还是右侧,直接利用平衡因子判断哈
    if (curRightBf == 0)
    {
        parent->_bf = 0;
        cur->_bf = 0;
        curRight->_bf = 0;
    }
    else if (curRightBf == 1) //新插入的节点位于 curRight 的左侧
    {
        parent->_bf = -1;
        cur->_bf = 0;
        curRight->_bf = 0;
    }
    else if (curRightBf == -1)//新插入的节点位于 curRight 的右侧
    {
        parent->_bf = 0;
        cur->_bf = 1;
        curRight->_bf = 0;
    }

}

插入节点之后进行右左双旋

左单旋你回了,右单旋你也会了,左右双旋你也会了!接下来的右左双旋我相信你也一定会的!这里就不在制作动图演示过程啦!

右单旋的形状也是一个折线哈,从左上到右下再到左下角的折线。

表现在平衡因子上:parent 的平衡因子为 -2,cur 的平衡因子为 1,进行右左双旋。

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

    int curLeftBf = curLeft->_bf; //我们先记录一下 curLeftBf 的平衡因子,因为右单旋会修改平衡因子嘛, 还可以用来判断新插入的节点位于 curLeft 的位置

    RotateR(cur); //以 cur 为旋转点进行右单旋
    RotateL(parent); //以 parent 为旋转点进行左单旋

    //更新平衡因子,我们需要判断新插入的节点位于他的左侧还是右侧,直接利用平衡因子判断哈
    if (curLeftBf == 0) //curLeftBf为 0 说明 抽象图中的 b 和 c 均是空树,经过左右双旋之后这三个节点的平衡因子其实已经被修改成为了 0 但是为了解耦嘛,还是需要判断一下
    {
        parent->_bf = 0;
        cur->_bf = 0;
        curLeft->_bf = 0;
    }
    else if (curLeftBf == 1) //新插入的节点位于 curLeft 的左侧
    {
        parent->_bf = 0;
        cur->_bf = -1;
        curLeft->_bf = 0;
    }
    else if (curLeftBf == -1)//新插入的节点位于 curLeft 的右侧
    {
        parent->_bf = 1;
        cur->_bf = 0;
        curLeft->_bf = 0;
    }
}

AVL 树插入抽象图讲解

上面我们都是拿具体的例子来理解AVL树的插入,你可能会说,这能代表一切情况嘛?

下面我们会用抽象图讲解 AVL 树的插入,让我们一起来看看插入是不是这么个事儿。

我们来看左单旋的抽象图:这个图是怎么抽象的呢?

  • 节点值为 8 和 6 的两个节点只是一个代表,其值可以是满足 AVL 树的任意值。
  • 这里的 8 不一定是根节点,有可能只是一颗 AVL 树中的一部分。
  • a,b,c 均表示一颗子树。
  • 下图中在 c 下方插入一个节点,更新平衡因子时,在 8 这个节点处检测到了异常的平衡因子,截取了这个节点下面的所有节点。

用这个抽象图能将所有左单旋的情况抽象出来:

在这里插入图片描述

左单旋之后的结果:

在这里插入图片描述

我们看到左单旋之后,parent 的左右子树高度相同,cur 的左右子树的高度相同。这就是为什可以在左单旋之后能够直接将 cur 和 parent 的平衡因子更新为 0 的原因。

我们再来看右单旋的抽象图

在 a 子树中插入一个节点,向上更新平衡因子的过程中,parent 的平衡因子出现异常。

在这里插入图片描述

进行右单旋:

在这里插入图片描述

同样的,我们发现进行右单旋之后,parent 的左右子树高度相等,cur 的左右子树高度相等,这就是为什么能够在进行右单旋之后直接将 parent 和 cur 的平衡因子更新为 0 的原因。

我们再来看左右双旋的抽象图

如下图:我们可以看到想要发生左右双旋,必然是在 b 或者 c 所在的子树插入一个新节点,使得 3 的平衡因子变为 -1,9 的平衡因子变成 2,符合左右双旋特征(插入在 a 下方是右单旋,插入在 d 下方不用旋转)

在这里插入图片描述

在这里插入图片描述

我们可以看到左右双旋其实就是将 b 这个子树给给了 3 的右侧,c 这个子树给给了 9 的左侧,然后 3,9 分别充当 6 的左右子树。根据新节点插入如位置的不同,平衡因子的更新略有不同,因此我们需要判断新插入的节点位于 6 这个节点的左侧还是右侧。

  • 如果在左侧:
    • parent->_bf = -1。
    • cur->_bf = 0。
    • son->_bf = 0;
  • 如果在右侧:
    • parent->_bf = 0。
    • cur->_bf = 1。
    • son->_bf = 0。

接下来我们来看右左双旋:

我们画出了左单旋的抽象图,右单旋的抽象图,左右单旋的抽象图,那么你肯定能画出右左双旋的抽象图。这里就补在绘图了!

检查 AVL 树插入是否正确

我们可以写几个函数来判断我们实现的 AVL 树的插入函数是否正确,判断的依据就是 AVL 树自身的性质:

  • 对于每一个节点,求出其左右子树的高度,相减之后与节点存储的平衡因子作比较,如果不同则 AVL 树的插入出问题了。
  • 左右子树高度之差的绝对值不大于 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;
        }
        //检查 AVL 树是否平衡
        bool IsBalance()
        {
            return IsBalance(_root);
        }
        bool IsBalance(Node* root)
        {
            if (root == nullptr)
                return true;

            int leftHight = Height(root->_left);
            int rightHight = Height(root->_right);
            if (leftHight - rightHight != root->_bf)
            {
                cout << "平衡因子异常:" << root->_kv.first << "平衡因子:" << root->_bf << endl;
                return false;
            }

            return abs(rightHight - leftHight) < 2
                && IsBalance(root->_left)
                && IsBalance(root->_right);
        }

代码

bool insert(const pair<K, V>& kv)
{
    if (_root == nullptr)
    {
        _root = new Node(kv);
        return true;
    }
    else
    {
        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);
        //新节点的插入位置,parent 的左孩子还是parent的右孩子
        if (parent->_kv.first < kv.first)
        {
            parent->_right = cur;
        }
        else
        {
            parent->_left = cur;
        }
        //链接一下父节点
        cur->_parent = parent;
        //更新平衡因子
        while (parent)
        {
            //根据你的平衡因子ID定义来,如果你定义的平衡因子时左子树的高度减去右子树的高度
            //就跟我写的代码一样,如果你的平衡因子的定义是右子树的高度减去左子树的高度
            //parent 的平衡因子的跟新就跟写的相反就行了
            //如果cur在parent 的左侧,parent 的平衡因子加加
            if (cur == parent->_left)
            {
                parent->_bf++;
            }
            else //在右侧parent 的平衡因子减减
            {
                parent->_bf--;
            }

            //更新一次就要进行一次判断
            if (parent->_bf == 0)
            {
                break; //如果更新之后 parent 的平衡因子变成了0,直接结束循环
            }
            else if (parent->_bf == 1 || parent->_bf == -1) //继续向上跟新平衡因子
            {
                cur = parent;
                parent = parent->_parent;
            }
            else if (parent->_bf == 2 || parent->_bf == -2)
            {
                //AVL不平衡了,需要旋转
                if (parent->_bf == -2 && cur->_bf == -1)
                {
                    //以 parent 为旋转点进行左单旋
                    RotateL(parent);
                }
                else if (parent->_bf == 2 && cur->_bf == 1)
                {
                    //以 parent 为旋转点进行右单旋
                    RotateR(parent);
                }
                else if (parent->_bf == 2 && cur->_bf == -1)
                {
                    //先以 cur 为旋转电进行左单旋,再以 parent 为旋转点进行右单旋
                    RotateLR(parent);
                }
                else if (parent->_bf == -2 && cur->_bf == 1)
                {
                    //先以 cur 为旋转电进行右单旋,再以 parent 为旋转点进行左单旋
                    RotateRL(parent);
                }
                //旋转之后 AVL 树一定是平衡的,因此直接可以退出更新平衡因子的循环啦
                break;
            }
            else
            {
                //平衡因子如果出现上述枚举情况之外的情况那么一定是程序除了问题哈。
                //直接暴力结束程序
                assert(false);
            }

        }
    }
    return true;
}

bool erase(const pair<K, V>& kv)

AVL 树的删除比插入还复杂一些,这里就不做讲解了!有兴趣的 uu 可以自己去了解一下。面试的时候顶多考 AVL 树的插入呢~~😸
在这里插入图片描述

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

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

相关文章

实现Git增量代码的Jacoco覆盖率统计

今天我们给大家分享&#xff0c;如何使用Jacoco集合Git来做增量代码的覆盖率测试。实现的基本原理是&#xff1a; 使用Git的diff指令&#xff0c;计算出两个版本的差异&#xff1b;改造Jacoco源代码&#xff0c;只针对增量代码生成报告。 基本的功能滴滴的super-jacoco项目已…

使用Selenium IDE录制脚本

今天&#xff0c;我们开始介绍基于开源Selenium工具的Web网站自动化测试。 Selenium包含了3大组件&#xff0c;分别为&#xff1a;1. Selenium IDE 基于Chrome和Firefox扩展的集成开发环境&#xff0c;可以录制、回放和导出不同语言的测试脚本。 2. WebDriver 包括一组为不同…

2023年道路运输企业主要负责人证考试题库及道路运输企业主要负责人试题解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年道路运输企业主要负责人证考试题库及道路运输企业主要负责人试题解析是安全生产模拟考试一点通结合&#xff08;安监局&#xff09;特种作业人员操作证考试大纲和&#xff08;质检局&#xff09;特种设备作业人…

助力企业数智化转型,网易数帆是这样做的

伴随着云计算、大数据、人工智能等新兴技术的飞速发展&#xff0c;数字经济在国民经济中的重要性也变得愈发凸显。席卷全球的数字化和智能化浪潮不但深切地改变了人们的工作和生活方式&#xff0c;而且也给企业和组织带来了全新的发展机遇。 然而在数智化转型升级的道路上&…

免费外文文献检索网站,你一定要知道

01. Sci-Hub 网址链接&#xff1a;https://tool.yovisun.com/scihub/ Sci-hub是一个可以无限搜索、查阅和下载大量优质论文的数据库。其优点在于可以免费下载论文文献。 使用方法&#xff1a; 在Sci—hub搜索栏中粘贴所需文献的网址或者DOI&#xff0c;然后点击右侧的open即可…

分库分表自定义路由组件

1. 定义路由注解 Documented Retention(RetentionPolicy.RUNTIME) // Target用来表示注解作用范围&#xff0c;超过这个作用范围&#xff0c;编译的时候就会报错。 // Target(ElementType.TYPE)——接口、类、枚举、注解,Target(ElementType.METHOD)——方法 Target({Elem…

【Qt之事件过滤器】使用

介绍 事件过滤器是Qt中一种重要的机制&#xff0c;用于拦截并处理窗口和其他对象的事件。 它可以在不修改已有代码的情况下&#xff0c;动态地增加、删除一些处理事件的代码&#xff0c;并能够对特定对象的事件进行拦截和处理。 在Qt中&#xff0c;事件处理经过以下几个阶段&…

C++零散问题总结

什么是析构函数? return 0

图解Linux进程优先级

目录 1.什么是进程优先级&#xff1f; 2.进程优先级原理 3.查看进程优先级 4.修改进程优先级 4.1 setpriority函数原型 4.2 getpriority函数原型 4.3 sched_setscheduler函数原型 4.4 sched_getscheduler函数原型 4.5 sched_setparam函数原型 4.6 sched_getparam函数…

终极秘诀:打破无代码状态的小方法

终极秘诀&#xff1a;打破无代码状态的小方法 大家有没有遇到过不想写代码或学习的时候呢&#xff1f;这种情况下&#xff0c;你们会选择放松还是停下来呢&#xff1f;我很好奇大家是怎么度过这段时间的。我个人的情况是&#xff0c;当我不想写代码或学习的时候&#xff0c;我会…

Python基础入门例程39-NP39 字符串之间的比较(运算符)

最近的博文&#xff1a; Python基础入门例程38-NP38 牛牛的逻辑运算&#xff08;运算符&#xff09;-CSDN博客 Python基础入门例程37-NP37 不低于与不超过&#xff08;运算符&#xff09;-CSDN博客 Python基础入门例程36-NP36 谁的数字大&#xff08;运算符&#xff09;-CSD…

Hybrid App(原生+H5)开发

介绍 市面上主流的hybrid app框架主要有 React Native&#xff1a;由FaceBook开发&#xff0c;使用JavaScript和React来构建原生应用程序Flutter&#xff1a;由Google开发&#xff0c;使用Dart语言。Flutter使用自己的渲染引擎Ionic&#xff1a;基于 Web 技术&#xff08;HTM…

探索无限可能:APITable免费开源多维表格与可视化数据库远程访问的魅力

APITable免费开源的多维表格与可视化数据库公网远程访问 文章目录 APITable免费开源的多维表格与可视化数据库公网远程访问前言1. 部署APITable2. cpolar的安装和注册3. 配置APITable公网访问地址4. 固定APITable公网地址 前言 vika维格表作为新一代数据生产力平台&#xff0c…

【Java初阶练习题】-- 数组练习题

数组练习题 1. 创建的数组&#xff0c;并且赋初始值2. 改变原有数组元素的值3. 数组所有元素之和4. 奇数位于偶数之前5.两数之和6. 只出现一次的数字7. 多数元素8. 给你一个整数数组 arr&#xff0c;请你判断数组中是否存在连续三个元素都是奇数的情况&#xff1a;如果存在&…

Kibana中使用Dev Tools控制台创建索index索引同时添加date类型的时间参数(用于根据时间序列展示数据)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

android studio 字节码查看工具jclasslib bytecode viewer

jclasslib bytecode viewer 是一款非常好用的.class文件查看工具&#xff1b; jclasslib bytecode editor is a tool that visualizes all aspects of compiled Java class files and the contained bytecode. Many aspects of class files can be edited in the UI. In addit…

AD教程(六)现有元件模型的调用

AD教程&#xff08;六&#xff09;现有元件模型的调用 导入现有原理图 Altium Schematic Document (.SchDoc) 直接拖入AD即可 直接用现有原理图生成原理图库 点击设计&#xff0c;选择生成原理图库&#xff0c;进入归类设置界面&#xff08;用原理图直接生成原理图库&#xf…

【漏洞复现】Apache_HTTPD_未知后缀名解析

感谢互联网提供分享知识与智慧&#xff0c;在法治的社会里&#xff0c;请遵守有关法律法规 upload-labs/Pass-07 上传1.php文件 <?php eval($_REQUEST[6868]);phpinfo();?>访问/upload/1.php.jaychou 蚁剑连接

(自适应手机端)响应式新闻博客知识类pbootcms网站模板 自媒体运营博客网站源码下载

(自适应手机端)响应式新闻博客知识类pbootcms网站模板 自媒体运营博客网站源码下载 带后台系统PbootCMS内核开发的网站模板&#xff0c;该模板适用于新闻博客网站、自媒体运营网站等企业&#xff0c;当然其他行业也可以做&#xff0c;只需要把文字图片换成其他行业的即可&#…

linux的另一种判断符号【中括号】

由于正在表达式的关系&#xff0c;所以如下 第一、括号内每个组件需要空格分隔 第二、变量最好用双引号 第三、常量最好用单引号或双引号 中括号常用条件判断是 if then fi 为啥发3张图片&#xff0c;因为运行的试试程序报错&#xff0c;说我语法错误“”&#xff0c;可以…