【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro

简介: 【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro

看到这句话的时候证明:此刻你我都在努力

加油陌生人 2.png

前言

今天给大家带来一篇有关Java顺序表和链表的文章,顺序表和链表我之前的专栏也是写过的,是用C语言实现的,也是模仿实现了顺序表和链表里的方法了。

认识List

Java中的Listjava.util包下的一个接口,它是Collection接口的一个子接口,表示一个有序的集合,可以包含重复的元素。List接口提供了一些独特的方法来插入、访问、删除元素以及搜索列表中的元素。以下是List接口的一些关键特性和常用实现:

特性

  1. 有序性List中的元素按照添加的顺序进行排序。
  2. 允许重复:可以包含重复的元素。
  3. 动态数组:大多数List实现(如ArrayList)使用动态数组来存储元素,这使得随机访问非常高效。

常用方法add(E e):向列表末尾添加一个元素。

add(int index, E element):在指定位置插入一个元素。

remove(int index):移除指定位置的元素并返回被移除的元素。

remove(Object o):移除列表中第一次出现的指定元素。

get(int index):返回指定位置的元素。

set(int index, E element):用指定元素替换列表中指定位置的元素。

size():返回列表中的元素数量。

indexOf(Object o):返回第一次出现的指定元素的索引。

lastIndexOf(Object o):返回最后一次出现的指定元素的索引。

clear():移除列表中的所有元素。

常用实现

ArrayList:基于动态数组实现,支持快速随机访问。但插入和删除操作可能需要数组复制,效率较低。

LinkedList:基于双向链表实现,适合频繁的插入和删除操作。但随机访问效率较低。

Vector:和ArrayList类似,但它是同步的。

Stack:继承自Vector,实现栈的功能,后进先出(LIFO)。

CopyOnWriteArrayList:线程安全的变体,在读多写少的场景下性能较好。

泛型

从Java 5开始,List接口支持泛型,允许开发者指定列表中元素的类型,提高类型安全。



使用场景
  • 当你需要有序集合并且频繁进行随机访问时,选择ArrayList
  • 当你需要频繁在列表中插入或删除元素,并且对随机访问的需求不高时,选择LinkedList


注意事项
  • List的实现不是线程安全的。如果需要线程安全,可以使用Collections.synchronizedList()方法或CopyOnWriteArrayList
  • 选择合适的List实现对于性能至关重要,因为不同的实现在不同的操作下表现不同。


List接口是Java集合框架中非常重要的一部分,合理选择和使用List可以提高程序的性能和可读性。


认识ArrayList(顺序表)

顺序表一个用数组实现的一个结构,他和数组不同的就是它是一个类,数组是一个引用类型,顺序表还扩展了一些方法

像增,删,查,改等是最基本的,还有一些其它方法,像List里的接口方法也实现了。

ArrayList是Java集合框架中的一种实现,属于List接口的实现类之一,同时也实现了RandomAccess接口,表明它支持快速的随机访问。

基本特性

  1. 基于数组ArrayList内部使用一个动态数组(Object数组)来存储元素。
  2. 动态扩容:当添加元素导致数组容量不足时,ArrayList会自动扩容,通常是将现有容量增加到原来的1.5倍(或根据需要调整)。
  3. 允许空元素:可以包含null值。
  4. 非同步ArrayList不是线程安全的。


性能特点


随机访问:由于基于数组实现,ArrayList提供了快速的随机访问能力,即get(int index)操作的时间复杂度为O(1)。

添加和删除:在列表末尾添加元素(add(E e))是高效的,时间复杂度为O(1)。但是,如果需要在列表中间或开始位置添加或删除元素,可能需要移动其他元素以维持数组的连续性,这会导致时间复杂度为O(n)。

常用方法

代码演示:

import java.util.ArrayList;

public class T {

    public static void main(String[] args) {
        ArrayList<Integer> arrayList1=new ArrayList<>();  //创建一个存储整数的顺序表
        ArrayList<Character> arrayList2=new ArrayList<>();  //创建一个存储字符的顺序表
        ArrayList<String> arrayList3=new ArrayList<>();   //创建一个存储字符串的顺序表

        //给整形顺序表添加数据
        arrayList1.add(1);
        arrayList1.add(2);
        arrayList1.add(3);

        //给字符顺序表添加数据
        arrayList2.add('a');
        arrayList2.add('b');
        arrayList2.add('c');

        //给字符串顺序表添加数据
        arrayList3.add("abc");
        arrayList3.add("abcd");
        arrayList3.add("abcde");

        //分别打印三个顺序表
        System.out.println(arrayList1.toString());
        System.out.println(arrayList2.toString());
        System.out.println(arrayList3.toString());

        //也可以单独取其中的元素进行打印
        //如下取每个顺序表第一个元素进行打印
        System.out.println(arrayList1.get(0));
        System.out.println(arrayList2.get(0));
        System.out.println(arrayList3.get(0));


    }
}


