JAVA数据结构学习(1)——链表

本文主要介绍了Java中的链表数据结构,包括链表的概念、分类,以及单链表和双向链表的操作。链表不同于数组,它通过对象引用构成序列,提供了灵活的添加、删除操作。同时,文章探讨了链表在实际操作中的优势以及与数组的性能对比。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、概念

1.1 Java

Java一般用来做服务器开发
拿一个淘宝网站举例子:
本质上我们所看到的网页, 是一些数据的集合
我们在我们的电脑上看到这些数据, 都是从淘宝服务器获得的。

Java代码 .java - .class – 根据。Class文件产生的对象才是实际有意义的
数据存储在数据库MySql 管控数据(外存)

Java本质工作核心: 攒数据
Java要不要临时存储数据

// java为了更好更优秀的处理数据(不在完全通过我们手动操作)java提供了一些class-----》 java的集合类
java 最终怎么临时存储数据 = 数组 + 对象

1.2 数据结构和JAVA有什么关系?

(数据结构在JAVA上的表现) 数据结构本身和JAVA没有任何关系, 只不过JAVA中有多种集合、数组、等集合性质的多对象存储结构, 有时候我们希望更便捷,更具有逻辑性的操作这些集合或者数组数据 所以,我们根据数据结构的组织方式, 构建了一些特殊的JAVA集合类 用于描述了一些JAVA对象的底层数据的组成关系
Java的集合类底层组织数据的方式, 是参照某些数据结构(逻辑、思想)来进行的。
Java需要临时存储数据, --》 提供集合类 —》 数组 + 链表

1.3 数据结构的种类和JAVA中的集合类别有哪些?

数据结构: 抽象概念/逻辑概念
集合: 一堆数据
线性表: 有序的序列(操作受限的线性表: 栈:先进后出 队列: 先进先出)
Y = ax + b
树: 一对多的数据关系, 国家, 族谱
图: 多对多的关系: 理论层级
集合类分类:
1, Collection
2, Map
在这里插入图片描述

  1. 为什么需要集合类?
    很多情况下,我们需要对一组对象进行操作。而且很可能事先并不知道到底有多少个对象。为了解决这个问题呢,Java 就提供了集合类供我们使用。(存储更多类型问题, 扩容问题, 内存空间浪费问题, 数据查找问题, 数据删除问题等等)
  2. 集合类的特点: a. 只能存储引用数据类型 b. 可以自动地调整自己的大小
  3. 数组和集合类都是容器,它们有何不同?
    a. 数组可以存储基本数据类型的数据,集合不可以。
    b. 数组的长度是固定的,集合可以自动调整自己的大小。
    c. 数组的效率高,相对来说集合效率比较低。
    d. 数组没有API,集合有丰富的API。

数据结构是相互之间存在一种或多种特定关系的数据元素的集合。 公式:数据结构=数据+结构
数据结构用来抽象的描述数据的组织关系 数据:用类型表示 结构:在任何问题中,数据元素都不是孤立存在的,它们之间都存在着某种关系,这种数据元素相互之间的关系称为结构。 元素之间,通常有以下四种基本结构: 集合 线性结构 树形结构 图结构
前面分类中定义的关系,描述的是数据元素间的逻辑关系,因此又称为逻辑结构。 但是仅仅知道数据元素间的逻辑关系是不够的,因为我们得实现自己的数据结构。

我们需要用具体的java代码, 来模拟或者来表示数据结构
因此,我们得关注数据结构在计算机底层是如何表示的? 数据结构在计算机中的表示,称为数据的物理结构,又称为存储结构或者映像。 结构的表示可以分为两种:顺序存储结构 (顺序映像) 和 链式存储结构 (非顺序映像)。 顺序映像:借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系。(数组) 非顺序映像:借助指示元素存储地址的”指针”,来表示数据元素的逻辑关系。(链表)

2 数组

2.1 数组

Q1: 数组我们都很熟悉,那你理解的数组是什么样的呢?它的最主要特点是什么呢?
数组是连续存储 -->随机访问
Q2: 为什么数组的索引是一般都是从0开始的呢?
第一个层面: 历史遗留问题. 硬件资源, 上世纪40-50年代 微型机, 存储数据
第二个层面: 方便计算.
(下标-1) * 单个元素空间 + 基础位置
Q3: 为什么数组的效率比链表高?
链表: 链表是非连续存储, 如果查找一个元素需要一步一步遍历
数组: 是连续存储, 可以根据下标一步计算出存储位置

