JAVA数据结构之顺序表、单向链表及双向链表的设计和API实现

news2025/1/18 20:19:40

一、顺序表

顺序表在内存中是数组的形式存储

类名SequenceList
构造方法SequenceList(int capacity):创建容量为capacity的SequenceList对象
成员方法1. public void clear():空置线性表
2. public boolean isEmpty():判断线性表是否为空,是返回true,否返回false
3. public int length():获取线性表中元素的个数
4. public T get(int i):读取并返回线性表中的第i个元素的值
5. public void insert(int i,T t):在线性表的第i个元素之前插入一个值为t的数据元素
6. public void insert(T t):向线性表中添加一个元素t
7. public T remove(int i):删除并返回线性表中第i个数据元素。
8. public int indexOf(T t):返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1。
成员变量1. private T[] eles:存储元素的数组
2.private int N:当前线性表的长度

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FJhR2QzJ-1681547704344)(C:\Users\29973\AppData\Roaming\Typora\typora-user-images\image-20230413095931799.png)]

//用来构造顺序表
public class SequenceList<T> implements Iterable{

    //存储元素的数组
    private T[] eles;
    //当前线性表的长度
    private int N;
    //获得当前容量
    private int capacity;

    //构造方法
    public SequenceList(int capacity) {
        //强制类型转化
        this.eles = (T[])new Object[capacity];
        //初始化长度
        N = 0;
        //获取当前顺序表的容量
        this.capacity = capacity;
    }

    //空置线性表
    public void clear() {
        for (int index = 0 ; index<getLength();index++){
            eles[index] = null;
        }
        this.N = 0;
    }

    //判断线性表是否为空,是返回true,否返回false
    public boolean isEmpty() {
        return N==0;
    }

    //获取线性表中元素的个数
    public int getLength() {
        return N;
    }

    //获取线性表的容量
    public int getCapacity() {
        return capacity;
    }

    //读取并返回线性表中的第i个元素的值
    public T get(int i) {
        if (i <= 0 || i>=N){
            throw new RuntimeException("当前元素不存在");
        }
        return eles[i];
    }

    //在线性表的第i个元素之前插入一个值为t的数据元素
    public void insert(int i,T t) {
        //如果当前元素的数量等于当前数组的长度
        if(N==eles.length){
            //扩容
            resize(2*eles.length);
        }
        //将i索引处以及后面的元素全部向后移动
        for (int index = N;index>i;index--){
            eles[index] = eles[index-1];
        }
        eles[i] = t;
        N++;
    }

    //向线性表中添加一个元素t
    public void insert(T t) {
        //如果当前元素的数量等于当前数组的长度
        if(N==eles.length){
            //扩容
            resize(2*eles.length);
        }
        //先将N处赋值为t,随后N自增
        eles[N]=t;
        N++;
    }

    //删除并返回线性表中第i个数据元素
    public T remove(int i) {
        //记录i处索引的值,并且后面的值全部向前移动
        T current = eles[i];
        for (int index = i ; index<N-1; index++ ){
            eles[index] = eles[index+1];
        }
        //元素个数减一
        N--;
        //如果当前元素的数量等于当前(数组的长度/4)
        if(N<eles.length/4){
            //将顺序表的长度变为原来的一半
            resize(eles.length/2);
        }
        return current;
    }


    //根据参数的newSize,重新来设置数组的大小
    public void resize(int newSize){
        //定义一个临时数组,指向原数组,用来提供复制数组
        T[] temp = eles;
        //容量翻倍
        eles = (T[]) new Object[newSize];
        //把原数组的数据拷贝到新数组即可
        for (int i = 0 ; i<N;i++){
            eles[i] = temp[i];
        }
        //重新定义容量的大小
        capacity = newSize ;
    }

    //返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返 回-1。
    public int indexOf(T t){
        if(t==null){
            throw new RuntimeException("查找的元素不合法");
        }
        for (int i = 0; i < N; i++) {
            if (eles[i].equals(t)){
                return i;
            }
        }
        return -1;
    }

    //实现遍历输出
    @Override
    public Iterator iterator() {
        return new SIterator();
    }

