【高阶数据结构】AVL树

文章目录

  • 前言
  • 1. 什么是二叉搜索树
  • 2. 什么是AVL树
  • 3. AVL树节点的定义
  • 4. AVL树的插入
    • 4.1 新节点插入较高右子树的右侧
    • 4.2 新节点插入较高左子树的左侧
    • 4.3 新节点插入较高左子树的右侧
    • 4.4 新节点插入较高右子树的左侧
    • 插入操作完整代码
    • 插入操作总结
  • AVL树的验证
  • AVL树的删除
  • AVL树性能分析

前言

前面我们学习了什么是二叉搜索树,但是由于二叉搜索树的局限性,所以对二叉搜索树进行了改进,出现了 AVL 树,这篇文章,我将为大家分享关于 AVL 树——高度平衡的二叉搜索树。

1. 什么是二叉搜索树

我前面为大家详细介绍了二叉搜索树,大家可以去看看这篇文章二叉搜索树,在这里简单为大家介绍一下什么是二叉搜索树。

二叉搜索树(Binary Search Tree)是一种数据结构,它可以用于存储有序的元素集合。在二叉搜索树中,每个节点都有两个子节点,通常称为左子节点和右子节点。节点的值必须大于其左子树中的任何节点的值,并且必须小于其右子树中的任何节点的值。因此,二叉搜索树也被称为二叉排序树或二叉查找树。

在这里插入图片描述

二叉搜索树的特点是,对于每个节点,其左子树中的所有元素都小于该节点,而右子树中的所有元素都大于该节点。这种结构使得在二叉搜索树中查找特定的元素变得非常快速。

对于高度平衡的二叉搜索树,查询数据的时间复杂度可以达到 O(logN),但是如果该二叉搜索树接近于一个单分支树,那么时间复杂度就会退化到 O(N)。

在这里插入图片描述
所以为了避免出现这种极端的二叉搜索树的情况,就对二叉搜索树进行了优化,出现了 AVL 树,也叫做高度平衡的二叉搜索树。

2. 什么是AVL树

AVL树,也称为自平衡二叉查找树,是一种特殊的二叉查找树,其中任何节点的两个子树的高度差最多为1,也就是平衡因子的绝对值小于等于1。这种平衡性质使得AVL树在插入、删除和查找操作中保持相对稳定,时间复杂度为O(log n)。

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

AVL树得名于其发明者G. M. Adelson-Velsky和E. M. Landis,他们在1962年的论文《An algorithm for the organization of information》中发表了AVL树。

AVL树的特点包括:

  1. 任何节点的两个子树的高度差最多为1,因此也被称为高度平衡树。
  2. 插入和删除操作可能需要通过一次或多次旋转来重新平衡树。
  3. 查找、插入和删除在平均和最坏情况下都是O(log n)。

在AVL树中,旋转操作用于平衡树的左右子树的高度差,使得树保持平衡。根据需要插入或删除节点的位置,可以进行右旋、左旋或左右旋、右左旋等旋转操作。

AVL树的平衡性质使得它在动态数据集中表现良好,广泛应用于数据库、文件系统和缓存等场景。

3. AVL树节点的定义

为了 AVL 树实现简单,AVL 树节点在定义时会维护一个平衡因子:

class TreeNode {
    public int bf; //平衡因子=右子树的高度-左子树的高度
    public int val;
    public TreeNode left; //节点的左孩子
    public TreeNode right; //节点的右孩子
    public TreeNode parent; //节点的双亲节点

    public TreeNode(int val) {
        this.val = val;
    }
}

4. AVL树的插入

AVL 树就是在二叉搜索树的基础上引入了平衡因子,因此 AVL 树也可以看成是二叉搜索树,AVL 树的插入就是在二叉搜索树插入的基础上添加了一个调整平衡因子的操作。

在这里插入图片描述

通过代码体现就是这样的:

public boolean insert(TreeNode root, int data) {
    TreeNode node = new TreeNode(data);
    if (root == null) {
        root = node;
        return true;
    }
    TreeNode cur = root, parent = null; //parent节点用来记录cur节点的双亲节点,为后面的节点插入做准备
    while (cur != null) {
        if (cur.val < data) {
            parent = cur;
            cur = cur.right;
        }else if (cur.val > data) {
            parent = cur;
            cur = cur.left;
        }else {
            return false;
        }
    }
    //遍历到叶子节点之后,根据data和parent节点值的大小关系决定是插入到parent的左子树还是右子树
    if (parent.val < data) {
        parent.right = node;
    }else {
        parent.left = node;
    }
    node.parent = parent;
}

插入新数据之后,对节点的平衡因子进行调整:

在这里插入图片描述

在这里插入图片描述

cur = node;
while (parent != null) {
    if (parent.right == cur) {
        parent.bf++;
    }else {
        parent.bf--;
    }
    //如果插入节点之后的,parent的平衡因子变为0,那么就说明parent在
    //插入这个节点之前就拥有孩子节点,插入节点之后,parent节点拥有
    //左、右孩子,所以该树中的所有节点的平衡因子都不会发生变化。
    if (parent.bf == 0) return true;
    else if (parent.bf == -1 || parent.bf == 1) {
        cur = parent;
        parent = parent.parent;
    }else {
        //节点的平衡因子等于2或者-2的时候,调整节点,使之高度平衡
    }
}

在调整平衡因子的过程中,如果出现了左右孩子高度差的绝对值大于1的情况,就需要对该节点的左右子树进行调整,插入新节点之后,不能保证该二叉搜索树是高度平衡的二叉树,所以就需要我们对不平衡的地方做出调整,那么当前情况该如何对节点进行调整,才能保证该二叉树是一个高度平衡的二叉搜索树呢?

这种情况我们可以对高度不平衡的节点的左右子树进行左旋的操作:

在这里插入图片描述

这只是一种插入情况,下面我们讲所有的需要对节点进行调整的插入情况列举出来。

4.1 新节点插入较高右子树的右侧

在这里插入图片描述

在这里插入图片描述

当新节点插入的是较高的右子树的右侧的时候,也就是 parent.bf = 2,cur .bf= 1 的时候通过左单旋来降低右子树的高度。

public void rotateLeft(TreeNode parent) {
    TreeNode subR = parent.right;
    TreeNode subRL = subR.left;
    TreeNode pParent = parent.parent;
    parent.right = subRL;
    if (subRL != null) {
        subRL.parent = parent;
    }
    subR.left = parent;
    parent.parent = subR;
    //这里判断需要旋转的节点是否为根节点,如果是根节点,那么旋转之后的根节点没有双亲节点
    //如果不是根节点,那么旋转之后的根节点的双亲节点就是原根节点的双亲节点
    if (root == parent) {
        root = subR;
        root.parent = pParent;
    }else {
        if (pParent.left == parent) {
            pParent.left = subR;
        }else {
            pParent.right = subR;
        }
        subR.parent = pParent;
    }
    //调整平衡因子
    subR.bf = parent.bf = 0;
}

调整左右子树的高度之后,再调整平衡因子。

在这里插入图片描述

4.2 新节点插入较高左子树的左侧

在这里插入图片描述

此时需要降低左子树的高度,增加右子树的高度,也就是对该树进行右旋操作:

在这里插入图片描述

通过代码演示就是这样的:

public void rotateRight(TreeNode parent) {
    TreeNode subL = parent.left;
    TreeNode subLR = subL.right;
    parent.left = subLR;
    if (subLR != null) {
        subLR.parent = parent;
    }
    TreeNode pParent = parent.parent;
    if (root == parent) {
        root = subL;
        subL.parent = null;
    }else {
        if (pParent.left == parent) {
            pParent.left = subLR;
        }else {
            pParent.right = subLR;
        }
        subLR.parent = pParent;
    }
    subL.right = parent;
    parent.parent = subL;
    subL.bf = parent.bf = 0;
}

4.3 新节点插入较高左子树的右侧

在这里插入图片描述

这种情况下要想降低左子树的高度,增加右子树的高度,只通过右单旋是无法做到的,当新节点插入较高左子树的右侧的时候,就需要线将 cur 的左右子树进行左单旋,然后再对 parent 的左右子树进行右单旋:

在这里插入图片描述

