数据结构——B树

news2024/11/24 5:20:18

文章目录

  • B树
    • 1. 概念
    • 2. B树插入分析
    • 3.插入过程
    • 4. B树插入实现
    • 5.B树验证
    • 6. B树性能分析
    • 7.B+树&B*树
    • 8. 小结
    • 9. B树的运用
      • MyISAM
      • InnoDB
    • 10. 总结


B树

可以用于查询的数据结构非常的多,比如说二插搜索树、平衡树、哈希表、位图、布隆过滤器,但如果需要存储一些数据量极大的数据,上述的数据结构就很不适用了,内存是不一定放的下的,比如用平衡树搜索一个贼大的文件。

因为数据量非常大,一次性无法加载到内存中,那么就可以在平衡树中保存数据项需要查找的部分,以及指向该数据在磁盘中的地址的指针。

虽然在上诉方法在内存中存放的是数据的地址,真实的数据是在磁盘上,解决了内存占用的问题。但是也有一定的缺陷:

  • 当数据量比较大的时候,比如使用的是红黑树,虽然时间复杂度是 O ( l o g n ) O(logn) O(logn),但数据量比较大的时候,树的高度是比较高的。
  • 当数据量特别大时,树中的节点可能无法一次性加载到内存中,这就需要增加磁盘的I/O次数

解决办法:就是减少I/O的次数,降低树的高度,引入多叉平衡树(B树)

1. 概念

B树是一棵M阶(M>2)的B树,是一棵平衡的M路平衡搜索树,可以是空树或者满足一下性
质:

  1. 根节点至少有两个孩子
  2. 每个非根节点至少有 M / 2 − 1 M/2-1 M/21(上取整)个关键字,至多有 M − 1 M-1 M1个关键字,并且以升序排列
    例如:当M=3的时候,至少有3/2=1.5,向上取整等于2,2-1=1个关键字,最多是2个关键字
  3. 每个非根节点至少有 M / 2 M/2 M/2(上取整)个孩子,至多有M个孩子
    例如:当M=3的时候,至少有3/2=1.5,向上取整等于2个孩子。最多有3个孩子。
  4. key[i]和key[i+1]之间的孩子节点的值介于key[i]、key[i+1]之间
  5. 所有的叶子节点都在同一层

2. B树插入分析

为了简单起见,假设M = 3. 即三叉树,每个节点中存储两个数据,两个数据可以将区间分割成三个部分,因此节点应该有三个孩子,为了后续实现简单期间,节点的结构如下:

在这里插入图片描述

注意:孩子永远比数据多一个
插入过程当中,有可能需要分裂,分裂的
前提是:
假设,当前是要组成一个M路查找树,关键字数必须<=M-1**(这里关键字数**>M-1**就要进行节点拆分)**规则是:
把中间的元素,提取出来,放到父亲节点上,左边的单独构成一个节点,右边的单独构成一个节点。

假设用 53 , 139 , 75 , 49 , 145 , 36 , 101 {53,139,75,49,145,36,101} 53,139,75,49,145,36,101构造B树的过程如下:

  1. 先插入53和139

在这里插入图片描述

  1. 再插入75,插入75后按照插入排序的思想对元素进行排序

    在这里插入图片描述

  2. 因为该节点的元素已经满了,就需要进行分裂

    节点分裂的步骤:

    • 找到节点数据域的中间位置
    • 给一个新节点,将中间位置的数据移动到新创建的节点中
    • 将中间位置的那个数据移动到父节点中
    • 将节点的关系连接好

    在这里插入图片描述

  3. 插入49和145

    • 找到该元素的插入位置的位置
    • 按照插入排序的思想将节点插入到该节点的合适位置
    • 再检测是否满足B数的性质
    • 满足插入结束,不满足对节点进行分裂

    在这里插入图片描述

  4. 插入36

    插入后违法了B树的性质,需要进行分裂

    在这里插入图片描述

    对cur节点进行分裂:

    • 找到中间位置
    • 将中间位置右侧元素移动到新节点中
    • 将中间位置元素49移动到父节点parent中
    • 将新节点的父节点修改成parent

    在这里插入图片描述

  5. 插入数字101

    • 在B树中找到该节点的插入位置
    • 按照插入排序思想将该节点插入到指定位置
    • 检测插入后的节点元素个数是否小于M
    • 如果小于M插入完成,等于M则需要对该节点进行分裂