    private class SIterator implements Iterator{

        private int index;
        public SIterator(){
            this.index = 0;
        }
       @Override
        public boolean hasNext() {
            //表示还有下一个元素
            return index<N;
        }

        @Override
        public Object next() {
            return eles[index++];
        }
    }
}
  • 从以上代码可得知,顺序表查找元素很快,只需要进行一次遍历即可,时间复杂度为O(1);
  • 在插入或者删除元素的时候,我们要进行相应的元素移动,时间复杂度大,时 间复杂为O(n);

二、链表

链表分为两个类组成,分别为Node节点类和LinkList链表类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CBhN8HRy-1681547848794)(C:\Users\29973\AppData\Roaming\Typora\typora-user-images\image-20230413100004528.png)]

2.1 单向链表


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FWtSJUS9-1681547848794)(C:\Users\29973\AppData\Roaming\Typora\typora-user-images\image-20230413101025423.png)]

2.1.1 设计Node节点类

类名Node
构造方法Node(T t,Node next):创建Node对象
成员变量T item:存储数据
Node next:指向下一个结点
//用来定义节点
public class Node<T> {
    //存储数据
    T item;
    //下一个节点
    Node next;
    
    public Node(T item, Node next) {
        this.item = item;
        this.next = next;
    }
}

2.1.2 设计LinkList链表类

类名LinkList
构造方法LinkList():创建LinkList对象
成员方法1. public void clear():空置线性表
2. public boolean isEmpty():判断线性表是否为空,是返回true,否返回false
3. public int length():获取线性表中元素的个数
4. public T get(int i):读取并返回线性表中的第i个元素的值
5. public void insert(T t):往线性表中添加一个元素;
6. public void insert(int i,T t):在线性表的第i个元素之前插入一个值为t的数据元素。
7. public T remove(int i):删除并返回线性表中第i个数据元素。
8. public int indexOf(T t):返回线性表中首次出现的指定的数据元素的位序号,若不存在,则 返回-1。
成员内容类private class Node:结点类
成员变量1. private Node head:记录首结点
2.private int N:记录链表的长度
//实现链表的构造
public class LinkList<T> implements Iterable{
    //记录头节点
    private Node<T> head;
    //记录链表的长度
    private int N;

    //构造方法
    public LinkList(){
        //初始化头节点
        this.head = new Node<T>(null,null);
        //初始化元素个数
        this.N = 0;
    }

    //清空链表:原理是断开头节点的指向,并且将链表置零
    public void clear(){
        head.next = null;
    }

    //获取链表的长度
    public int length(){
        return N;
    }

    //判断链表是否为空
    public boolean isEmpty(){
        return N==0;
    }

    //获取指定位置i处的元素
    public T get(int i){
        Node n = head.next;
        for (int index = 0 ; index<i ; index++){
            n = n.next;
        }
        return (T) n.item;
    }

    //向链表中插入元素
    public void insert(T t){
        //找到当前的最后一个节点
        Node n = head;
        while (n.next!=null){
            n = n.next;
        }
        //创建节点,保存元素
        Node newNode = new Node(t,null);
        //让当前最后一个节点指向新节点
        n.next = newNode;
        //元素的个数++
        N++;
    }

    //向链表的指定位置插入元素
    public void insert(int i , T t){
        if (i<0 || i>=N){
            throw new RuntimeException("位置不合法");
        }
        //找到i-1和i节点
        Node preNode = head;// i-1 节点
        for (int index = 0 ; index<=i-1 ; index++){
            preNode = preNode.next;
        }
        Node currentNode = preNode.next;// i 节点
        //创建新节点newNode
        Node newNode = new Node(t,null);
        //让i-1节点的next节点指向创建的newNode
        preNode.next = newNode;
        //将newNode的next节点指向原本的i节点
        newNode.next = currentNode;
        //元素个数++
        N++;
    }

