Java 数据结构篇-实现双链表的核心API

news2024/10/7 6:37:50

🔥博客主页: 小扳_-CSDN博客
❤感谢大家点赞👍收藏⭐评论✍
 

 

 

文章目录

        1.0 双链表的说明

        1.1 双链表 - 创建

        1.2 双链表 - 根据索引查找节点

        1.3 双链表 - 根据索引插入节点

        1.4 双链表 - 头插节点

        1.5 双链表 - 尾插

        1.6 双链表 - 根据索引来删除节点

        1.7 头删节点

        1.8 尾删节点

        1.9 实现迭代器循环

        2.0 双链表完整的实现代码

        3.0 环形双链表的说明

        3.1 环形双链表 - 创建

        3.2 环形双链表 - 头插节点

        3.3 环形双链表 - 尾插节点

        3.4 环形双链表 - 头删节点

        3.5 环形双链表 - 尾删节点

        3.6 环形链表 - 根据值来删除节点

        4.0 环形双链表完整的实现代码


        1.0 双链表的说明

        双链表是一种数据结构,其中每个节点包含两个指针一个指向前一个节点一个指向后一个节点。双链表可以在任意位置插入或删除节点,而不需要像单链表那样遍历整个链表来找到需要操作的位置。双链表可以用于实现栈、队列、以及其他需要快速插入和删除操作的数据结构。由于每个节点包含两个指针,双链表的内存占用量通常比单链表更大

        1.1 双链表 - 创建

        把双链表封装成一个类,类中还需要封装一个节点类 Node,该节点类的成员有指向前一个节点的变量 Node prev,值 value ,指向后一个节点的变量 Node next

代码如下:

public class MyDoubleLists{
    private final Node hand;
    private final Node tail;

    private static class Node {
        public Node prev;
        public Node next;
        public int value;

        public Node(Node prev, int value, Node next) {
            this.prev = prev;
            this.next = next;
            this.value = value;
        }
    }

    public MyDoubleLists() {
        hand = new Node(null,0,null);
        tail = new Node(hand,-1,null);
        hand.next = tail;
        tail.prev = hand;
    }

}

         注意外部类中,还需要定义头节点,尾节点,再利用外部类的构造器中就可以完成对头尾节点的初始化了。内部类中的节点类建议用静态来修饰。

        1.2 双链表 - 根据索引查找节点

        由于这个方法可以为其他几个方法提供服务,因此,把这个方法独立出来。实现思路为:开始的节点应为 hand,循环终止条件为:p == tail 当指向尾节点的那一刻就该结束循环了。还要加一个变量 i = -1 ,每一次循环结束要进行 i++ ,当 i == index 时,找到了该索引的节点,返回该节点即可,若循环结束还是没找到,就得返回 null

代码如下:

    //根据索引查找节点
    private Node findNode(int index) {
        int i = -1;
        for (Node p = hand; p !=tail ; p = p.next,i++){
            if (i == index) {
                return p;
            }
        }
        return null;
    }

        这个方法一般不会对外开发,因此,加上 private 来修饰,当 i == -1 时,返回的时头节点,所以一开始的节点为 hand 。对外是索引从 0 开始才会存储值的,对于头节点存储什么值是不关心的。

        1.3 双链表 - 根据索引插入节点

         根据索引插入节点,提前需要准备好该节点的前一个节点 prev、该节点的后一个节点 next 。通过以上已经实现的方法来找到插入位置的前一个结点,prev = findNode(index - 1),后一个节点也就可以找到了,next = prev.next

代码如下:

    //根据索引插入节点
    public void insert(int index, int value) {
        Node p = findNode(index - 1);
        if (p == null){
            throw new RuntimeException("用索引访问失败");
        }
        Node prev = p;
        Node next = prev.next;
        Node insertNode = new Node(prev, value,next);
        prev.next = insertNode;
        next.prev = insertNode;
    }

        prev.next 指向新的节点next.prev 指向上一个节点。新的节点的头节点的引用为 prev,尾节点为 next 。 

         1.4 双链表 - 头插节点

        其实这个方法就比较简单了,这个就是根据索引 0 来插入节点的,就是一个索引等于0的特例,所以可以直接调用已经实现的 insert(0,int value) 方法。