在这里插入图片描述

在这里插入图片描述

分裂完后,发现根节点也需要进行分裂,根节点分裂需要进行特殊处理,因为它会增加树的高度。

在这里插入图片描述

3.插入过程

  1. 如果树为空,直接插入新节点中,该节点为树的根节点

  2. 树非空,找待插入元素在树中的插入位置(注意:找到的插入节点位置一定在叶子节点中)

  3. 检测是否找到插入位置(假设树中的key唯一,即该元素已经存在时则不插入)

  4. 按照插入排序的思想将该元素插入到找到的节点中

  5. 检测该节点是否满足B-树的性质:即该节点中的元素个数是否等于M,如果小于则满足

  6. 如果插入后节点不满足B树的性质,需要对该节点进行分裂:

    • 申请新节点
    • 找到该节点的中间位置
    • 将该节点中间位置右侧的元素以及其孩子搬移到新节点中
    • 将中间位置元素以及新节点往该节点的双亲节点中插入,即继续4
  7. 如果向上已经分裂到根节点的位置,插入结束

4. B树插入实现

B树的节点定义

// M叉树
private static final int M = 3;
static class BTreeNode {
    // 节点关键字
    int[] keys;
    // 孩子
    BTreeNode[] subs;
    // 关键字个数
    int keySize;
    // 父节点
    BTreeNode parent;

    public BTreeNode() {
        keys = new int[M];
        // 多给一个方便分裂
        subs = new BTreeNode[M+1];
    }
}
    

定义一个类方便查找指定节点

public class Pair<K,V> {
    K key;
    V val;

    public Pair(K key, V val) {
        this.key = key;
        this.val = val;
    }
}

插入代码

/**
 * B树
 */
public class BTree {
    // B树根节点
    BTreeNode root;

    public boolean insert(int key) {
        if (root == null) {
            root = new BTreeNode();
            root.keys[0] = key;
            root.keySize++;
            return true;
        }
        // 查找关键字是否已经存在
        Pair<BTreeNode,Integer> cur = find(key);
        if (cur.val != -1) {
            // 说明key已经存在,插入失败
            return false;
        }
        // 开始插入(插入排序)
        BTreeNode parent = cur.key;
        int i = 0;
        for (i = parent.keySize-1; i >= 0; i--) {
            if (parent.keys[i] >= key) {
                parent.keys[i+1] = parent.keys[i];
            } else {
                break;
            }
        }
        parent.keys[i+1] = key;
        parent.keySize++;

        // 判断是否需要分裂
        if (parent.keySize >= M) {
            split(parent);
        }
        return true;
    }