    //删除指定位置的索引,并且返回被删除的元素
    public T remove(int i){
        if (i<0 || i>=N){
            throw new RuntimeException("位置不合法");
        }
        //找到 i-1 、 i 、i+1 节点
        Node preNode = head ; //找到i-1
        for (int index = 0 ; index<=i-1 ; index++){
            preNode = preNode.next;
        }
        Node currentNode = preNode.next; //找到i
        Node nextNode = currentNode.next; //找到i+1
        //将i-1的next节点指向i+1即可
        preNode.next = nextNode ;
        //元素个数--
        N--;
        return (T) currentNode.item;
    }

    //查找元素t在链表中第一次出现的位置
    public int indexOf(T t){
        Node n = head ;
        //依次去除节点元素进行比较
        for (int i = 0;n.next!=null;i++){
            n = n.next;
            if (t.equals(n.item)){
                return i;
            }
        }
        return -1;
    }


    //实现遍历输出
    @Override
    public Iterator iterator() {
        return new LIterator();
    }

    public class LIterator implements Iterator{
        private Node n;
        public LIterator(){
            this.n = head;
        }
        @Override
        public boolean hasNext() {
            return n.next!=null;
        }

        //获取下一个元素的值
        @Override
        public Object next() {
            n = n.next;
            return n.item;
        }
    }
}

2.2 双向链表


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2VkiR53v-1681547985087)(C:\Users\29973\AppData\Roaming\Typora\typora-user-images\image-20230413170710947.png)]

2.2.1 设计Node节点类

类名Node
构造方法Node(T t,Node next):创建Node对象
成员变量T item:存储数据
Node next:指向下一个结点
Node pre:指向上一个结点
public class Node<T> {
    //下一个节点
    public Node next;
    //上一个节点
    public Node pre;
    //存储数据
    public T item;

    //构造函数
    public Node(Node pre, Node next, T item) {
        this.pre = pre;
        this.next = next;
        this.item = item;
    }
}

2.2.2 设计DoublyLinkList链表类

类名DoublyLinkList
构造方法DoublyLinkList():创建DoublyLinkList对象
成员方法1. public void clear():空置线性表
2. public boolean isEmpty():判断线性表是否为空,是返回true,否返回false
3. public int length():获取线性表中元素的个数
4. public T get(int i):读取并返回线性表中的第i个元素的值
5. public void insert(T t):往线性表中添加一个元素;
6. public void insert(int i,T t):在线性表的第i个元素之前插入一个值为t的数据元素。
7. public T remove(int i):删除并返回线性表中第i个数据元素。
8. public int indexOf(T t):返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1。
9. public T getFirst():获取第一个元素
10. public T getLast():获取最后一个元素
成员内容类private class Node:结点类
成员变量1. private Node head:记录首结点
2. private Node end:记录尾结点
3.private int N:记录链表的长度
public class DoublyLinkList<T> implements Iterable{
    //记录头节点
    private Node<T> head;
    //记录尾节点
    private Node<T> end;
    //记录链表的长度
    private int N;

    //构造方法
    public DoublyLinkList(){
        //初始化头节点和尾节点
        this.head = new Node<T>(null,null,null);
        this.end = null;
        //所以这个地方头节点和尾节点不占用长度
        this.N = 0;
    }

    //清空链表:原理是断开头节点和尾节点的指向,并且将链表置零
    public void clear(){
        head.next = null;
        end = null;
        N = 0;
    }

    //获取链表的长度
    public int length(){
        return N;
    }

    //判断链表是否为空
    public boolean isEmpty(){
        return N==0;
    }

    //获取第一个元素
    public T getHead(){
        if (isEmpty()){
            return null;
        }
        return (T) head.next.item;
    }

    //获取最后一个元素
    public T getLast(){
        if (isEmpty()){
            return null;
        }
        return (T) end.item;
    }


    //向链表中插入元素(默认是尾部)
    public void insert(T t){
        //1. 如果链表为空
        if (isEmpty()){
            //1.1 创建新的节点
            Node newNode = new Node(head, null, t);
            //1.2 让新的的节点称之为尾节点
            end = newNode;
            //1.3 让头节点指向尾节点
            head.next = end;
        }else {
        //2. 如果链表不为空
            Node oldNode = end;
            //2.1 创建新的节点
            Node newNode = new Node(oldNode,null,t);
            //2.2 让当前的尾节点指向新节点
            oldNode.next = newNode;
            //2.3 让新节点成为尾节点
            end = newNode;
        }
        N++;
    }