代码如下:

    //头插节点
    public void addFirst(int value) {
        insert(0,value);
    }

         1.5 双链表 - 尾插

        对尾部的操作是双链表的一个很大的优势,对尾部的查询、增加节点、删除节点效率都很快,因为对尾节点是有记录的,就不用从头开始来查找尾部节点了,直接可以得到。

        实现方法的思路为:需要找到插入的前一个节点插入的后一个节点肯定是尾节点,所以,可以通过   prev =  tail.prev,来找到插入的前一个节点。

代码如下:

    //尾插节点
    public void addLast(int value) {
        Node lats = tail;
        Node prev = lats.prev;
        Node addNode = new Node(prev,value,lats);
        prev.next = addNode;
        lats.prev = addNode;
    }

        新的节点的前一个节点为 prev ,后节点为 tail,prev.next 与 tail.prev 就要更改为指向新的节点了。

        1.6 双链表 - 根据索引来删除节点

        先通过已经实现的 findNode() 方法来找到该索引的节点,还有找到删除该节点的前一个节点与后一个节点。

代码如下:

    //根据索引来删除节点
    public void remove(int index) {
        Node prev = findNode(index - 1);
        if (prev == null) {
            throw new RuntimeException("用索引来删除数据失败!");
        }
        Node remove = prev.next;
        if (remove == tail) {
            throw new RuntimeException("用索引来删除数据失败!");
        }
        Node next = remove.next;

        prev.next = next;
        next.prev = next;
    }

         接着就可以将要删除的前一个节点指向要删除的节点后一个节点,但是考虑两种情况,第一种,当要删除的节点没找到,就要抛出异常了。第二种,当发现要删除的节点为 tail 时需要抛出异常,总不能自己把自己的尾巴 "切了吧" 然而不用考虑是否把头节点删除,因为要删除的节点总是拿不到头节点。

         1.7 头删节点

        当索引为0时,就是头删,是 remove(0) 方法的一个特例。可以直接来调用根据索引来删除节点的方法。

代码如下:

    //头删节点
    public void removeFirst() {
        remove(0);
    }

         1.8 尾删节点

        双链表尾删的效率是很高的,因为对尾节点是有记录的。

代码如下:

    //尾删节点
    public void removeLast() {
        Node remove = tail.prev;
        if (remove == hand){
            throw new RuntimeException();
        }
        Node prev = remove.prev;
        prev.next = tail;
        tail.prev = prev;
    }

        需要找到三个节点,要删除的节点、删除节点的前一个节点、还有尾节点。需要注意的是,判断要删除的节点为头节点,需要抛出异常,总不能自己把自己的 " 头部 " 给切了吧。

         1.9 实现迭代器循环

        这个完成 Iterable 接口,创建新的该接口子类对象,重写两个方法即可。

代码如下:

    //实现迭代器循环
    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            Node p = hand.next;
            @Override
            public boolean hasNext() {
                return p != tail;
            }

            @Override
            public Integer next() {
                int value = p.value;
                p = p.next;
                return value;
            }
        };

    }

        需要注意的是,头节点的值是不用在乎的,所以遍历开始为 hand.next ,循环结束条件为,p == tail,遍历到尾节点就要终止了。

        2.0 双链表完整的实现代码

 

import java.util.Iterator;

public class MyDoubleLists implements Iterable<Integer>{
    private final Node hand;
    private final Node tail;

    private static class Node {
        public Node prev;
        public Node next;
        public int value;

        public Node(Node prev, int value, Node next) {
            this.prev = prev;
            this.next = next;
            this.value = value;
        }
    }

    public MyDoubleLists() {
        hand = new Node(null,0,null);
        tail = new Node(hand,-1,null);
        hand.next = tail;
        tail.prev = hand;
    }

    //根据索引查找节点
    private Node findNode(int index) {
        int i = -1;
        for (Node p = hand; p !=tail ; p = p.next,i++){
            if (i == index) {
                return p;
            }
        }
        return null;
    }

    //根据索引插入节点
    public void insert(int index, int value) {
        Node p = findNode(index - 1);
        if (p == null){
            throw new RuntimeException("用索引访问失败");
        }
        Node prev = p;
        Node next = prev.next;
        Node insertNode = new Node(prev, value,next);
        prev.next = insertNode;
        next.prev = insertNode;
    }

