【数据结构】C++实现红黑树

news2024/11/25 5:55:07

【数据结构】C++实现红黑树

红黑树的概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。

在这里插入图片描述

红黑树的性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

思考为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点 个数的两倍?

红黑树确保从根到叶子的最长可能路径不会超过最短可能路径的两倍,是因为它的性质3和性质4的组合导致的。让我解释一下为什么这些性质可以实现这个目标:

  1. 性质3:如果一个节点是红色的,则它的两个孩子结点是黑色的。

    这个性质确保了没有两个相邻的红色节点,因为如果有相邻的红色节点,那么它们的父节点必须是黑色的,这会违反性质2。

  2. 性质4:对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。

    这个性质确保了从根到叶子的各个路径上的黑色节点数目是相等的。这意味着树中最短路径上的黑色节点数目将限制了整个树的最长路径上的黑色节点数目。

现在让我们考虑一下为什么最长路径不会超过最短路径的两倍:

  • 假设最短路径上的黑色节点数目为k。这是因为性质4,从根到叶子的任何路径上都有k个黑色节点。
  • 最长路径上的黑色节点数目不可能超过2k。这是因为性质3,如果最长路径上有相邻的红色节点,那么它们的父节点必须是黑色的,这会导致两个黑色节点被跳过,从而使最短路径上的黑色节点数目不等于k。

所以,红黑树的性质3和性质4保证了最长路径上的黑色节点数目最多是最短路径上的黑色节点数目的两倍,从而确保了从根到叶子的最长可能路径不会超过最短可能路径的两倍。这也是红黑树保持平衡性的关键特性之一,确保了树的性能在各种操作中保持较为一致。

在这里插入图片描述

在这里插入图片描述

红黑树的实现

红黑树结点的定义

我们这里直接实现KV模型的红黑树,为了方便后序的旋转操作,将红黑树的结点定义为三叉链结构,除此之外还新加入了一个成员变量,使用枚举用于表示结点的颜色。

// 红黑树结点颜色
enum Colour {
    RED,
    BLACK,
};

template<class K, class V>
struct RBTreeNode {
    //使用三叉链
    RBTreeNode<K, V> *_left;
    RBTreeNode<K, V> *_right;
    RBTreeNode<K, V> *_parent;
    pair<K, V> _kv;
    Colour _col;//结点颜色
    // 在单参数构造函数中使用 explicit 关键字是一种好的编程习惯,可以提高代码的可读性和健壮性。
    // 加上 explicit 关键字,以避免出现不必要的隐式类型转换。如果没有加上 explicit 关键字,那么可以使用该构造函数创建一个 RBTreeNode 对象时,会发生隐式类型转换,将一个 pair<K, V> 类型的对象转换为 RBTreeNode 对象,这可能导致程序行为出现意外的结果。
    explicit RBTreeNode(const pair<K, V> &kv)
        : _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED) {
    }
};

思考:在结点的定义中,为什么要将结点的默认颜色给成红色的?

当我们向红黑树插入结点时,若我们插入的是黑色结点,那么插入路径上黑色结点的数目就比其他路径上黑色结点的数目多了一个,即破坏了红黑树的性质4,此时我们就需要对红黑树进行调整。

若我们插入红黑树的结点是红色的,此时如果其父结点也是红色的,那么表明出现了连续的红色结点,即破坏了红黑树的性质3,此时我们需要对红黑树进行调整;但如果其父结点是黑色的,那我们就无需对红黑树进行调整,插入后仍满足红黑树的要求。

总结一下

  • 插入黑色结点,一定破坏红黑树的性质4,必须对红黑树进行调整。
  • 插入红色结点,可能破坏红黑树的性质3,可能对红黑树进行调整。

权衡利弊后,我们在构造结点进行插入时,默认将结点的颜色设置为红色。

红黑树的插入

红黑树插入结点的逻辑分为三步:

  1. 按二叉搜索树的插入方法,找到待插入位置。
  2. 将待插入结点插入到树中。
  3. 若插入结点的父结点是红色的,则需要对红黑树进行调整。