    /**
     * 对cur节点进行分裂
     * @param cur
     */
    private void split(BTreeNode cur) {
        BTreeNode newNode = new BTreeNode();
        BTreeNode parent= cur.parent;
        // 中间位置
        int mid = cur.keySize>>1;
        // 把mid往后的元素移动到新节点
        int j = 0;
        int i = mid+1;
        for (; i < cur.keySize; i++) {
            newNode.keys[j] = cur.keys[i];
            // 子节点引用
            newNode.subs[j] = cur.subs[i];
            // 修改子节点的parent引用
            if (newNode.subs[j] != null) {
                newNode.subs[j].parent = newNode;
            }
            newNode.keySize++;
            j++;
        }
        // 多拷贝一次最后的子节点
        newNode.subs[j] = cur.subs[i];
        if (newNode.subs[j] != null) {
            newNode.subs[j].parent = newNode;
        }
        //更新分裂节点关键字个数,-1指的是mid位置的节点要向上提
        cur.keySize = cur.keySize-mid-1;

        // 处理根节点分裂情况!
        if (cur == root) {
            root = new BTreeNode();
            root.keys[0] = cur.keys[mid];
            root.keySize++;
            root.subs[0] = cur;
            root.subs[1] = newNode;
            cur.parent = root;
            newNode.parent = root;
            return;
        }
        // 更新新节点的父亲节点
        newNode.parent = parent;

        // 将mid位置的元素向上提
        int midVal = cur.keys[mid];
        i = parent.keySize-1;
        for (; i >= 0; i--) {
            if (parent.keys[i] >= midVal) {
                parent.keys[i+1] = parent.keys[i];
                parent.subs[i+2] = parent.subs[i+1];
            } else {
                break;
            }
        }
        parent.keys[i+1] = midVal;
        parent.keySize++;
        // 将当前父节点的孩子节点设置为newNode
        parent.subs[i+2] = newNode;
        // 如果个数超过M递归分裂
        if (parent.keySize >= M) {
            split(parent);
        }
    }

    /**
     * 在B树中查找关键字是否已经存在
     * 找到返回节点和起下标,否则返回其父节点和-1
     * @param key
     * @return
     */
    private Pair<BTreeNode, Integer> find(int key) {
        BTreeNode cur = root;
        BTreeNode parent = root;
        int index = 0;
        while (cur != null) {
            while (index < cur.keySize) {
                if (cur.keys[index] == key) {
                    return new Pair<>(cur,index);
                } else if (cur.keys[index] < key) {
                    index++;
                } else {
                    break;
                }
            }
            parent = cur;
            // 向子节点找
            cur = cur.subs[index];
            index = 0;
        }
        return new Pair<>(parent,-1);
    }
}

5.B树验证

对B树进行中序遍历,只要打印出来的数据是有序说明插入正确

/**
     * 验证B树
     * @param root
     */
    private void inorder(BTreeNode root){
        if(root == null)
            return;
        for(int i = 0; i < root.keySize; ++i){
            inorder(root.subs[i]);
            System.out.println(root.keys[i]);
        }
        inorder(root.subs[root.keySize]);
    }

6. B树性能分析

对于一棵节点为N度的B树,查找和插入需要 l o g M − 1 N log_{M-1}N logM1N~ l o g M / 2 N log_{M/2}N logM/2N次比较。对于度为M的B树,每一个节点的子节点个数为 M / 2 M/2 M/2~ ( M − 1 ) (M-1) (M1)之间,因此树的高度应该要在 l o g M − 1 N log_{M-1}N logM1N l o g M / 2 N log_{M/2}N logM/2N之间,在定位到该节点后,再采用二分查找的方式可以很快的定位到该元素。

B树的效率是很高的对于 N = 62 ∗ 1000000000 N=62*1000000000 N=621000000000个节点,如果M为1024,则 l o g M / 2 N < = 4 log_{M/2}N<=4 logM/2N<=4,既在620亿个元素中,如果这棵树的度为1024,则需要小于4次即可定位到该节点,然后利用二分查找,可以快速定位到该元素,大大减少了读取磁盘的次数。

总结:

  1. B树实际上是一棵M叉平衡树
  2. 非根节点的关键字数量[M/2-1,M-1],因为每次满了之后,会拷走一半
  3. 非根节点的孩子数量[M/2,M]
  4. 所有的叶子节点都在同一层,所以B树是天然平衡的
  5. M越大效率越高,但M越大也会有空间浪费的问题,节点分裂会拷走一半就会存在空间浪费的问题

7.B+树&B*树

B+树是B树的变形,也是一种多路搜索树:

其定义基本与B树相同,除了:

  1. B+树非叶子节点的子树指针与关键字个数相同,而B树的孩子数量比关键字多一个
  2. B+树非叶子节点的子树指针指向的子树都属于它的右子树,也就是大于当前节点
  3. 为所有叶子节点增加一个链指针,也就是叶子节点是一个链表
  4. B+数的叶子节点存储了数据,而非叶子节点只是存储了关键字。