通过代码实现就是这样的:

public void rotateLR(TreeNode parent) {
    TreeNode subL = parent.left;
    TreeNode subLR = subL.right;
    int bf = subL.bf;
    
    rotateLeft(parent.left);
    rotateRight(parent);

	//根据插入的位置是左子树还是右子树来修改平衡因子
    if (bf == 1) {
        parent.bf = 1;
        subL.bf = 0;
        subLR.bf = 0;
    }else {
        parent.bf = 0;
        subL.bf = -1;
        subLR.bf = 0;
    }
}

4.4 新节点插入较高右子树的左侧

在这里插入图片描述

这种情况下,只对 parent 的左右子树进行左单旋的话,是无法保证平衡的,当新插入的节点在较高右子树的左侧的时候,需要先对 subR 的左右子树进行右旋,然后对 parent 的左右子树进行左旋:

在这里插入图片描述

通过代码体现就是这样的:

public void rotateRL(TreeNode parent) {
    TreeNode subR = parent.right;
    TreeNode subRL = subR.left;
    int bf = subRL.bf;

    rotateRight(subR);
    rotateLeft(parent);

    if (bf == -1) {
        parent.bf = 0;
        subR.bf = 1;
        subRL.bf = 0;
    }else {
        parent.bf = -1;
        subR.bf = 0;
        subRL.bf = 0;
    }
}

插入操作完整代码

class TreeNode {
    public int bf; //平衡因子=右子树的高度-左子树的高度
    public int val;
    public TreeNode left; //节点的左孩子
    public TreeNode right; //节点的右孩子
    public TreeNode parent; //节点的双亲节点

    public TreeNode(int val) {
        this.val = val;
    }
}

public class AVLTree {
    TreeNode root;
    public boolean insert(int data) {
        TreeNode node = new TreeNode(data);
        if (root == null) {
            root = node;
            return true;
        }
        TreeNode cur = root, parent = null; //parent节点用来记录cur节点的双亲节点,为后面的节点插入做准备
        while (cur != null) {
            if (cur.val < data) {
                parent = cur;
                cur = cur.right;
            }else if (cur.val > data) {
                parent = cur;
                cur = cur.left;
            }else {
                return false;
            }
        }
        //遍历到叶子节点之后,根据data和parent节点值的大小关系决定是插入到parent的左子树还是右子树
        if (parent.val < data) {
            parent.right = node;
        }else {
            parent.left = node;
        }
        node.parent = parent;
        cur = node;
        while (parent != null) {
            //如果新节点插入的是parent的右子树,那么平衡因子就会加一
            if (parent.right == cur) {
                parent.bf++;
            }else {  //如果新节点插入的是parent的左子树,那么平衡因子就会减一
                parent.bf--;
            }
            //如果parent的平衡因子为0,那么其他节点的平衡因子的绝对值都不会大于1
            if (parent.bf == 0) break;
            //如果parent的平衡因子为1或者-1,那么就说明其他节点的平衡因子可能就会变化,
            //所以需要向上遍历调整其他节点的平衡因子
            else if (parent.bf == -1 || parent.bf == 1) {
                cur = parent;
                parent = parent.parent;
            }else {
                //节点的平衡因子等于2或者-2的时候,调整节点,使之高度平衡
                if (parent.bf == 2) {
                    if (cur.bf == 1) {
                        rotateLeft(parent);
                    }else {
                        rotateRL(parent);
                    }
                }else if (parent.bf == -2) {
                    if (cur.bf == -1) {
                        rotateLeft(parent);
                    }else {
                        rotateLR(parent);
                    }
                }
                //调整完成之后,其他节点的平衡因子的绝对值就是小于2的了,可以直接跳出循环
                break;
            }
        }
        return true;
    }

    public void rotateLeft(TreeNode parent) {
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;
        TreeNode pParent = parent.parent;
        parent.right = subRL;
        if (subRL != null) {
            subRL.parent = parent;
        }
        if (root == parent) {
            root = subR;
            root.parent = pParent;
        }else {
            if (pParent.left == parent) {
                pParent.left = subR;
            }else {
                pParent.right = subR;
            }
            subR.parent = pParent;
        }
        subR.left = parent;
        parent.parent = subR;
        subR.bf = parent.bf = 0;
    }