    //头插节点
    public void addFirst(int value) {
        insert(0,value);
    }

    //尾插节点
    public void addLast(int value) {
        Node lats = tail;
        Node prev = lats.prev;
        Node addNode = new Node(prev,value,lats);
        prev.next = addNode;
        lats.prev = addNode;
    }

    //根据索引来删除节点
    public void remove(int index) {
        Node prev = findNode(index - 1);
        if (prev == null) {
            throw new RuntimeException("用索引来删除数据失败!");
        }
        Node remove = prev.next;
        if (remove == tail) {
            throw new RuntimeException("用索引来删除数据失败!");
        }
        Node next = remove.next;

        prev.next = next;
        next.prev = next;
    }

    //头删节点
    public void removeFirst() {
        remove(0);
    }

    //尾删节点
    public void removeLast() {
        Node remove = tail.prev;
        if (remove == hand){
            throw new RuntimeException();
        }
        Node prev = remove.prev;
        prev.next = tail;
        tail.prev = prev;
    }

    //根据索引来查询数据
    public int get(int index) {
        Node find = findNode(index);
        if (find == null){
            throw new RuntimeException("根据该索引找不到数据");
        }
        return find.value;
    }

    //实现迭代器循环
    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            Node p = hand.next;
            @Override
            public boolean hasNext() {
                return p != tail;
            }

            @Override
            public Integer next() {
                int value = p.value;
                p = p.next;
                return value;
            }
        };

    }

}

        3.0 环形双链表的说明

        环形双链表是一种数据结构,它类似于双向链表,但是链表的最后一个节点指向第一个节点,形成一个环形结构。简单来说,先对比与一般的双链表,环形双链表就是头尾节点都是同一个节点

        3.1 环形双链表 - 创建

        把环形双链表看成一个对象,该类中需要封装一个节点的内部类,对外不开放的,需要用限制修饰符来修饰。外部类中的成员变为 sentinel ,即作为头节点,也作为尾节点

代码如下:

public class MyAnnularDoubleList{
    private final Node sentinel;
    private static class Node {
        public Node prev;
        public int value;
        public Node next;

        public Node(Node prev, int value, Node next) {
            this.prev = prev;
            this.value = value;
            this.next = next;
        }
    }

    public MyAnnularDoubleList() {
        sentinel = new Node(null,0,null);
        sentinel.prev = sentinel;
        sentinel.next = sentinel;
    }

}

        在创建环形双链表时,在用无参构造器就可以先把 sentinel 初始化了。

        3.2 环形双链表 - 头插节点

        根据 next =  sentinel.next 就可以得到插入点的下一个节点,就可以把新节点的指向的前一个节点来指向 sntinel,新节点的指向后一个节点来指向 next

        这里需要考虑一种情况,假设,该环形双链表中就只有一个 sentinel 节点,以上阐述的思路还能不能用呢?答案时可以的,如果实在理解不了的话,可以把一个 sentinel 节点,想象成两个 sentinel 节点,效果都是一样的,都是指向自己嘛。

代码如下:

    //头插节点
    public void addFirst(int value) {
        Node hand = sentinel;
        Node tail = sentinel.next;
        Node addNode = new Node(hand,value,tail);
        hand.next = addNode;
        tail.prev = addNode;
    }

        3.3 环形双链表 - 尾插节点

        根据 prev = sentinel.prev 来找到插入节点的前一个的节点,插入节点的后一个节点就是 sentinel ,新节点的指向前的节点为 prev,新节点的指向后的节点为 sentinel ,接着 prev.next 指向新节点,sentinel.prev 指向新节点。

代码如下:

    //尾插节点
    public void addLast(int value) {
        Node prev = sentinel.prev;
        Node next = sentinel;
        Node addNode = new Node(prev,value,next);
        prev.next = addNode;
        next.prev = addNode;

    }

        同理,不需要额外考虑当节点只有一个时,以上代码依旧可以实现尾插节点。

        3.4 环形双链表 - 头删节点

        根据 remove = sentinel.next ,可以得到需要删除的节点,再由 next = remove.next,得到删除节点的后一个节点,这样三个节点后已知了,santinel.next 指向 next ,next.prev 指向sentinel ,剩下的没有被引用的节点(对象),会被 JVM 自动回收