在这里插入图片描述

对于B+树来说,如果要查找数据,那么一定得遍历整个树的高度,因为数据在叶子节点,但B树的每个节点都存储了数据,就不一定要遍历到叶子节点。

B+树的分裂:当一个结点满时,分配一个新的结点,
并将原结点中1/2的数据
复制到新结点,最后在父结点中增加新结点的指针,B+树的分裂只影响原结点和父
结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针;

B*树

B*树是B+树的变形,在B+树的非根和非叶子节点再增加指向兄弟节点的指针。

在这里插入图片描述

B树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了),如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针;所以,B*树分配新结点的概率比B+树要低,空间使用率更高;

8. 小结

  • B树:多路搜索树,每个结点存储M/2到M个关键字,非叶子结点存储指向关键
    字范围的子结点;所有关键字在整颗树中出现,且只出现一次,非叶子结点可以命中;
  • B+树:在B-树基础上,为叶子结点增加链表指针,所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引,B+树总是到叶子结点才命中;
  • B*树:在B+树基础上,为非叶子结点也增加链表指针,将结点的最低利用率从1/2提高到2/3;

9. B树的运用

B-树最常见的应用就是用来做索引。索引通俗的说就是为了方便用户快速找到所寻之物,比如:书籍目录可以让读
者快速找到相关信息,hao123网页导航网站,为了让用户能够快速的找到有价值的分类网站,本质上就是互联网
页面中的索引结构。
MySQL官方对索引的定义为:索引(index)是帮助MySQL高效获取数据的数据结构,简单来说:索引就是数据结
构。

注意:索引是基于表的,而不是基于数据库的

MyISAM

MyISAM引擎是MySQL5.5.8版本之前默认的存储引擎,不支持事物,支持全文检索,使用B+Tree作为索引结构,
叶节点的data域存放的是数据记录的地址,其结构如下:

在这里插入图片描述

上图是以以Col1为主键,MyISAM的示意图,可以看出MyISAM的索引文件仅仅保存数据记录的地址**。MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key**可以重复。如果想在Col2上建立一个辅助索引,则此索引的结构如下图所示 :

在这里插入图片描述

同样也是一棵B+Tree,data域保存数据记录的地址。因此,MyISAM中索引检索的算法为首先按照B+Tree搜索算
法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。
MyISAM的索引方式也叫做**“非聚集索引”**的

InnoDB

InnoDB存储引擎支持事务,其设计目标主要面向在线事务处理的应用,从MySQL数据库5.5.8版本开始,InnoDB存储引擎是默认的存储引擎。InnoDB支持B+树索引、全文索引、哈希索引。但InnoDB使用B+Tree作为索引结构
时,具体实现方式却与MyISAM截然不同。
第一个区别是InnoDB的数据文件本身就是索引文件MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而InnoDB索引,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保
存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引

在这里插入图片描述

上图是InnoDB主索引(同时也是数据文件)的示意图,可以看到叶节点包含了完整的数据记录,这种索引叫做聚集索引。因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形
第二个区别是InnoDB的辅助索引data域存储相应记录主键的值而不是地址,所有辅助索引都引用主键作为data
域。

**聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录 **

10. 总结

  1. 像链表,顺序表这样的是否可以?

    时间复杂度太高,不行

  2. AVL树&红黑树是否可以?
    数据量大时树的高度较高,增加了I/O次数

  3. 哈希表是否可以?
    哈希表存在哈希冲突哈希只能支持 是或者不是。比如: where id =? 或者 where id =? 做不到范围查找: where id >10

  4. 为什么使用B+树,不适用B树?

    • 节点点不存储data,这样一个节点就可以存储更多的key。可以使得树更矮,所以I/O操作次数更少。
    • 叶子节点相连,更便于进行范围查找