    //向链表的指定位置插入元素
    public void insert(int i , T t){
        if (i<0 || i>=N){
            throw new RuntimeException("位置不合法");
        }

        //找到 i-1 的节点
        //假如在 3 位置插入新节点,我们就是遍历从0-2,恰好可以找到2节点
        Node preNode = head;
        for (int index = 0 ; index<=i-1 ; index++){
            preNode = preNode.next;
        }
        //找到 i 位置节点
        Node currentNode = preNode.next;
        //创建新节点
        Node newNode = new Node(null,null,t);
        //修改指向
        preNode.next = newNode;
        newNode.next = currentNode;
        currentNode.pre = newNode;
        newNode.pre = preNode;
        //元素个数加一
        N++;
    }

    //删除指定位置的索引,并且返回被删除的元素
    public T remove(int i){
        if (i<0 || i>=N){
            throw new RuntimeException("位置不合法");
        }
        //找到i位置的节点
        Node node = head;
        for (int index = 0 ;index<=i ; index++){
            node = node.next;
        }
        //找到 i+1 位置节点
        Node nextNode = node.next;
        //找到 i-1 位置节点
        Node preNode = node.pre;
        //修改指向
        preNode.next = nextNode;
        nextNode.pre = preNode;
        N--;
        return (T) node.item;
    }

    //获取指定位置i处的元素
    public T get(int i){
        Node node = head;
        for (int index = 0 ; index<=i ; index++){
            node  = node.next;
        }
        return (T) node.item;
    }

    //查找元素t在链表中第一次出现的位置
    public int indexOf(T t){
        Node node = head;
        for (int index = 0 ; node.next != null ; index++){
            node  = node.next;
            if (t.equals(node.item)){
                return index;
            }
        }
        return -1;
    }

    @Override
    public Iterator iterator() {
        return new DIterator();
    }

    private class DIterator implements Iterator{
        private Node n;

        public DIterator() {
            this.n = head;
        }

        @Override
        public boolean hasNext() {
            return n.next != null;
        }

        @Override
        public Object next() {
            n = n.next;
            return n.item;
        }
    }
}

2.2.3 总结和提醒


总结

  • 每一次查询,都需要从链表的头部开始,依次向后查找,随着数据元素N的增多,比较的元素越多,时间复杂度为O(n)
  • 对于插入和移除节点,随着数据元素N的增多,查找的 元素越多,时间复杂度为O(n);
  • 相比较顺序表,链表插入和删除的时间复杂度虽然一样,但仍然有很大的优势,因为链表的物理地址是不连续的, 它不需要预先指定存储空间大小,或者在存储过程中涉及到扩容等操作,同时它并没有涉及的元素的交换。

提醒

其中对于双向链表遍历的边界值条件,如下代码

//获取指定位置i处的元素
public T get(int i){
    Node node = head;
    for (int index = 0 ; index<=i ; index++){
        node  = node.next;
    }
    return (T) node.item;
}

我们需要获取到i处的元素,因为我们是从头节点head开始遍历,所以其实我们的跳数也是从头节点开始计算,假设我们需要定位到2号节点,从head节点开始计算,index设置为0,我们刚好要移动3次。即要定位i处的元素,边界值判定条件恰好为i

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l9GLQKD9-1681548030257)(C:\Users\29973\AppData\Roaming\Typora\typora-user-images\image-20230415155614643.png)]

参考

黑马程序员Java数据结构与java算法全套教程

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

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

相关文章

浅谈java网络编程及RPC框架

目录 1.计算机网络 2.TCP/IP协议 3.UDP协议 4.RPC框架 1.计算机网络 从资源共享的角度上来说&#xff0c;计算机网络就是以能够相互共享资源的方式互连起来的自治计算机系统的集合。网络建立的主要目的是实现计算机资源的共享。 目前来说&#xff0c;计算机网络分为两大模…