2.2 数组的操作

2.2.1 数组的基本操作

(1)添加 (保证元素的顺序)
最好情况:O(1)
最坏情况:移动n个元素,O(n)
平均情况:移动 n/2 个元素,O(n)
(2) 删除 (保证元素的顺序)
最好情况:O(1)
最坏情况:移动n-1个元素,O(n)
平均情况:移动(n-1)/2个元素,O(n)
(3)查找
a. 根据索引查找元素:O(1)
b. 查找数组中与特定值相等的元素 ①大小无序:O(n) ②大小有序:O(log2n)
(4)总结
数组: 添加和删除慢 o(n)
查找快: 尤其根据下标查找 -->大小有序的折半查找

3 链表

3.1 什么是链表

Java中什么是链表, 一个对象持有另一个对象的引用, 另一个对象持有之后的一个对象的引用,…… 构成一个链表

Java的角度,链表的本质: 是java对象的相互持有和引用

Node zs = new Node("zs", null);
Node ls = new Node("ls", null);
Node wu = new Node("wu", null);
Node zl = new Node("zl", null);
zs.next = ls;
ls.next = wu;
wu.next = zl;

得到一个链表如下:zs -> ls ->wu ->zl
形象地说,链表就是用一串链子将结点串联起来。
结点:包含数据域和指针域。
数据域:数据
指针域:下一个结点的地址

3.2 链表的分类

(1)单链表: 在链表中的每一个节点, 都有一个子结点, (尾结点没有子结点)

(2)循环单链表: 单链表的改进, 让单链表的尾结点的下一个结点, 指向头结点

(3)双向链表: 双向链表也是单链表的改进, 让结点不仅可以指向之后的元素, 也可以指向之前的元素

(4)双向循环链表: 双向链表的一个改进, 头元素的之前指向尾元素, 尾元素的之后指向头元素

3.3 单链表操作

单链表:
(1)单链表操作: 查找o(n), 添加和删除o(1)

3.4 双向链表的操作

双向链表操作:查找o(n), 添加和删除o(1)

(1)很容易验证,前面那些操作,双向链表和单链表的时间复杂度是一样的。那为什么在工程上,我们用的一般是双向链表而不是单链表呢?
因为在实际操作过程中, 双向链表根据下标查找的效率, 是高于单链表.

--> 隐性的结论, 在实际工作中, 根据下标查找这个操作相对添加和删除比较频繁

思想: 用空间换时间

(2)求链表的中间元素 判断链表中是否有环(circle)

public class Ex1 {
    public static void main(String[] args) {

        Node1 node1 = new Node1("1", null);
        Node1 node2 = new Node1("2", node1);
        Node1 node3 = new Node1("3", node2);
        Node1 node4 = new Node1("4", node3);
        Node1 node5 = new Node1("5", node4);
        Node1 node6 = new Node1("6", node5);
        Node1 node7 = new Node1("7", node6);

        // 7 -- 6 --5  --4  --3  --2  --1
        // 快慢指针:
        Node1 f = node7;// 快指针
        Node1 l = node7;// 慢指针
        // 判断快指针的下一个元素, 以及下下个元素, 是不是null, 不是null向后继续移动
        while (f.next != null && f.next.next != null){
            f = f.next.next;// 移动两步
            l = l.next; // 移动一步
        }

        // f接近于于尾部的时候, 跳出上述循环
        System.out.println(l.value);

    }
}
class Node1{
    String value;
    Node1 next;
    public Node1(String value, Node1 next) {
        this.value = value;
        this.next = next;
    }
}

(3)反转单链表