    public void rotateRight(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;
        parent.left = subLR;
        if (subLR != null) {
            subLR.parent = parent;
        }
        TreeNode pParent = parent.parent;
        if (root == parent) {
            root = subL;
            subL.parent = null;
        }else {
            if (pParent.left == parent) {
                pParent.left = subLR;
            }else {
                pParent.right = subLR;
            }
            subLR.parent = pParent;
        }
        subL.right = parent;
        parent.parent = subL;
        subL.bf = parent.bf = 0;
    }

    public void rotateLR(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;
        int bf = subL.bf;
        rotateLeft(parent.left);
        rotateRight(parent);

        //根据插入的位置是左子树还是右子树来修改平衡因子
        if (bf == 1) {
            parent.bf = 1;
            subL.bf = 0;
            subLR.bf = 0;
        }else {
            parent.bf = 0;
            subL.bf = -1;
            subLR.bf = 0;
        }
    }

    public void rotateRL(TreeNode parent) {
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;
        int bf = subRL.bf;

        rotateRight(subR);
        rotateLeft(parent);

        if (bf == -1) {
            parent.bf = 0;
            subR.bf = 1;
            subRL.bf = 0;
        }else {
            parent.bf = -1;
            subR.bf = 0;
            subRL.bf = 0;
        }
    }
}

插入操作总结

新节点插入之后,假设以 parent 为根的子树不平衡,即 parent 的平衡因子为 2 或者 -2,解决情况如下:

  1. parent 的平衡因子为 2,说明 parent 的右子树高,设 parent 的右子树的根为 subR
  • 当 subR 的平衡因子为 1 时,执行左单旋
  • 当 subR 的平衡因子为 -1 时,执行右左双旋
  1. 当 parent 的平衡因子为 -2,说明 parent 的左子树高,设 parent 的左子树的根为 subL
  • 当 subL 的平衡因子为 -1 时,执行右单旋
  • 当 subL 的平衡因子为 1 时,执行左右双旋

AVL树的验证

因为 AVL 树是高度平衡的二叉搜索树,所以我们在验证是否为 AVL 树的时候,不仅需要判断它是否为二叉搜索树,还需要验证它是否高度平衡。

判断是否为二叉搜索树:

private int prev = Integer.MIN_VALUE;
public boolean isBinarySearchTree(TreeNode root) {
    if (root == null) return true;
    boolean l = isBinarySearchTree(root.left);
    if (!l) return false;
    if (root.val > prev) {
        prev = root.val;
    }else {
        return false;
    }
    return isBinarySearchTree(root.right);
}

判断是否高度平衡:

public boolean isBalance(TreeNode root) {
    if (root == null) return true;
    boolean lb = isBalance(root.left);
    if (!lb) return false;
    boolean rb = isBalance(root.right);
    if (!rb) return false;
    int l = height(root.left);
    int r = height(root.right);
	//平衡因子可能有错误
    if (r - l != root.bf) {
        System.out.println(root.val + "平衡因子异常");
    }
    return Math.abs(l - r) <= 1;
}

提供接口:

public boolean isAVLTree(TreeNode root) {
    return isBalance(root) && isBinarySearchTree(root);
}

AVL树的删除

因为 AVL 树也是二叉搜索树,所以也可以先根据二叉搜索树的方式进行删除,然后再调整平衡因子,这里我就不为大家介绍了,大家有兴趣可以去看看《算法导论》或者《数据结构-用面向对象方法与C++描述》殷人昆版 这两本书。

AVL树性能分析

AVL树是一种自平衡二叉搜索树,每个节点都保存一个平衡因子,用于判断是否需要进行旋转操作来保持树的平衡。平衡因子是右子树的高度减去左子树的高度(有时相反),其值只可能是-1、0或1。如果插入或删除一个节点后,某个节点的平衡因子的绝对值大于1,就需要进行旋转操作来重新平衡这棵树。