其中前两步与二叉搜索树插入结点时的逻辑相同,红黑树的关键在于第三步对红黑树的调整。

红黑树在插入结点后是如何调整的?

实际上,在插入结点后并不是一定会对红黑树进行调整,若插入结点的父结点是黑色的,那么我们就不用对红黑树进行调整,因为本次结点的插入并没有破坏红黑树的五点性质。

只有当插入结点的父结点是红色时才需要对红黑树进行调整,因为我们默认插入的结点就是红色的,如果插入结点的父结点也是红色的,那么此时就出现了连续的红色结点,因此需要对红黑树进行调整。

因为插入结点的父结点是红色的,说明父结点不是根结点(根结点是黑色的),因此插入结点的祖父结点(父结点的父结点)就一定存在。

红黑树调整时具体应该如何调整,主要是看插入结点的叔叔(插入结点的父结点的兄弟结点),根据插入结点叔叔的不同,可将红黑树的调整分为三种情况。

情况一

插入结点的叔叔存在,且叔叔的颜色是红色。此时,cur为红,p为红,u为红,g为黑

此时为了避免出现连续的红色结点,我们可以将父结点变黑,但为了保持每条路径黑色结点的数目不变,因此我们还需要将祖父结点变红,再将叔叔变黑。这样一来既保持了每条路径黑色结点的数目不变,也解决了连续红色结点的问题。

在这里插入图片描述

但调整还没有结束,因为此时祖父结点变成了红色,如果祖父结点是根结点,那我们直接再将祖父结点变成黑色即可,此时相当于每条路径黑色结点的数目都增加了一个。

但如果祖父结点不是根结点的话,我们就需要将祖父结点当作新插入的结点,再判断其父结点是否为红色,若其父结点也是红色,那么又需要根据其叔叔的不同,进而进行不同的调整操作。

因此,情况一的抽象图表示如下:

在这里插入图片描述

注意: 叔叔存在且为红时,cur结点是parent的左孩子还是右孩子,调整方法都是一样的。

情况二

插入结点的叔叔存在,且叔叔的颜色是黑色。

这种情况一定是在情况一继续往上调整的过程中出现的,即这种情况下的cur结点一定不是新插入的结点,而是上一次情况一调整过程中的祖父结点,如果u结点存在,则其一定是黑色的,那么cur结点原来的颜色一定是黑色的,现在看到其是红色的原因是因为cur的子树在调整过程中将cur结点的颜色由黑色改成红色。

需要注意:

  1. 从根结点一直走到空位置就算一条路径,而不是从根结点走到左右结点均为空的叶子结点时才算一条路径。
  2. 情况二和情况三均需要进行旋转处理,旋转处理后无需继续往上进行调整,所以说情况二一定是由情况一往上调整的过程中出现的。

单旋处理

出现叔叔存在且为黑时,单纯使用变色已经无法处理了,这时我们需要进行旋转处理。若祖孙三代的关系是直线(cur、parent、grandfather这三个结点为一条直线),则我们需要先进行单旋操作,从g结点进行右单旋,再进行颜色调整,颜色调整后这棵被旋转子树的根结点是黑色的,因此无需继续往上进行处理。

图表示如下:

在这里插入图片描述

说明一下: 当直线关系为,parent是grandfather的右孩子,cur是parent的右孩子时,就需要先进行左单旋操作,再进行颜色调整。

双旋转处理

若祖孙三代的关系是折线(cur、parent、grandfather这三个结点为一条折线),则我们需要先进行左右双旋操作,再进行颜色调整,颜色调整后这棵被旋转子树的根是黑色的,因此无需继续往上进行处理。

旋转操作是先将p左单旋,在将g右单旋

在这里插入图片描述

说明一下: 当折线关系为,parent是grandfather的右孩子,cur是parent的左孩子时,就需要先进行右左双旋操作,再进行颜色调整

情况三

插入结点的叔叔不存在。

在这种情况下的cur结点一定是新插入的结点,而不可能是由情况一变化而来的,因为叔叔不存在说明在parent的下面不可能再挂黑色结点了,如下图:

在这里插入图片描述

如果插入前parent下面再挂黑色结点,就会导致图中两条路径黑色结点的数目不相同,而parent是红色的,因此parent下面自然也不能挂红色结点,所以说这种情况下的cur结点一定是新插入的结点。

单旋处理

和情况二一样,若祖孙三代的关系是直线(cur、parent、grandfather这三个结点为一条直线),则我们需要先进行单旋操作,再进行颜色调整,颜色调整后这棵被旋转子树的根结点是黑色的,因此无需继续往上进行处理。

图表示如下:

在这里插入图片描述

说明一下: 当直线关系为,parent是grandfather的右孩子,cur是parent的右孩子时,就需要先进行左单旋操作,再进行颜色调整。

双旋处理

若祖孙三代的关系是折线(cur、parent、grandfather这三个结点为一条折线),则我们需要先进行双旋操作,再进行颜色调整,颜色调整后这棵被旋转子树的根是黑色的,因此无需继续往上进行处理。

图表示如下:

在这里插入图片描述

说明一下: 当折线关系为,parent是grandfather的右孩子,cur是parent的左孩子时,就需要先进行右左双旋操作,再进行颜色调整。

我们发现情况三的处理是和情况二相同的,所以只需要处理情况二即可。

代码

bool Insert(const pair<K, V> &kv) {
    //创建根结点
    if (_root == nullptr) {
        _root = new Node(kv);
        _root->_col = BLACK;//根结点为黑色
        return true;
    }

    Node *parent = nullptr;
    Node *cur = _root;
    //寻找结点插入位置
    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走到了合适的位置
    cur = new Node(kv);
    // 选择插入到parent的左边还是右边
    if (kv.first < parent->_kv.first) {
        parent->_left = cur;
    } else {
        parent->_right = cur;
    }
    // cur链接parent
    cur->_parent = parent;
    // parent存在且parent的节点为红色的(意味着,循环往上调整到parent不存在或者parent为黑就不用调整了)
    // 红黑树性质:红色结点的孩子必须是黑色的
    while (parent && parent->_col == RED) {
        Node *grandfather = parent->_parent;
        // 如果爷爷的左边是父亲,那么爷爷的右边就是叔叔
        if (grandfather->_left == parent) {
            Node *uncle = grandfather->_right;
            // 情况1:u存在且为红,变色处理,并继续往上处理
            if (uncle && uncle->_col == RED) {
                // 调整parent变黑,uncle变黑,grandfather变红
                parent->_col = BLACK;
                uncle->_col = BLACK;
                grandfather->_col = RED;

                // 继续往上调整,此时的grandfather作为新插入结点,继续判断他的父亲是否是红色结点
                // 重置parent,先将grandfather位置看成新增结点cur
                cur = grandfather;
                parent = cur->_parent;
            } else {// 情况2+3  u不存在/u存在且为黑,旋转+变色
                //插入结点是父亲的左孩子
                if (cur == parent->_left) {
                    //     g
                    //   p   u
                    // c
                    // g p c成为一条直线,并且cur在parent的左边
                    // 需要右旋+变色,右旋后parent成为根,需要变黑,grandfather变为parent的右孩子,需要变红
                    RotateRight(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                } else {
                    //     g
                    //   p   u
                    //     c
                    // 当cur为parent的右边时,需要左旋+右旋+变色
                    RotateLeft(parent);
                    RotateRight(grandfather);// 右旋cur成为新的根,变为黑色,grandfather变为cur孩子,变为红色
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }
                break;
            }
        } else {//(grandfather->_right == parent)  如果爷爷的右边是父亲,那么爷爷的左边就是叔叔
            //    g
            //  u   p
            //        c
            Node *uncle = grandfather->_left;
            // 情况1:u存在且为红,变色处理,并继续往上处理
            if (uncle && uncle->_col == RED) {
                parent->_col = BLACK;
                uncle->_col = BLACK;
                grandfather->_col = RED;

                // 继续往上调整
                cur = grandfather;
                parent = cur->_parent;
            } else// 情况2+3:u不存在/u存在且为黑,旋转+变色
            {
                //    g
                //  u   p
                //        c
                if (cur == parent->_right) {
                    RotateLeft(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                } else {
                    //     g
                    //  u     p
                    //     c
                    RotateRight(parent);
                    RotateLeft(grandfather);
                    //parent的位置没变不需要变色
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }
                break;
            }
        }
    }
    // 最后的根变为黑节点
    _root->_col = BLACK;
    return true;
}

// 左单旋
void RotateLeft(Node *parent) {
    Node *subR = parent->_right;// 要旋转的parent的右子树
    Node *subRL = subR->_left;  // 子树的左子树

    // 旋转链接
    parent->_right = subRL;
    if (subRL)
        subRL->_parent = parent;

    // 需要记录要旋转的树还有没有父亲
    Node *ppnode = parent->_parent;

    subR->_left = parent;
    parent->_parent = subR;

    // 如果ppnode为nullptr,说明parent一开始为根,旋转后subR为根
    if (ppnode == nullptr) {
        // 更新根节点
        _root = subR;
        _root->_parent = nullptr;
    } else {
        if (ppnode->_left == parent) {
            ppnode->_left = subR;
        } else {
            ppnode->_right = subR;
        }
        subR->_parent = ppnode;
    }
}

// 右单旋
void RotateRight(Node *parent) {
    Node *subL = parent->_left;
    Node *subLR = subL->_right;

    parent->_left = subLR;
    if (subLR)
        subLR->_parent = parent;

    Node *ppnode = parent->_parent;

    subL->_right = parent;
    parent->_parent = subL;

    if (ppnode == nullptr) {
        _root = subL;
        _root->_parent = nullptr;
    } else {
        if (ppnode->_left == parent) {
            ppnode->_left = subL;
        } else {
            ppnode->_right = subL;
        }
        subL->_parent = ppnode;
    }
}

红黑树的验证

红黑树也是一种特殊的二叉搜索树,因此我们可以先获取二叉树的中序遍历序列,来判断该二叉树是否满足二叉搜索树的性质。

代码如下:

void InOrder(Node *root) {
    if (root == nullptr) {
        return;
    }

    InOrder(root->_left);
    cout << root->_kv.first << " ";
    InOrder(root->_right);
}

void InOrder() {
    InOrder(this->_root);
}

但中序有序只能证明是二叉搜索树,要证明二叉树是红黑树还需验证该二叉树是否满足红黑树的性质。

bool IsBalance() {
    if (_root && _root->_col == RED) {
        cout << "根节点颜色是红色" << endl;
        return false;
    }

    int benchmark = 0;// 基准值,任选一条做,用于比较每条节点黑色节点相同,如果不相同则说明不平衡
    Node *cur = this->_root;
    while (cur) {
        if (cur->_col == BLACK)
            benchmark++;
        cur = cur->_left;
    }
    // 连续红色节点
    return _check(this->_root, 0, benchmark);
}

bool _check(Node *root, int BlackNum, int benchmark) {
    // 检查不能存在连续的红色节点
    // benchmark基准值
    if (root == nullptr) {
        if (benchmark != BlackNum) {
            cout << "某条路径黑色节点的数量不相等" << endl;
            return false;
        }

        return true;
    }

    if (root->_col == BLACK) {
        BlackNum++;
    }

    if (root->_col == RED && root->_parent && root->_parent->_col == RED) {
        cout << "存在连续的红色节点" << endl;
        return false;
    }

    return _check(root->_left, BlackNum, benchmark) && _check(root->_right, BlackNum, benchmark);
}

红黑树的查找

红黑树的查找函数与二叉搜索树的查找方式一模一样

Node *find(const K &key) {
    Node *cur = this->_root;
    while (cur) {
        if (key > cur->_kv.first) {
            cur = cur->_right;
        } else if (key < cur->_kv.first) {
            cur = cur->_left;
        } else {
            return cur;
        }
    }
    return nullptr;
}

红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(logN),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数, 所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

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

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

相关文章

多数网工碌碌无为,都是败在这件事上

大家好&#xff0c;我是老杨。 这周是网络安全周&#xff0c;博览会的现场很热闹&#xff0c;我也被邀请去参观了一圈。 主要是学习观摩各大厂出的最新的安全产品、解决方案等。 这几天&#xff0c;也遇上了不少大佬。 有从路由交换转到安全的&#xff0c;也有从渗透转到防御…

Unity中Shader的模板测试

文章目录 前言什么是模板测试1、模板缓冲区2、模板缓冲区中存储的值3、模板测试是什么&#xff08;看完以下流程就能知道模板测试是什么&#xff09;模板测试就是在渲染&#xff0c;后渲染的物体前&#xff0c;与渲染前的模板缓冲区的值进行比较&#xff0c;选出符合条件的部分…

红队打靶:Me and My Girlfriend打靶思路详解(vulnhub)

目录 写在开头 第一步&#xff1a;主机发现和端口扫描 第二步&#xff1a;Web渗透&#xff08;修改XFF代理&#xff09; 第三步&#xff1a;数据库手工枚举 第四步&#xff1a;sudo php提权 总结与思考 写在开头 本篇博客在自己的理解之上根据大佬红队笔记的视频进行…

GPIO子系统编写LED灯的驱动、linux内核定时器

一、GPIO子系统 1.概念&#xff1a; 一个芯片厂商生产出芯片后会给linux提供一个当前芯片中gpio外设的驱动&#xff0c;我们当前只需要调用对应的厂商驱动即可完成硬件的控制。而linux内核源码中的gpio厂商驱动有很多&#xff0c;这里linux内核对厂商驱动做了一些封装&#x…

基于Java的大学生心理健康答题小程序设计与实现(亮点:选题新颖、可以发布试卷设置题目、自动判卷、上传答案、答案解析)

校园点餐小程序 一、前言二、我的优势2.1 自己的网站2.2 自己的小程序&#xff08;小蔡coding&#xff09;2.3 有保障的售后2.4 福利 三、开发环境与技术3.1 MySQL数据库3.2 Vue前端技术3.3 Spring Boot框架3.4 微信小程序 四、功能设计4.1 主要功能描述 五、系统主要功能5.1 登…

python-字符串去掉空格的常见方法

python提供了去掉字符串空格的方法&#xff0c;可以满足大部分需求。 但在实际应用中&#xff0c;还需要灵活借助python其他方法&#xff0c;来实现字符串空格的删除。 比如&#xff0c;去掉字符串的全部空格、字符串连续空格保留一个等&#xff0c;都需要结合其他的方法来实现…

【kafka】mac环境安装kafka

生产中使用到的中间件使用心得记录&#xff0c;感兴趣可以关注我一起学习&#xff5e; 环境&#xff1a; 硬件&#xff1a;mac 软件&#xff1a;kafka v3.0.0 安装步骤&#xff1a; 终端安装&#xff1a; 通过brew安装&#xff0c;会默认直接安装最新的版本 步骤1&#xf…

Android StringFog 字符串自动加密

一、StringFog 作用 一款自动对dex/aar/jar文件中的字符串进行加密Android插件工具&#xff0c;正如名字所言&#xff0c;给字符串加上一层雾霭&#xff0c;使人难以窥视其真面目。可以用于增加反编译难度&#xff0c;防止字符串代码重复。 支持java/kotlin。支持app打包生成…

【JDK 8-函数式编程】4.6 方法引用与构造函数引用

一、 方法引用与构造函数引用 1. 说明 2. 语法: 二、静态方法 三、实例方法 四、构造函数 4.1 单个参数 4.2 2个参数 五、执行结果 一、 方法引用与构造函数引用 以前调用&#xff1a;对象.方法名、类名.方法名 jdk1.8提供了另外一种调用方式 :: 1. 说明 用来直接访…

子网的划分

强化计算机网络发现王道没有这一块的内容&#xff0c;导致做题稀里糊涂。于是个人调研补充。 子网划分是将一个大型IP网络划分成更小的子网&#xff0c;以实现更有效的网络管理和资源分配。 原因&#xff1a; 提高网络性能&#xff1a;子网划分可以减少广播域的大小&#xff…

靶场练习——SDcms文件上传漏洞靶场

文章目录 前言一、寻找网站后台页面1、点击请登录&#xff0c;查看URL2、修改URL参数&#xff0c;找到后台登录页面 二、登录后台管理系统1、不能使用爆破2、使用弱口令登录 三、寻找文件上传点四、上传文件操作1、上传普通的图片文件&#xff0c;查看数据包2、尝试上传PHP文件…

Mysql----锁

文章目录 锁 概述 全局锁全局锁 概述全局锁 操作表级锁表级锁 表锁表级锁 元数据锁表级锁 意向锁行级锁行级锁 行锁行级锁 间隙锁&临键锁 锁 概述 是什么 是计算机协调多个进程或线程并发访问某一资源的机制。 意义 在数据库中&#xff0c;数据是一种供许多用户共享的…

C/C++计算分数的浮点数值 2019年12月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C计算分数的浮点数值 一、题目要求 1、编程实现 2、输入输出 二、解题思路 1、案例分析 三、程序代码 四、程序说明 五、运行结果 六、考点分析 C/C计算分数的浮点数值 2019年12月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 两个整数a和b分别作…

每日一题 337. 打家劫舍 III

难度&#xff1a;中等 整体思路相当于是前两天的方法倒过来&#xff0c;毕竟二叉树最常用的解法就是递归倒推 对于每一颗子树&#xff0c;他必定有一种最大的盗取方法&#xff0c;但是只有它的 root 的盗取情况才会影响到 root 的父节点&#xff0c;即如果收益最大的盗取方法…

【TCP】滑动窗口、流量控制 以及拥塞控制

滑动窗口、流量控制 以及拥塞控制 1. 滑动窗口&#xff08;效率机制&#xff09;2. 流量控制&#xff08;安全机制&#xff09;3. 拥塞控制&#xff08;安全机制&#xff09; 1. 滑动窗口&#xff08;效率机制&#xff09; TCP 使用 确认应答 策略&#xff0c;对每一个发送的数…

机器学习第七课--情感分析系统

分词 分词是最基本的第一步。无论对于英文文本&#xff0c;还是中文文本都离不开分词。英文的分词相对比较简单&#xff0c;因为一般的英文写法里通过空格来隔开不同单词的。但对于中文&#xff0c;我们不得不采用一些算法去做分词。 常用的分词工具 # encodingutf-8 import …

Linux C 多线程

为什么会有线程? ————————>>>> 进程实现多任务的缺点&#xff1a; 进程间切换的计算机资源开销很大&#xff0c;切换效率非常低进程间数据共享的开销也很大 线程和进程的关系 线程是进程的一个执行单元&#xff0c;是进程内的调度实体。比进程…

应用:使用#todo标签,清理未完成笔记 | Obsidian实践

各位【见睿思齐】的朋友&#xff0c;好久不见。断更了这么久&#xff0c;非常抱歉。起初是因为家里出了些事情&#xff0c;不得不断更了一段时间&#xff1b;后来是因为断更太久&#xff0c;找不回写作的感觉&#xff0c;于是又断更了更久的时间……总之是太不应该。最近读了一…

ESLint+Prettier+VSCode编程规范

编程规范 ESLintPrettierESLint和Prettier配合解决代码格式化问题1. 在VSCode搜索Prettier插件安装2. 创建prettier配置文件3. 在VSCode中设置3.1 找到左下角设置图标&#xff0c;点击设置3.2 但是对VSCode 而言&#xff0c;默认一个 tab 等于 4 个空格&#xff0c;而 ESLint 希…

【Hierarchical Coverage Path Planning in Complex 3D Environments】

Hierarchical Coverage Path Planning in Complex 3D Environments 复杂三维环境下的分层覆盖路径规划 视点采样全局TSP 算法分两层&#xff0c;一层高级一层低级&#xff1a; 高层算法将环境分离多个子空间&#xff0c;如果给定体积中有大量的结构&#xff0c;则空间会进一步细…