聚簇索引和非聚簇索引的区别

  • 聚簇索引将数据行存储在与索引相同的 B+ 树结构中,而非聚簇索引是将索引和主键 ID 存储在 B+ 树结构中;
  • 数量限制不同:一张表只能有一个聚簇索引,但可以有多个非聚簇索引;
  • 范围查询不同:聚簇索引中的数据行与索引行是一一对应的,因此聚簇索引通常比非聚簇索引更适合范围查询,而非聚簇索引需要进行两次查找:首先查找索引,然后查找数据行
  • 非聚簇索引最大的缺点。 当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询,首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录
  • 在Innodb下主键索引是聚集索引,在Myisam下主键索引是非聚集索引
  • MyISAM 不提供事务支持。InnoDB 提供事务支持,实现了 SQL 标准定义了四个隔离级别。
  • MyISAM 不支持外键,而 InnoDB 支持。

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

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

相关文章

【MySQL】MySQL入门基础

文章目录 一、数据库基础1. 什么是数据库2. 数据库和文件3. 主流数据库&#xff08;关系型数据库&#xff09; 二、MySQL的基本使用1. 连接服务器2. 服务器管理3. 服务器、数据库、表关系4. 使用案例 三、数据的逻辑存储和实际存储四、MySQL的架构五、SQL分类六、存储引擎 一、…

Java基础-015-System.java常用类