AVL树的性能如下:

  1. 查询性能:AVL树的每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即O(log n),其中n是树中节点的数量。因此,AVL树的查询性能比普通的二叉搜索树更稳定。
  2. 插入性能:在AVL树中插入节点时,需要维护其绝对平衡,因此旋转的次数可能比较多。插入操作的时间复杂度也是O(log n)。
  3. 删除性能:在AVL树中删除节点时,有可能需要一直进行旋转操作,直到达到根节点。因此,删除操作的性能可能相对较差,时间复杂度也可能较高。

总体来说,AVL树的性能比普通的二叉搜索树更稳定,查询性能更高。但是,如果需要对AVL树进行频繁的结构修改操作,其性能可能会受到影响。因此,如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑使用AVL树。但如果数据经常需要修改,其他数据结构可能更适合。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

git push后,如何撤销git log上的错误注释

修改了本地的代码&#xff0c;执行了下面的操作&#xff0c;提交之后&#xff0c;怎么样修改 git add ********(文件名)//git add 添加修改文件名之后 git commit //git commit 在当前分支提交&#xff0c;编写提交注释 git push //git push 提交修…

虹科干货 | 如何使用nProbe Cento构建100 Gbit NetFlow 传感器

本文是一份全面的指南&#xff0c;解释了如何使用nProbe Cento构建一个高效的100 Gbit NetFlow传感器。旨在帮助大家充分利用NetFlow技术&#xff0c;以监控和分析高速网络流量。 当需要监控分布式网络&#xff0c;了解流经上行链路或关键网段的网络流量时&#xff0c;NetFlow…

ES实战回顾

1、你用的集群节点情况&#xff1f; 一个ES集群&#xff0c;18个节点&#xff0c;其中3个主节点&#xff0c;15个数据节点&#xff0c;500G左右的索引数据量&#xff0c;没有单独的协调节点&#xff0c;它的每个节点都可以充当协调功能&#xff1b; 2、你们常用的索引有哪些&a…

【游戏服务器部署】幻兽帕鲁服务器一键部署保姆级教程,游戏私服还是自己搭建的香

在帕鲁的世界&#xff0c;你可以选择与神奇的生物「帕鲁」一同享受悠闲的生活&#xff0c;也可以投身于与偷猎者进行生死搏斗的冒险。帕鲁可以进行战斗、繁殖、协助你做农活&#xff0c;也可以为你在工厂工作。你也可以将它们进行售卖&#xff0c;或肢解后食用。—幻兽帕鲁 想要…

AI工具【OCR 01】Java可使用的OCR工具Tess4J使用举例(身份证信息识别核心代码及信息提前方法分享)

Java可使用的OCR工具Tess4J使用举例 1.简介1.1 简单介绍1.2 官方说明 2.使用举例2.1 依赖及语言数据包2.2 核心代码2.3 识别身份证信息2.3.1 核心代码2.3.2 截取指定字符2.3.3 去掉字符串里的非中文字符2.3.4 提取出生日期&#xff08;待优化&#xff09;2.3.5 实测 3.总结 1.简…

RocketMq5源码搭建

最近公司使用到了RocketMQ,所以打算研究一下RocketMQ 准备自己动手搭建一下RocketMq源码 前提 需要搭建jdk环境&#xff0c;jdk版本需要高于1.8 首先 从github 拉源码 源码地址&#xff1a;rocketMq源码传送门 启动namesrv 启动之前需要配置一下ROCKERMQ_HOME 配置如下&a…

力扣hot100 括号生成 递归回溯 超简洁版