代码如下:

    //头删节点
    public void removeFirst() {
        Node remove = sentinel.next;
        if (remove == sentinel) {
            throw new RuntimeException("删除失败!");
        }
        Node next = remove.next;
        sentinel.next = next;
        next.prev = sentinel;
    }

        需要注意的是,当删除的节点为 sentinel 需要抛出异常,但是个人感觉不加判断也可以,sentinel 根本不会被删除,即使只有 sentinel 一直被删除。

        3.5 环形双链表 - 尾删节点

        根据 remove = sentinel.prev 得到需要删除的节点,通过 prev = remove.prev 得到需要删除的前一个节点,prev.next 指向 sentinelsentinel.prev 指向 prev 即可。

代码如下:

    //尾删节点
    public void removeLast() {
        Node remove = sentinel.prev;
        Node prev = remove.prev;
        prev.next = sentinel;
        sentinel.prev = prev;
    }

        同样的,当删除的节点为 sentinel 需要抛出异常,但是个人感觉不加判断也可以,sentinel 根本不会被删除,即使只有 sentinel 一直被删除。

         3.6 环形链表 - 根据值来删除节点

        先独立一个方法,根据值来查询节点,一开始的循环节点为 sentinel.next ,对于 sentinel 里面的值是什么根本不在乎的。循环终止条件为:p == sentinel 已经转完一圈了。找到就返回该节点,找不到就返回 null 。删除的原理是一样的,得先找到三个节点,然后把前后节点直接关联起来。

代码如下:

    //根据值来删除节点
    private Node findValue (int value) {
        for (Node p = sentinel.next; p != sentinel; p = p.next) {
            if (p.value == value) {
                return p;
            }
        }
        return null;
    }

    public void removeValue (int value) {
        Node remove = findValue(value);
        if (remove == null) {
            throw new RuntimeException("找不到该数据来删除相对应的节点");
        }
        Node prev = remove.prev;
        Node next = remove.next;
        prev.next = next;
        next.prev = prev;
    }

        需要注意的是,当接收到的节点为 null 时,需要抛出异常,找不到对应该值的节点。

        4.0 环形双链表完整的实现代码

import java.util.Iterator;

public class MyAnnularDoubleList implements Iterable<Integer>{
    private final Node sentinel;
    private static class Node {
        public Node prev;
        public int value;
        public Node next;

        public Node(Node prev, int value, Node next) {
            this.prev = prev;
            this.value = value;
            this.next = next;
        }
    }

    public MyAnnularDoubleList() {
        sentinel = new Node(null,0,null);
        sentinel.prev = sentinel;
        sentinel.next = sentinel;
    }

    //头插节点
    public void addFirst(int value) {
        Node hand = sentinel;
        Node tail = sentinel.next;
        Node addNode = new Node(hand,value,tail);
        hand.next = addNode;
        tail.prev = addNode;
    }

    //尾插节点
    public void addLast(int value) {
        Node prev = sentinel.prev;
        Node next = sentinel;
        Node addNode = new Node(prev,value,next);
        prev.next = addNode;
        next.prev = addNode;

    }


    //头删节点
    public void removeFirst() {
        Node remove = sentinel.next;
        if (remove == sentinel) {
            throw new RuntimeException("删除失败!");
        }
        Node next = remove.next;
        sentinel.next = next;
        next.prev = sentinel;
    }

    //尾删节点
    public void removeLast() {
        Node remove = sentinel.prev;
        Node prev = remove.prev;
        prev.next = sentinel;
        sentinel.prev = prev;
    }

    //根据值来删除节点
    private Node findValue (int value) {
        for (Node p = sentinel.next; p != sentinel; p = p.next) {
            if (p.value == value) {
                return p;
            }
        }
        return null;
    }

    public void removeValue (int value) {
        Node remove = findValue(value);
        if (remove == null) {
            throw new RuntimeException("找不到该数据来删除相对应的节点");
        }
        Node prev = remove.prev;
        Node next = remove.next;
        prev.next = next;
        next.prev = prev;
    }

    //实现迭代器
    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            Node p = sentinel.next;
            @Override
            public boolean hasNext() {
                return p != sentinel;
            }

            @Override
            public Integer next() {
                int value = p.value;
                p = p.next;
                return value;
            }
        };
    }
}

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

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