JRE和JDK 及 常用DOS命令

JRE和JDK Java程序开发的三个步骤 ●编写代码 ●编译代码 ●运行代码 1.编写代码 A.txt JRE : JRE是Java Runtime Environment缩写&#xff0c;指Java运行环境&#xff0c;包含JVM虚拟机及Java核心类库。 类&#xff1a;java文件在代码中的集合体现( 类java文件&#xf…

CAD转SHP最好的方法 赶快收藏起来吧

1、利用 ArcToolsbox 工具先将 DWG 文件转为 MDB 通过 CASS 软件生成的 DWG 文件&#xff0c;字段中包含有很多属性内容&#xff0c;所以我们先将 DWG 格式 的文件转换为 MDB 格式&#xff0c;再通过 MDB 转换为 SHP 格式数据进行整理。具体步骤如下&#xff1a; 通过 ArcTool…

jenkins——凭据管理

这里写目录标题 一、Jenkins 凭据管理1、凭据管理入口2、凭据的新增3、用户名和密码方式的凭据配置4、SSH密钥方式的凭据配置5、凭据的更新和删除6、凭据的使用 一、Jenkins 凭据管理 凭据管理的作用&#xff1a;管理ssh、邮箱、git等认证信息 1、凭据管理入口 Dashboard —…

深度学习实战24-人工智能(Pytorch)搭建transformer模型,真正跑通transformer模型,深刻了解transformer的架构

大家好&#xff0c;我是微学AI&#xff0c;今天给大家讲述一下人工智能(Pytorch)搭建transformer模型&#xff0c;手动搭建transformer模型&#xff0c;我们知道transformer模型是相对复杂的模型&#xff0c;它是一种利用自注意力机制进行序列建模的深度学习模型。相较于 RNN 和…

【微信小程序】数据监听器,纯数据字段

一、数据监听器 1.1 什么是数据监听器 数据监听器用于 监听和响应任何属性和数据字段的变化&#xff0c;从而执行特定的操作 。它的作用类似于 vue 中 的 watch 侦听器。在小程序组件中&#xff0c; 在componets中新建一个test2文件夹在文件夹里新建component 在app.json …

C学习笔记3

1、将一个整数转换成二进制形式&#xff0c;就是其原码。&#xff08;通俗的理解&#xff0c;原码就是一个整数本来的二进制形式。&#xff09; 例如short a 6;a 的原码就是0000 0000 0000 0110 2、反码就区分正负数&#xff0c;因为正负数的反码是不一样的&#xff0c;正数…

2023年MathorCup数模D题赛题解题思路

MathorCup俗称妈杯&#xff0c;是除了美赛国赛外参赛人数首屈一指的比赛&#xff0c;而我们的妈杯今天也如期开赛。今年的妈杯难度&#xff0c;至少在我看来应该是2023年截至目前来讲最难的一场比赛。问题的设置、背景的选取等各个方面都吐露着我要难死你们的想法。难度是恒定的…

Euro-NCAP 2030愿景

每隔五年,Euro NCAP都会将利益相关者聚集在一起,研究当前的汽车技术现状,预测未来几年可能的挑战,并确定未来的机会所在。讨论的结果是该组织的未来发展方向和明确的未来愿景:Euro NCAP 2030年愿景。 2020年初,Euro NCAP开始制定一套新的战略目标,打算在下一年公布其《2…

STM-32:SPI通信协议/W25Q64简介—软件SPI读写W25Q64

目录 一、SPI简介1.1电路模式1.2通信原理1.3SPI时序基本单元1.3.1起始和终止1.3.2交换字节 二、W25Q642.1W25Q64简介2.2W25Q64硬件电路2.3W25Q64框图2.4Flash操作注意事项 三、软件SPI读写W25Q643.1接线图3.2程序代码 一、SPI简介 SPI是串行外设接口&#xff08;Serial Periph…

Spring Boot异步任务、异步消息