注:使用ArrayList前需先导入对应的包 :import java.util.ArrayList;

上面代码中我只是简单使用了其中的几个方法,但是顺序表即:ArrayList类还自带许多方法呢。


这张图的方法仅仅只是ArrayList中2/3的方法呢,所以ArrayList功能还是很强大的。

顺序表之类的集合方法都有个非常厉害的功能,就是可以将另一个顺序表,一次性直接加入另一个顺序表。

代码演示:

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

        ArrayList<Integer> arrayList1=new ArrayList<>();
        ArrayList<Integer> arrayList2=new ArrayList<>();

        //给arrayList1添加数据
        arrayList1.add(1);
        arrayList1.add(2);
        arrayList1.add(3);

        //给arrayList2添加数据
        arrayList2.add(4);
        arrayList2.add(5);
        arrayList2.add(6);


        arrayList1.addAll(arrayList2);
        System.out.println(arrayList1.toString());
        

    }
    }


这样是否见识到ArrayList的强大呢。

除了继承自CollectionList接口的方法外,ArrayList还提供了一些特定方法:


ensureCapacity(int minCapacity):增加内部数组的容量至少为指定的最小容量,有助于减少扩容操作。

trimToSize():将内部数组的大小调整为当前元素的数量,释放多余的内存。

扩容机制

当ArrayList中的元素数量达到当前数组容量时,会进行扩容操作:


创建一个新的数组,容量为原数组的1.5倍(加上原数组容量)。

将原数组中的所有元素复制到新数组中。

用新数组替换原数组。

使用场景

当你需要一个可以快速随机访问元素的集合时,ArrayList是一个好选择。

当元素的添加主要集中在列表末尾时,ArrayList的性能较好。

认识LinkedList(链表)

链表也是一个实现了List的一个类。它的功能和ArrayList比较相似,不同的是LinkedList是没有扩容机制的。


且他们在某些地方插入数据的时间复杂度是不一样的。

LinkedList 在 Java 中是一种实现了 ListDeque 接口的双向链表。

基本特性

双向链表LinkedList 中的每个元素都是一个节点,包含数据和两个指针,分别指向前一个和后一个节点。


动态数组:链表的长度可以根据需要动态增长或缩小。

非线程安全LinkedList 默认不是线程安全的。如果需要线程安全,可以使用 Collections.synchronizedList() 方法或 CopyOnWriteArrayList


内部结构

LinkedList 的内部结构由 Node 内部类实现,每个 Node 对象包含:

item:存储数据。

next:指向下一个节点的引用。

prev:指向前一个节点的引用。

性能特点

插入和删除:在列表的头部或尾部进行插入和删除操作非常高效(O(1)),因为不需要移动其他元素。在列表中间进行这些操作需要 O(n) 时间复杂度,因为需要遍历到特定位置。

随机访问:由于链表的非连续性,随机访问(通过索引获取元素)的时间复杂度为 O(n),因为需要从头开始遍历链表。

常用方法

代码演示:

import java.util.LinkedList;

public class T {

    public static void main(String[] args) {
        LinkedList<Integer> linkedList1=new LinkedList<>();//创建一个存储整数的链表
        LinkedList<Character> linkedList2=new LinkedList<>();//创建一个存储字符的链表
        LinkedList<String> linkedList3=new LinkedList<>();//创建一个存储字符串的链表


        //给整形顺序表添加数据
        linkedList1.add(1);
        linkedList1.add(2);
        linkedList1.add(3);


        //给字符顺序表添加数据
        linkedList2.add('a');
        linkedList2.add('b');
        linkedList2.add('c');


        //给字符串顺序表添加数据
        linkedList3.add("abc");
        linkedList3.add("abcd");
        linkedList3.add("abcde");

        //分别打印三个顺序表
        System.out.println(linkedList1.toString());
        System.out.println(linkedList2.toString());
        System.out.println(linkedList3.toString());

        //也可以单独取其中的元素进行打印
        //如下取每个顺序表第一个元素进行打印
        System.out.println(linkedList1.get(0));
        System.out.println(linkedList2.get(0));
        System.out.println(linkedList3.get(0));

    }

}

链表也是支持addall()方法的,也是可以将一个链表添加到另一个链表。

import java.util.LinkedList;

public class T {