相关文章

VS Code Counter统计代码量(vscode扩展工具)

1.VS Code Counter 该vscode扩展工具用于统计代码行数以及代码量等信息。 2. 安装 既可以点击左侧扩展图标&#xff0c;也可以 ShiftCtrlX vscode快捷命令打开扩展&#xff1a;商店&#xff0c;然后在商店中搜索 VS Code Counter&#xff0c;并点击安装。 3. 使用 使用时用…

ggrcs包3.5版本发布—增加了大家喜闻乐见的P for overall和主题色彩定制

目前本人写的ggrcs包新的3.5版本已经在CRAN上线&#xff0c;目前支持逻辑回归&#xff08;logistic回归&#xff09;、cox回归和多元线性回归。 需要的可以使用代码安装 install.packages("ggrcs")如果原来安装了旧版本&#xff0c;可以通过Rstudio进行升级 这样就…

ArcGIS丨SWAT丨农业水土环境及面源污染建模及对农业措施的响应

农业面源污染治理是生态环境保护的重要内容&#xff0c;事关农村生态文明建设&#xff0c;事关国家粮食安全和农业绿色发展&#xff0c;事关城乡居民的水缸子、米袋子、菜篮子。加强农业水土环境建模与农业面源污染治理与监督指导&#xff0c;可以保护生态环境&#xff0c;维护…

[黑马程序员Pandas教程]——Pandas常用计算函数

目录&#xff1a; 学习目标排序函数 sort_values函数rank函数常用聚合函数 corr函数计算数值列之间的相关性min函数计算最小值max函数计算最大值mean函数计算平均值std函数计算标准偏差quantile函数计算分位数sum函数求和count计算非空数据的个数其他常用计算函数 round改变浮…

Docker学习——⑤

文章目录 1、什么是Docker Container&#xff08;容器&#xff09;2、容器的生命周期2.1 容器 OOM2.2 容器异常退出2.3 容器暂停 3、容器命令详解4、容器操作案例4.1 容器批量处理技巧4.2 容器交互模式4.3 容器自动重启4.4 容器环境变量配置 5、综合实战5.1 Mysql 容器化安装5.…

clang插件对llvm源码插桩,分析函数调用日志(2)

tick_plot__compile.ipynb clang插件对llvm源码插桩&#xff0c;分析函数调用日志(1) 分析 进出、链、出 df进出df[ df[tickKind].isin( [FuncEnter,FuncReturn] ) ]#代码中&#xff0c;只有在函数进入时&#xff0c;计算了链条长度 并写磁盘 df入df[ df[tickKind].isin…

基于React使用swiperjs实现竖向滚动自动轮播

很多文章&#xff0c;都只提供了js部分&#xff0c;包括官方的文档也只有js部分&#xff0c;如果css设置不正确&#xff0c;会导致轮播图不自动播放。 使用的swiper版本&#xff1a;v11.0.3 文档 https://swiperjs.com/get-startedhttps://swiperjs.com/react 实现效果 使…

Go和JavaScript结合使用:抓取网页中的图像链接

前言 在当今数字化时代&#xff0c;数据是金钱的源泉&#xff0c;对于许多项目和应用程序来说&#xff0c;获取并利用互联网上的数据是至关重要的。其中之一的需求场景是从网页中抓取图片链接&#xff0c;这在各种项目中都有广泛应用&#xff0c;特别是在动漫类图片收集项目中…

C# OpenCvSharp 去除文字中的线条