目录 1.异步任务 1.1.概述 1.2.使用 2.异步消息 2.1.概述 2.2.使用 1.异步任务 1.1.概述 举一个例子&#xff0c;我现在有一个网上商城&#xff0c;客户在界面点击下单后&#xff0c;后台需要完成两步&#xff1a; 1.创建客户订单 2.发短信通知客户订单号 这里面第2…

selenium 连接已经打开的chrome浏览器 MAC

selenium 连接已经打开的chrome浏览器 MAC 一&#xff0c;前言 今天在爬取chatGPT的谷歌插件的prompts的时候&#xff0c;发现绕不过他的反爬机制&#xff0c;失败111&#xff0c;所以想用连接已打开的chatGPT页面进行控制 二&#xff0c;具体步骤 1&#xff0c;添加环境变…

Android入门

一、Android系统架构 Android大致可以分为4层架构&#xff1a;Linux内核层、系统运行库层、应用框架层和应用层 1.1Linux内核层 Android系统是基于Linux内核的&#xff0c;这一层为Android设备的各种硬件提供了如显示、音频、照相机、蓝牙、Wi-Fi等底层的驱动。 1.2系统运行层…

2023MathorCup 高校数学建模挑战赛D题思路解析

如下为MathorCup 高校数学建模挑战赛D题思路解析&#xff1a; D 题 航空安全风险分析和飞行技术评估问题 飞行安全是民航运输业赖以生存和发展的基础。随着我国民航业的快速发展&#xff0c;针对飞行安全问题的研究显得越来越重要。2022 年 3 月 21 日&#xff0c;“3.21”空难…

如何使用vim的插件Ctags查看Linux源码

一.ubuntu下安装Linux内核源码 (1).查看自己的内核版本 (2).查看源内的内核源码类表 (3).下载源码 (4).进入/usr/src (5).解压下载的文件到用户主 二.安装vim插件Ctags和使用 插件的介绍 Ctags工具是用来遍历源代码文件生成tags文件&#xff0c;这些tags文件能被编辑器或其它工…

2023年的深度学习入门指南(4) - 在你的电脑上运行大模型

2023年的深度学习入门指南(4) - 在你的电脑上运行大模型 上一篇我们介绍了大模型的基础&#xff0c;自注意力机制以及其实现Transformer模块。因为Transformer被PyTorch和TensorFlow等框架所支持&#xff0c;所以我们只要能够配置好框架的GPU或者其他加速硬件的支持&#xff0…

同为科技(TOWE)防雷科普篇1—雷电灾害认识与雷电预警信号解读

前 言 雷电是自然界最为壮观的大气现象之一。其强大的电流、炙热的高温、猛烈的冲击波以及强烈的电磁辐射等物理效应能够在瞬间产生巨大的破坏作用&#xff0c;常常导致人员伤亡&#xff0c;击毁建筑物、供配电系统、通信设备&#xff0c;造成计算机信息系统中断&#xff0c;引…

电风扇出口欧美CE/UL507认证办理

电风扇简称电扇&#xff0c;是一种利用电动机驱动扇叶旋转&#xff0c;来达到使空气加速流通的家用电器&#xff0c;主要用于清凉解暑和流通空气。风扇主要由扇头、叶片、网罩和控制装置等部件组成。电风扇的主要部件是:交流电动机。其工作原理是:通电线圈在磁场中受力而转动。…

Streamlit 中函数多次进入的问题

Streamlit 函数多次进入的问题 Streamlit 学习的背景重要案例心得踩坑或注意点 Streamlit 学习的背景 最近在学习ai相关的知识&#xff0c;同时需要做一些方便使用的web网页。 例如&#xff1a;调用chatGPT的api&#xff0c;做对话窗。 之前用过gradio, 但是发现在手机端上&am…

NestJS:理解ORM(Object Relational Mapping)

一、理解ORM(Object Relation Mapping) ORM是对象—关系映射&#xff08;Object/Relation Mapping&#xff0c;ORM&#xff09;是为了解决面向对象与关系数据库存在的互不匹配现象而产生的技术。业务实体在内存中表现为对象&#xff0c;在数据库中表现为关系数据。ORM通过使用描…