    public static void main(String[] args) {
        LinkedList<Integer> linkedList1=new LinkedList<>();
        LinkedList<Integer> linkedList2=new LinkedList<>();


        linkedList1.add(1);
        linkedList1.add(2);
        linkedList1.add(3);

        linkedList2.add(4);
        linkedList2.add(5);
        linkedList2.add(6);

        linkedList1.addAll(linkedList2);

        System.out.println(linkedList1.toString());
    }
    }

除了继承自 List 的方法外,LinkedList 还提供了以下特有方法:

addFirst(E e) 和 addLast(E e):在链表头部和尾部添加元素。

getFirst() 和 getLast():获取链表头部和尾部的元素。

removeFirst() 和 removeLast():移除链表头部和尾部的元素。

offerFirst(E e)、offerLast(E e)、pollFirst() 和 pollLast():这些方法提供了双端队列的功能。

使用场景

当需要频繁地在列表的头部或尾部添加或删除元素时,LinkedList 是一个很好的选择。

当不需要频繁地进行随机访问时,LinkedList 可以提供比 ArrayList 更好的性能。

总结:

顺序表(如ArrayList)

基于数组:顺序表使用数组来存储元素,元素在内存中连续存放。

随机访问:支持快速的随机访问,即可以直接通过索引访问任意位置的元素(时间复杂度O(1))。

动态扩容:当元素数量超过数组容量时,需要进行扩容操作,这通常涉及到创建更大的数组并复制现有元素。

插入和删除:在数组末尾添加元素非常快(时间复杂度O(1)),但在中间或开始位置插入或删除元素可能较慢,因为需要移动后续元素以维持连续性(时间复杂度O(n))。

内存使用:通常比链表更紧凑,因为不需要额外存储指向其他元素的引用。

适用场景:适合于随机访问频繁的场景,以及在列表末尾添加元素的操作。

链表(如LinkedList)

基于节点:链表由一系列节点组成,每个节点包含数据和指向下一个(及前一个,对于双向链表)节点的指针。

非连续存储:元素在内存中可以是不连续的,通过指针连接。

动态大小:大小可以动态变化,不需要预先分配大量内存。

插入和删除:在已知前一个节点的情况下,可以在O(1)时间内完成,因为只需要改变几个指针。但在中间位置进行操作可能需要O(n)时间来找到前一个节点。

随机访问:不支持高效的随机访问,访问特定位置的元素需要从头开始遍历(时间复杂度O(n))。

内存使用:每个节点需要额外存储至少一个(单向链表)或两个(双向链表)指针,因此内存使用相对较高。

适用场景:适合于插入和删除操作频繁的场景,尤其是在列表中间,以及不需要频繁随机访问元素的应用。

总结

顺序表适合于需要快速随机访问元素的场景,以及主要在列表末尾添加元素的情况。

链表适合于插入和删除操作频繁,尤其是在列表中间,且不经常进行随机访问的场景。

选择使用顺序表还是链表,取决于具体的应用需求和操作模式。理解它们的特点可以帮助开发者选择最合适的数据结构,以优化程序的性能。


. - 力扣(LeetCode):给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

. - 力扣(LeetCode):逆置单链表

. - 力扣(LeetCode):给你单链表的头结点 head ,请你找出并返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

. - 力扣(LeetCode):给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。

在「杨辉三角」中,每个数是它左上方和右上方的数的和。

. - 力扣(LeetCode):合并两个有序链表

. - 力扣(LeetCode):求环的路口

. - 力扣(LeetCode):判断链表是否为环

. - 力扣(LeetCode):给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

需要答案可私聊我,当然官方也有题解唔。

目录
相关文章
|
1月前
|
存储 算法 Perl
数据结构实验之链表
本实验旨在掌握线性表中元素的前驱、后续概念及链表的建立、插入、删除等算法,并分析时间复杂度,理解链表特点。实验内容包括循环链表应用(约瑟夫回环问题)、删除单链表中重复节点及双向循环链表的设计与实现。通过编程实践,加深对链表数据结构的理解和应用能力。
57 4
|
26天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
51 5
|
1月前
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
数据结构与算法系列学习之串的定义和基本操作、串的储存结构、基本操作的实现、朴素模式匹配算法、KMP算法等代码举例及图解说明;【含常见的报错问题及其对应的解决方法】你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
|
1月前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
1月前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
91 4
|
1月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
49 0
|
1月前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
67 0
|
3月前
|
Java
java数据结构,双向链表的实现
文章介绍了双向链表的实现,包括数据结构定义、插入和删除操作的代码实现,以及双向链表的其他操作方法,并提供了完整的Java代码实现。
java数据结构,双向链表的实现