效果 中间过程效果 项目 代码 using OpenCvSharp; using System; using System.Drawing; using System.Windows.Forms; using static System.Net.Mime.MediaTypeNames;namespace OpenCvSharp_Demo {public partial class frmMain : Form{public frmMain(){InitializeComponent…

JavaScript学习笔记——对象

JavaScript 中的所有事物都是对象&#xff1a;字符串、数值、数组、函数...除此之外JavaScript 允许自定义对象。 一、所有事物都是对象 JavaScript 提供多个内建对象&#xff0c;比如 String、Date、Array 等等。 对象只是带有属性和方法的特殊数据类型。 1.布尔型可以是一个…

FHE Circuit Privacy

参考文献&#xff1a; [MP12] Micciancio D, Peikert C. Trapdoors for lattices: Simpler, tighter, faster, smaller[C]//Annual International Conference on the Theory and Applications of Cryptographic Techniques. Berlin, Heidelberg: Springer Berlin Heidelberg, …

视频转码教程:轻松制作GIF动态图,一键高效剪辑操作

随着社交媒体的兴起&#xff0c;GIF动态图已经成为了人们表达情感、分享精彩瞬间的重要方式。而将视频转化为GIF动态图&#xff0c;不仅可以方便地在社交媒体上分享&#xff0c;还可以延长视频的播放时长&#xff0c;吸引更多的观众。本篇文章将为大家介绍如何将视频轻松转化为…

使用 Ruby 的 Nokogiri 库来解析

爬虫程序的主要目标是获取指定网站上的数据。在这里&#xff0c;我们将使用 Ruby 的 Nokogiri 库来解析 HTML&#xff0c;并使用 HTTParty 库来发送 HTTP 请求。下面是一个简单的示例&#xff0c;演示如何使用 Ruby 编写一个爬虫程序来爬取 1688 网站的数据。 require nokogir…

计网----累积应答,TCP的流量控制--滑动窗口,粘包问题,心跳机制,Nagle算法,拥塞控制,TCP协议总结,UDP和TCP对比,中介者模式

计网----累积应答&#xff0c;TCP的流量控制–滑动窗口&#xff0c;粘包问题&#xff0c;心跳机制&#xff0c;Nagle算法&#xff0c;拥塞控制&#xff0c;TCP协议总结&#xff0c;UDP和TCP对比&#xff0c;中介者模式 一.累积应答 1.什么是累计应答 每次发一些包&#xff0…

【小尘送书-第十一期】《算法秘籍》:算法是编程的基石,开发的核心

大家好&#xff0c;我是小尘&#xff0c;欢迎你的关注&#xff01;大家可以一起交流学习&#xff01;欢迎大家在CSDN后台私信我&#xff01;一起讨论学习&#xff0c;讨论如何找到满意的工作&#xff01; &#x1f468;‍&#x1f4bb;博主主页&#xff1a;小尘要自信 &#x1…

【RocketMQ】深入剖析延迟消息核心实现原理

一、背景 电商相关业务的时候&#xff0c;有一个常见的需求场景是&#xff1a;用户下单之后&#xff0c;超过半小时不支付&#xff0c;就取消订单。现在我们在淘宝京东买东西&#xff0c;或者通过美团点外卖&#xff0c;下单之后&#xff0c;如果不在指定时间内支付&#xff0…

个人实用的街头防身自卫术,男女必学的防身实战技能

一、教程描述 本套教程&#xff0c;大小455.93M&#xff0c;共有17个文件。 二、教程目录 实战防身术01、街头防身自卫术示例.mp4 实战防身术02、街头防身自卫术序言.mp4 实战防身术03、腕部被抓解脱.mp4 实战防身术04、胸襟被抓解脱.mp4 实战防身术05、腰部被抓解脱.mp…

应用在全固态激光雷达中的ALS环境光传感芯片

全固态扫描式激光雷达系统这一创新性技术在多个领域都有着巨大的潜力&#xff0c;将改变未来科技格局。本文将探讨这一革命性的发明&#xff0c;以及它在自动驾驶、无人机、工业自动化、环境监测等领域的关键应用。 传统激光雷达系统通常使用复杂的机械装置&#xff0c;这些部…

如何上传自己的Jar到Maven中央仓库

在项目开发过程中&#xff0c;我们常常会使用 Maven 从仓库拉取开源的第三方 Jar 包。本文将带领大家将自己写好的代码或开源项目发布到 Maven中央仓库中&#xff0c;让其他人可以直接依赖你的 Jar 包&#xff0c;而不需要先下载你的代码后 install 到本地。 注册帐号 点击以…

基于Pymavlink协议的BlueROV开发

1 BlueROV概述 1.1 什么是ROV 维基百科遥控潜水器&#xff08;Remotely operated underwater vehicle&#xff0c;缩写ROV&#xff09;是一个无人的水下航行器&#xff0c;以电缆连接到母船的人员操作。常搭载水下光源和照相机、摄影机、机械手臂、声纳等。因为具有机械手臂&a…