public class Ex2 {
    public static void main(String[] args) {

        Node2 node1 = new Node2("1", null);
        Node2 node2 = new Node2("2", node1);
        Node2 node3 = new Node2("3", node2);
        Node2 node4 = new Node2("4", node3);
        Node2 node5 = new Node2("5", node4);
        Node2 node6 = new Node2("6", node5);
        Node2 node7 = new Node2("7", node6);

        // 7 -- 6 --5  --4  --3  --2  --1
        node1.next = node5;

        Node2 f = node7;
        Node2 l = node7;

        while (f.next != null && f.next.next != null){
            f = f.next.next;
            l = l.next;
            if (f == l){// 快指针和慢指针相遇, 必定意味着有环
                break;
            }
        }

        if (f.next != null && f.next.next != null){
            System.out.println("有环");
        } else {
            System.out.println("没有环");
        }
    }
}

class Node2{
    String value;
    Node2 next;
    public Node2(String value, Node2 next) {
        this.value = value;
        this.next = next;
    }
}

3.5 使用链表/数组实现一个线性表

自己手动实现一个集合类
这个集合类: 从集合类使用的角度: 就是一个数据容器(添加, 删除, 查找)
底层结构: 链表
数据结构角度: 模拟一个线性表

线性表: 有序序列: 一个序列除了头尾元素以外, 每一个元素都有唯一的前驱和后继
---->线性表一般伴随着有序, 伴随着可以通过下标操作

public class MyLinked {

    private Node head; // 这个集合类底层所维护链表的头结点
    private  int size; // 用来标记这个链表/集合类存储了多少数据

    public boolean add(String str){
//        if (str == null) throw new IllegalArgumentException("parame is null");

        // 原本的链表是否为空
//        if (head == null || size == 0){}
        if (size == 0){
            // 意味着 链表为空, 新添加的元素, 作为头结点
            head = new Node(str, null);
            size++;
            return true;
        }
        // 链表原本不空
        // 添加到尾部: 先找到尾部
        Node mid = head; // 用mid标记遍历结点, 最终找到尾结点
        while (mid.next != null){
            mid = mid.next;
        }

        // mid.next == null : mid是原链表的尾结点
        mid.next = new Node(str, null);
        size++;
        return true;
    }

    public boolean remove(String str){
        if (size == 0) throw new RuntimeException("linked is empty");

        if (str == null){
            // 如皋要删除的元素是null, ==比较
            if (str == head.value){
                // 判断头结点是不是我们要删除的元素
                head = head.next;
                size--;
                return true;
            }
            // 删除不是头结点
            Node mid = head;
            // mid的下一个元素不是null
            // mid的下一个元素 存储的值也不是要找的
            while (mid.next != null && str != mid.next.value ){
                mid = mid.next;// mid向后遍历
            }

            // 两种结果
            // 1, 没找到, mid.next == null
            // 2, 找到了,
            if (mid.next == null){
                // 没找到
                return false;
            }
            // 找到了, 要删除的是mid的next
            mid.next = mid.next.next;// 删除mid的next
            size--;
            return true;

        }else {
            // 如果要删除的元素不是null, equals比较
            if (str.equals(head.value)){
                // 判断头结点是不是我们要删除的元素
                head = head.next;
                size--;
                return true;
            }
            // 删除不是头结点
            Node mid = head;
            // mid的下一个元素不是null
            // mid的下一个元素 存储的值也不是要找的
            while (mid.next != null && !str.equals(mid.next.value) ){
                mid = mid.next;// mid向后遍历
            }

            // 两种结果
            // 1, 没找到, mid.next == null
            // 2, 找到了,
            if (mid.next == null){
                // 没找到
                return false;
            }
            // 找到了, 要删除的是mid的next
            mid.next = mid.next.next;// 删除mid的next
            size--;
            return true;
        }
    }

    public boolean contains(String str){
        if (isEmpty()) throw new RuntimeException("linked is empty");

        if (str == null){
            // 如皋要查找的元素是null, ==比较
            if (str == head.value){
                // 头结点就是要查找的结点
                return true;
            }
            // 删除不是头结点
            Node mid = head;
            // mid的下一个元素不是null
            // mid的下一个元素 存储的值也不是要找的
            while (mid.next != null ){
                mid = mid.next;// mid向后遍历
                if (mid.value == str){
                    // 判断遍历过程中的元素, 是否要查找的元素
                    return true;
                }
            }
        } else {
            // 如果要查找的元素不是null, equals比较
            if (str.equals(head.value)){
                // 头结点就是要查找的结点
                return true;
            }
            // 删除不是头结点
            Node mid = head;
            // mid的下一个元素不是null
            // mid的下一个元素 存储的值也不是要找的
            while (mid.next != null ){
                mid = mid.next;// mid向后遍历
                if (str.equals(mid.value)){
                    // 判断遍历过程中的元素, 是否要查找的元素
                    return true;
                }
            }
        }
        return false;
    }