Problem: 22. 括号生成 Code 使用 static 会被复用 class Solution {List<String> ans new ArrayList<>();public List<String> generateParenthesis(int n){dfs(n, n, "");return ans;}/*** param l 左括号待补个数* param r 右括号待补个数*…

基于SpringBoot+Vue的甘肃非物质文化网站(V2.0),附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

如何在Shopee泰国站点进行有效的选品

在Shopee泰国站点进行选品时&#xff0c;卖家可以采取一些策略来提高产品的市场竞争力和销售业绩。以下是一些可以遵循的策略&#xff1a; 先给大家推荐一款shopee知虾数据运营工具知虾免费体验地址&#xff08;复制浏览器打开&#xff09;&#xff1a;D。dDqbt。Com/JU5o知虾…

财务数据可视化大屏:企业决策的智慧之眼

在大数据时代&#xff0c;财务数据的管理与分析对于企业的决策和发展至关重要。然而&#xff0c;面对海量的数据&#xff0c;如何快速、准确地获取有价值的信息&#xff0c;一直是企业面临的挑战。这时&#xff0c;财务数据可视化大屏的出现&#xff0c;为企业提供了一个全新的…

Pandas--数据结构 - DataFrame(4)

DataFrame 是一个表格型的数据结构&#xff0c;它含有一组有序的列&#xff0c;每列可以是不同的值类型&#xff08;数值、字符串、布尔型值&#xff09;。DataFrame 既有行索引也有列索引&#xff0c;它可以被看做由 Series 组成的字典&#xff08;共同用一个索引&#xff09;…

1_Matlab基本操作

文章目录 工作环境操作界面运行命令窗口使用历史窗口当前目录浏览器工作空间浏览器帮助系统 工作环境 操作界面 命令窗口&#xff1a;用户进行操作的主要窗口。可以输入各种MATLAB的命令。函数和表达式。同时操作的运算结构也会在该窗口出现。历史命令窗口&#xff1a;记录用户…

【vue】vue.config.js里面获取本机ip:

文章目录 一、效果&#xff1a;二、实现&#xff1a; 一、效果&#xff1a; 二、实现&#xff1a; const os require(os);function getLocalIpAddress() {const interfaces os.networkInterfaces();for (let key in interfaces) {const iface interfaces[key];for (let i …

直播观看人次破30W | 极新「2024未来直播电商科技峰会」圆满落幕

“共话直播电商&#xff06;消费科技行业破局之道” 文&#xff5c;德江&凯丰 编辑 | 云舒 出品&#xff5c;极新 1月27日&#xff0c;由极新携手北京电子商务协会联合举办的「2024未来直播电商科技峰会」圆满落幕&#xff01;在峰会上&#xff0c;共进行了10 场演讲 &a…

解决npm安装phantomjs失败

失败信息 Progress: resolved 102, reused 102, downloaded 0, added 0, done .pnpm/phantomjs2.1.7/node_modules/phantomjs: Running install script, failed in 21.3s .../node_modules/phantomjs install$ node install.js │ PhantomJS not found on PATH │ Downloading…

PyTorch自动微分机制的详细介绍

PyTorch深度学习框架的官方文档确实提供了丰富的信息来阐述其内部自动微分机制。在PyTorch中&#xff0c;张量&#xff08;Tensor&#xff09;和计算图&#xff08;Computation Graph&#xff09;的设计与实现使得整个系统能够支持动态的、高效的自动求导过程。 具体来说&#…

07.领域驱动设计:掌握整洁架构、六边形架构以及3种常见微服务架构模型的对比和分析

目录 1、概述 2、整洁架构 3、六边形架构 4、三种微服务架构模型的对比和分析 5、从三种架构模型看中台和微服务设计 5.1 中台建设要聚焦领域模型 5.2 微服务要有合理的架构分层 5.2.1 项目级微服务 5.2.2 企业级中台微服务 5.3 应用和资源的解耦与适配 6、总结 1、概…

【测试开发】第六节.测试——对个人博客系统进行web自动化测试(包含测试代码和测试的详细过程)

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;Java测试开发 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01; 文章目录 前…

OAK深度相机主机时钟同步提升10倍!

编辑&#xff1a;OAK中国 首发&#xff1a;oakchina.cn 喜欢的话&#xff0c;请多多&#x1f44d;⭐️✍ 内容可能会不定期更新&#xff0c;官网内容都是最新的&#xff0c;请查看首发地址链接。 ▌前言 Hello&#xff0c;大家好&#xff0c;这里是OAK中国&#xff0c;我是Ash…

Linux, Certbot快速申请免费https证书

linux环境. 更新apt,为了能正确的下载certbot apt update 安装certbot apt install certbot 如果之前nginx已经开启着了,先关掉,防止端口占用 nginx -s stop 运行certbot开始获取证书文件 certbot certonly 输入1直接回车,意思就是让certbot模拟一个web服务器执行下面的…