Java基础-015-System.java常用类 1、标准输入输出2、获取属性3、System.java初始化4、设置标准输出System.out java/lang/System.java 1、标准输入输出 System.in、System.out public class Test {public static void main(String[] args) {String charsetName String.valueOf…

青少年python大赛知识点学习5--字典

python中的字典&#xff0c;与我们使用的新华字典有点像。索引与对象&#xff0c;新华字典存的是每个字的相关内容&#xff0c;python中的字典是一个容器类型的数据结构&#xff0c;通过key进行索引。 1.什么是字典 字典是python内置的重要数据之一&#xff0c;与列表一样是一…

Intel oneAPI笔记(2)--jupyter官方文档(oneAPI_Intro)学习笔记

前言 本文是对jupyterlab中oneAPI_Essentials/01_oneAPI_Intro文档的学习记录&#xff0c;包含对SYCL、DPC extends SYCL、oneAPI Programming models等介绍和SYCL代码的初步演示等内容 oneAPI编程模型综述 oneAPI编程模型提供了一个全面而统一的开发人员工具组合&#xff0…

Go与数据库:NoSQL数据库的应用

大家好&#xff01;我是[lincyang]。 今天我们将一起探索Go语言与NoSQL数据库结合的强大能力&#xff0c;并通过五个实际案例来深入理解它们的应用。 1. Go与MongoDB的结合 1.1 用户管理系统 在用户管理系统中&#xff0c;我们需要存储用户的基本信息和权限设置。MongoDB的…

MQTT协议零基础快速入门

MQTT协议零基础快速入门 MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级的发布/订阅消息传输协议&#xff0c;广泛应用于物联网&#xff08;IoT&#xff09;和机器对机器&#xff08;M2M&#xff09;通信场景。它具有简单、开放、易于实现等优…

产品经理入门学习(五):思维导图 原型设计

参考引用 黑马-产品经理入门基础课程 1. 思维导图的作用和应用场景 什么是思维导图&#xff1f; 思维导图是一种将思维进行可视化的实用工具。具体实现方法是用一个关键词去引发相关想法&#xff0c;再运用图文并茂的技巧把各级主题的关系用相互隶属的层级表现出来&#xff0c;…

多态 虚函数表深度剖析 纯干货讲解(2)

&#x1f4af; 博客内容&#xff1a;多态 &#x1f600; 作  者&#xff1a;陈大大陈 &#x1f680; 个人简介&#xff1a;一个正在努力学技术的准C后端工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎私信&#xff01; &#x1f496; 欢迎大家&#xff1a;这里是CSD…

crond服务

目录 一、crond服务基础知识 1、crond服务介绍 2、查看crond服务的状态 3、crond服务配置文件详解 4、额外的配置文件目录 二、crond服务基础命令 1、crond服务使用 2、 管理和操作 crond 服务 3、crond服务命令举例 一、crond服务基础知识 1、crond服务介绍 1、crond…

【redis面试题】双写一致性

文章目录 前言一、什么是双写一致性1. 先删除缓存还是先修改数据库1.1 先删除缓存的情况1.2 先修改数据库的情况 2. 双写一致性的解决方案 二、允许延迟一致的解决方法1. 采用 MQ 中间件2. 采用 canal 中间件 三、强一致性的解决方法1. 采用 Redisson 提供的读写锁 前言 跟着B…

7-2 数论中的模幂运算

solution 欧拉函数法可以解决模幂运算 #include<stdio.h> #include<math.h> int main(){int a, m, n, r1;scanf("%d%d%d", &a, &m, &n);while(m){if(m&1) r(r*a)%n;a(a*a)%n;m>>1; }printf("%d", r);return 0; }给定伪…

AtCoder Beginner Contest 327 G. Many Good Tuple Problems(带标号二分图计数+有区别小球放入有区别盒子)

题目 一个长为n(n<30)的原始序列x&#xff0c;x[i]可以取值0或1 一个长为m(m<1e9)的点对序列(s,t)&#xff0c; s序列第i项和t的第i项&#xff0c;均可以取值[1,n]&#xff0c; 如果构造好s和t后&#xff0c;对任意都存在01序列x使得&#xff0c; 则称这个序列是合法…

基于8086家具门安全控制系统设计

**单片机设计介绍&#xff0c;基于8086家具门安全控制系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 # 8086家具门安全控制系统设计介绍 8086家具门安全控制系统是一种用于保护家具和保证室内安全的系统。该系统基于808…

类的成员函数总结

前言&#xff1a; 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。 默认成员函数&#xff1a;用户没有显式实现&#xff0c;编译器会生成的…

【java学习—十三】处理流之一:缓冲流(1)

文章目录 0. 引言1. 缓冲流2. 缓冲字节流2.1. 缓冲字节输入流2.2. 缓冲字节输出流2.3. 缓冲流实现文件的复制 3. 缓冲字符流 0. 引言 上一章节讲了&#xff1a;文件字符流和字节流&#xff08;基于硬盘&#xff09; 其中&#xff0c;用到的方法有&#xff1a;FlieInuputStream、…

JDBC简单流程

Step1&#xff1a;加载对应数据库&#xff08;比如mysql、oracle、sqlserver&#xff09;的驱动器 1、以mysql驱动器为例&#xff0c;下载好对应的jar包后&#xff0c;在项目对应目录下新建一个lib文件夹&#xff0c;将jar包复制到该文件夹中。 2、然后选中改jar包&#xff0…

链队的练习

链队的练习 相关内容&#xff1a;队列的链式存储结构&#xff08;链队&#xff09; //链队的初始化、入队、出队、取对头 #include<stdio.h> #include<malloc.h> #define OK 1 #define ERROR 0 typedef int Status; //结点结构 typedef struct QNode{ int data; …

单链表的应用(1)

移除链表元素 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 思路&#xff1a;&#xff08;1&#xff09;创建三个结构体指针&#xff0c;分别代表一条新链表的头newhead&#xff0c;…

2023 electron最新最简版打包、自动升级详解

这里我将讲解一下从0搭建一个electron最简版架子&#xff0c;以及如何实现打包自动化更新 之前我有写过两篇文章关于electron框架概述以及 常用api的使用&#xff0c;感兴趣的同学可以看看 Electron桌面应用开发 Electron桌面应用开发2 搭建electron 官方文档&#xff1a;ht…

基于变色龙算法的无人机航迹规划-附代码

基于变色龙算法的无人机航迹规划 文章目录 基于变色龙算法的无人机航迹规划1.变色龙搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用变色龙算法来优化无人机航迹规划。 1.变色龙…