    public  boolean set(String oldValue, String newValue){
        if (isEmpty()) throw new RuntimeException("linked is empty");

        //
        if (oldValue == null){
            // 要替换的值是null
            Node mid = head;

            // 如果遍历的结点不是null, 并且这个结点存储的值也不是null
            // 接着向后遍历
            while (mid != null && oldValue != mid.value){
                mid = mid.next;
            }

            // 1, mid == null: 没有
            // 2, oldValue == mid.value: 找到了mid就是要改变的结点
            if (mid == null){// 就没有这个旧值
                return false;
            }
            // 有
            mid.value = newValue;
            return true;

        }else {
            // 要替换的值不是null
            Node mid = head;

            // 如果遍历的结点不是null, 并且这个结点存储的值也不是要替换的结点
            // 接着向后遍历
            while (mid != null && !oldValue.equals(mid.value)){
                mid = mid.next;
            }

            // 1, mid == null: 没有
            // 2, oldValue == mid.value: 找到了mid就是要改变的结点
            if (mid == null){// 就没有这个旧值
                return false;
            }
            // 有
            mid.value = newValue;
            return true;
        }
    }

    // 增删改查
    // 增删查
    // 改:

    /**
     * 实现一个添加方法
     * @param index : 要添加的位置
     * @param str : 要添加的内容
     * @return : 是否添加成功
     */
    public boolean add(int index, String str){
        // 判断给的下标是否合法
        if (index < 0 || index > size) throw new IllegalArgumentException("size = " + size );
        // zs -  ls - wu - zl

        // 头结点: index = 0
        if (index == 0){// 添加的位置就是头结点
            head = new Node(str, head);
            size++;
            return true;
        }

        // 添加的不是头位置
        int tag = 1; // 位置标记
        Node mid = head;
        while (tag != index){
            mid = mid.next;
            tag++;
        }
        // zs  ls  wu  zl

        // mid代表要添加位置的之前
        mid.next = new Node(str, mid.next);
        size++;

        return true;
    }

    public String remove(int index){
        // 判断给的下标是否合法
        if (index < 0 || index >= size) throw new IllegalArgumentException("size = " + size );

        // 删除是头元素
        if (index == 0){
            String oldValue = head.value;
            head = head.next;
            size--;
            return oldValue;
        }

        // 删除的不是头元素
        int tag = 1; // 位置标记
        Node mid = head;
        while (tag != index){
            mid = mid.next;
            tag++;
        }

        // mid 删除元素前一个
        String oldValue = mid.next.value;
        // 删除
        mid.next = mid.next.next;
        size--;
        return oldValue;
    }

    /**
     * 查找方法: 根据下标查找这个下标所对应存储的内容
     * @param index : 提供的下标
     * @return :  返回的内容
     */
    public String get(int index){
        // 判断给的下标是否合法
        if (index < 0 || index >= size) throw new IllegalArgumentException("size = " + size );

        int tag = 0;
        Node mid = head;
        while (tag != index){
            mid = mid.next;
            tag++;
        }

        //mid 就是要查找的位置
        return mid.value;
    }

//        myLinked.add(i, str);
//        myLinked.remove(i);
//        myLinked.get(i);// 根据下标获取这个下标的元素内容

    /**
     * 根据下标替换新内容
     * @param index
     * @param newValue
     * @return
     */
    public String set(int index, String newValue){
        // 判断给的下标是否合法
        if (index < 0 || index >= size) throw new IllegalArgumentException("size = " + size );

        int tag = 0;
        Node mid = head;
        while (tag != index){
            mid = mid.next;
            tag++;
        }

        // mid 就是你给定的查找位置
        String value = mid.value;
        mid.value = newValue;
        return value;
    }


    // 判断这个集合类存储的数据是否为空
    public boolean isEmpty(){
        return size == 0;
    }
    public int size(){
        return size;
    }



    class Node{
        String value;
        Node next;

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值