【数据结构】顺序表,链表

news2025/2/28 21:38:00

前言

小亭子正在努力的学习编程,接下来将开启  javaEE  的学习~~

分享的文章都是学习的笔记和感悟,如有不妥之处希望大佬们批评指正~~

同时如果本文对你有帮助的话,烦请点赞关注支持一波, 感激不尽~~

目录

前言

顺序表

ArrayList

ArrayList的构造

 ArrayList的创建方法:

构造方法:

ArrayList常用方法

ArrayList的扩容机制

ArrayList的实现和常用方法的代码演示

                ArrayList小结

链表

链表的分类

LinkedList

 LinkedList的构造方法

LinkedList的其他常用方法介绍

LinkedList的实现和常用方法的演示

ArrayList和LinkedList的区别


线性表:

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储
 

顺序表

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。

ArrayList

在集合框架中,ArrayList是一个普通的类,实现了List接口。

 

【说明】
1. ArrayList是以泛型方式
2. ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问
3. ArrayList实现了Cloneable接口,表明ArrayList是可以clone的
4. ArrayList实现了Serializable接口,表明ArrayList是支持序列化的
5. 和Vector不同,ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector或者CopyOnWriteArrayList
6. ArrayList底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表

ArrayList的构造

 ArrayList的创建方法:

public static void main(String[] args) {
// ArrayList创建,推荐写法
// 构造一个空的列表
List<Integer> list1 = new ArrayList<>();
// 构造一个具有10个容量的列表
List<Integer> list2 = new ArrayList<>(10);
list2.add(1);
list2.add(2);
list2.add(3);
// list2.add("hello"); // 编译失败,List<Integer>已经限定了,list2中只能存储整形元素
// list3构造好之后,与list中的元素一致
ArrayList<Integer> list3 = new ArrayList<>(list2);

}

构造方法:

方法解释
ArrayList()无参构造
ArrayList(Collection<? extends E> c)利用其他 Collection 构建 ArrayList
ArrayList(int initialCapacity)指定顺序表初始容量

ArrayList常用方法

boolean add(E e)尾插 e
void add(int index, E element)将 e 插入到 index 位置
boolean addAll(Collection<? extends E> c)尾插 c 中的元素
E remove(int index)删除 index 位置元素
boolean remove(Object o)删除遇到的第一个 o
E get(int index)获取下标 index 位置元素
E set(int index, E element)将下标 index 位置元素设置为 element
void clear()清空
boolean contains(Object o)判断 o 是否在线性表中
int indexOf(Object o)返回第一个 o 所在下标
int lastIndexOf(Object o)返回最后一个 o 的下标
List<E> subList(int fromIndex, int toIndex)截取部分 list

ArrayList的扩容机制

ArrayList是一个动态类型的顺序表,即:在插入元素的过程中会自动扩容。

扩容的步骤:

1. 检测是否真正需要扩容,如果是调用grow准备扩容
2. 预估需要库容的大小初步预估按照1.5倍大小扩容,如果用户所需大小超过预估1.5倍大小,则按照用户所需大小扩容,真正扩容之前检测是否能扩容成功,防止太大导致扩容失败
3. 使用copyOf进行扩容

ArrayList的实现和常用方法的代码演示

public class MyArrayList{
    public int [] elem;//顺序表相当于一个数组
    public int usedSize;//有效数据的长度
    public static  final int DEFAULT_SIZE = 5;
    private boolean isFull;

    public MyArrayList(){
        this.elem = new int [DEFAULT_SIZE];//实例化
 }

    /*
    判断顺序表是否已满
     */
    public boolean isFull(){
        //看有效数据长度是否等于顺序表长度,等于返回true
        return this.usedSize == this.elem.length;
    }
     /*
      扩容
      */
    public void resize(){
        //Array.copyOf() 用于复制指定的数组内容以达到扩容的目的
        this.elem = Arrays.copyOf(this.elem, 2*this.elem.length);
    }
    /*
    新增数据
     */
    public void  add(int data){
        if(this.isFull){ //判断顺序表空间是否满了,如果满了,执行扩容
            resize();
        }
        this.elem[this.usedSize] = data ;//插入数据
        this.usedSize++; // 有效数据长度加1
    }
    /*
    打印顺序表
     */
    public void display(){
        for (int i = 0; i < this.usedSize; i++) {   //遍历有效的数据
            System.out.print(this.elem[i] + " ");
        }
        System.out.println();
    }
    /*
    获取顺序表的长度
     */
     public int size (){
        return this.usedSize;
     }
 /*
 判定是否包含某个元素
  */
    public boolean contains (int toFind){
        for (int i = 0; i < this.usedSize; i++) {
            if (this.elem [i] == toFind){
                return true;
            }
        }
        return false;
    }
 /*
 查找某个元素对应的位置
  */
    public int indexof(int toFind){
        for (int i = 0; i < this.usedSize; i++) {
            if (this.elem[i] == toFind){
                return  i;
            }
        }
        return -1;//数组没有负数下标
    }

    /*
    在 pos 位置新增元素 O(N)
     */
    public void add(int pos,int data){
        checkIndex(pos);
        if (isFull()){
            resize();
        }
        for (int i = usedSize-1; i >= pos; i--) {
            elem[i+1] = elem [i];
              }
        elem[pos] = data ;
        usedSize++;
    }
  /*
  检查add数据的时候,pos是否是合法的
   */
    private void checkIndex(int pos) {
        if (pos < 0|| pos > elem.length)
        //抛出异常
        throw new IndexOutOfException
                ("位置不合法,请检查位置的合法性");
    }

 /*
  获取 pos 位置的元素
  */
    public int get(int pos) {
        checkGetIndex(pos);
        return elem[pos];
    }
 /*
 检查取数据时pos的合法性
  */
    private void checkGetIndex(int pos) {
        if(pos < 0 || pos >= usedSize) {  //取数据的时候元素下标可以等于数组有效长度
            throw new IndexOutOfException
                    ("get获取元素的时候,位置不合法,请检查位置的合法性!");
        }
    }
 /*
 给 pos 位置的元素设为 value
  */
    public void set(int pos, int value) {
        checkIndex(pos);
        elem[pos] = value;//顺序表的优点就是可以随机存取
    }

 /*
 删除第一次出现的关键字key O(n)
  */
    public boolean remove(int toRemove) {
        int index = indexof(toRemove);// 找到那个元素对应的位置
        if(index == -1){  // 判断元素位置的合法性,-1不合法
            System.out.println("没有这个数据");
            return false;
        }
        //删除这个元素后,后面的元素要向前移动
        for (int i = index; i < usedSize-1 ; i++){
            elem[i] = elem [i+1];
        }
        usedSize--;
        elem[usedSize] = 0;
        //最后一个元素的位置要置为空,内置数据类型置为0就行
        //elem[usedSize] = null;
        // 如果里面是引用类型 那么此时就需要手动置空
        return true;
    }
     /*
     清空数据类型
      */
    public void  clear(){
        //直接把有效长度置为0
        usedSize = 0;
         /*遍历,把每一个元素都置为空
       for (int i = 0; i < usedSize; i++) {
            elem[i] = null;
        }
        usedSize = 0;
        */
    }
    public static void main(String[] args) {
        MyArrayList myArrayList = new MyArrayList();
        myArrayList.add(1);
        myArrayList.add(3);
        myArrayList.add(5);
        myArrayList.add(7);
        myArrayList.display();
        myArrayList.size();
        myArrayList.add(3,111);
        myArrayList.indexof(3);
        myArrayList.get(1);
        myArrayList.set(1,99);
        myArrayList.remove(3);
        myArrayList.clear();
        System.out.println("sssssss");
    }
}

ArrayList小结

1. ArrayList底层使用连续的空间,任意位置插入或删除元素时,需要将该位置后序元素整体往前或者往后搬移,故时间复杂度为O(N),所以不适合用在需要频繁删除插入的场景。相反,由于使用的是连续的空间,在知道下标位置的情况下很容易找到目标元素,时间复杂度为O(1),所以更适合用在查找检索的场景。
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

链表

链表是一种由一系列节点组成,每个节点包含数据和一个指向下一个节点的指针。

链表在逻辑上是连续的,在物理空间上可能连续也可能不连续。

链表的分类

1. 单向或者双向

单向链表中,每个节点只包含一个指向下一个节点的指针,最后一个节点的指针指向空。

双向链表中,每个节点除了一个指向下一个节点的指针,还包含一个指向前一个节点的指针,提供前后两个方向的遍历。

 2. 带头或者不带头

 3. 循环或者非循环

 

小结:

与数组相比,链表具有一些优点和缺点。链表插入和删除节点的时间复杂度为O(1),而在数组中插入和删除节点需要移动其他节点,时间复杂度为O(n)。但在链表中访问某个节点的时间复杂度为O(n),而在数组中的访问时间复杂度为O(1)。

因此,链表适合频繁进行插入和删除操作,而数组适合频繁进行访问操作。 链表常用于实现栈、队列和图等数据结构,也用于解决一些特定的问题,如求解约瑟夫问题等。


LinkedList

LinkedList的底层是双向链表结构(链表后面介绍),由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。

在集合框架中,LinkedList也实现了List接口,具体如下

【说明】
1. LinkedList实现了List接口
2. LinkedList的底层使用了双向链表
3. LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
4. LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)
5. LinkedList比较适合任意位置插入的场景

 LinkedList的构造方法

方法解释
LinkedList()无参构造
public LinkedList(Collection<? extends E> c)使用其他集合容器中元素构造List

LinkedList的其他常用方法介绍

方法解释
boolean add(E e)尾插 e
void add(int index, E element)将 e 插入到 index 位置
boolean addAll(Collection<? extends E> c)尾插 c 中的元素
E remove(int index)删除 index 位置元素
boolean remove(Object o)删除遇到的第一个 o
E get(int index)获取下标 index 位置元素
E set(int index, E element)将下标 index 位置元素设置为 element
void clear()清空
boolean contains(Object o)判断 o 是否在线性表中
int indexOf(Object o)返回第一个 o 所在下标
int lastIndexOf(Object o)返回最后一个 o 的下标
List<E> subList(int fromIndex, int toIndex)截取部分 list

LinkedList的实现和常用方法的演示

public class MyLinkedList {
    public static class ListNode {
        public int val;
        public ListNode prev;//前驱
        public ListNode next;//后继
        public ListNode(int val) {
            this.val = val;
} }

    public ListNode head;
    public ListNode last;
 //打印
    public void display() {
        ListNode cur = head;
        while (cur != null) {
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
    }
 //求链表长度
    public int size() {
        ListNode cur = head;
        int len = 0;
        while (cur != null) {
            len++;
        }
        return len;
    }

//查找是否包含关键字key
    public boolean contains(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }
//头插法
    public void addFirst(int data) {
        ListNode node = new ListNode(data);
        if (head == null) {
            head = node;
            last = node;
        } else {
            head.prev = node;
            node.next = head;
            head = node;
        }
    }

//尾插法
    public void addList(int data) {
        ListNode node = new ListNode(data);
        if (head == null) {
            head = node;
            last = node;
        } else {
            node.prev = last;
            last.next = node;
            last = node;
        }
    }
 //任意位置插入
    public void addIndex (int index ,int data){
        if(index < 0 || index > size()){
            System.out.println("地址不合法");//此处可以抛出异常
        }
        if (index == size()){
            addList(data);
            return;
        }
        ListNode cur = fidIndexSubOne(index);
        ListNode node = new ListNode(data);
        node.next = cur ;
        cur.prev.next = node;
        node.prev = cur.prev;
        cur.prev = node;
    }

    //查找下标
    private ListNode fidIndexSubOne(int index) {
        ListNode cur = head;
        int count = 0;
        while (count != index - 1) {
            cur = cur.next;
            count++;
        }
        return cur;
    }

    /*
    删除第一次出现关键字为key的结点
    特殊情况是删头和删尾
     */
    public void remove(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                //删除的是头结点
                if (cur == head){
                    head = head.next;
                    //如果只有一个节点
                    if (head != null) {
                        head.prev = null;
                    }
                }else {
                    //中间部分的结点
                    cur.prev.next = cur.next;
                    if (cur.next != null){   //不是尾结点
                        cur.next.prev = null;
                    }else { //是尾结点
                        last = last.prev;
                    }
                }
                    return;
            }
            cur = cur.next;
        }

    }
    /*
    删除所有的关键字为key的结点
    删除第一个的那个最后不return就行
     */
    public void removeAllKey(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                //删除的是头结点
                if (cur == head){
                    head = head.next;
                    //如果只有一个节点
                    if (head != null) {
                        head.prev = null;
                    }
                }else {
                    //中间部分的结点
                    cur.prev.next = cur.next;
                    if (cur.next != null){   //不是尾结点
                        cur.next.prev = null;
                    }else { //是尾结点
                        last = last.prev;
                    }
                }

            }
            cur = cur.next;
        }

    }

    /*
    清除
    解法:遍历链表,把每一个结点的前驱和后继都置为空
     */
    public void  clear (){
        ListNode cur = head ;
        while (cur != null){
            ListNode curNext = cur.next;
            cur.prev = null;
            cur.next = null;
            cur = curNext;
        }
        head = null;
        last = null;
    }

}

ArrayList和LinkedList的区别

不同点ArrayListLinkedList
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持O(1)不支持:O(N)
头插需要搬移元素,效率低O(N)只需修改引用的指向,时间复杂度为O(1)
插入空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁

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

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

相关文章

K8S存储值之PV和PVC

1. 概念&#xff1a; 1.1. PersistentVolume (PV)&#xff1a; 是由管理员设置的存储&#xff0c;它是群集的一部分。就像节点是集群中的资源一样&#xff0c;PV也是集群中的资源。PV是Volume之类的卷插件&#xff0c;但具有独立于使用PV的Pod的生命周期。此API对象包含存储实…

数字图像处理-图像复原与重建

文章目录 一、图像退化/复原过程的模型二、噪声模型2.1噪声的空间和频率特性2.2一些重要的噪声概率密度函数2.2.1高斯噪声2.2.2瑞利噪声2.2.3爱尔兰&#xff08;伽马&#xff09;噪声2.2.4指数噪声2.2.5均匀噪声2.2.6脉冲&#xff08;椒盐&#xff09;噪声 2.3周期噪声 三、只存…

【ABAP】数据类型(四)「类型组TYPE-POOL」

💂作者简介: THUNDER王,一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学本科在读,同时任汉硕云(广东)科技有限公司ABAP开发顾问。在学习工作中,我通常使用偏后端的开发语言ABAP,SQL进行任务的完成,对SAP企业管理系统,SAP ABAP开发和数据库具有较…

基于电容电流前馈与电网电压全前馈的三相LCL并网逆变器谐波抑制MATLAB仿真

基于电容电流前馈&#xff0b;电网电压全前馈的三相并网逆变器谐波抑制MATLAB仿真资源-CSDN文库https://download.csdn.net/download/weixin_56691527/87940934模型简介&#xff1a; 测试环境为MATLAB2021b 一共包含两个模型&#xff1a;一个是传统无改进模型&#xff0c;一个…

chatgpt赋能python:Python画运动轨迹:探索世界的足迹

Python画运动轨迹&#xff1a;探索世界的足迹 作为一门快速发展的编程语言&#xff0c;Python在数据分析、机器学习、Web开发等多个领域中发挥着举足轻重的作用。其中&#xff0c;画运动轨迹是Python可视化库中较为常见的应用之一。本文将介绍使用Python画运动轨迹的方法&…

chatgpt赋能python:Python画点函数:如何绘制并掌握基础图形?

Python画点函数&#xff1a;如何绘制并掌握基础图形&#xff1f; Python是一个极为强大的编程语言&#xff0c;它可以用来编写各种应用程序&#xff0c;包括绘图。绘图是Python的重要特性之一&#xff0c;它提供了各种绘图函数&#xff0c;包括画点函数。Python的画点函数是一…

chatgpt赋能python:Python的frozenset:一种不可变的集合类型

Python的frozenset&#xff1a;一种不可变的集合类型 在Python中&#xff0c;集合是一种基本的数据类型。它们是无序的、可变的、且不允许重复的元素。但是&#xff0c;有时候我们需要一个不可变的集合&#xff0c;即一旦创建就不允许进行修改操作的集合。这就是frozenset的作…

【STM32 】芯片命名、内核、产品系列

文章目录 一、半导体公司介绍二、STM32 芯片2.1 芯片命名2.2 Cortex-M内核2.3 STM32 系列 一、半导体公司介绍 STM32是STMicroelectronics&#xff08;意法半导体&#xff09;公司的一系列32位微控制器&#xff0c;基于ARM Cortex-M内核&#xff0c;具有高性能、低功耗、丰富的…

【软件设计师暴击考点】软件工程知识高频考点【二】

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;软件…

chatgpt赋能python:Python的Pipe:快速高效的数据传输工具

Python的Pipe&#xff1a;快速高效的数据传输工具 如果你是一名Python工程师&#xff0c;那么你一定会非常了解数据传输的重要性。Python的Pipe就是一种可以让你快速高效地传输数据的工具。在本文中&#xff0c;我们将对Python的Pipe进行详细介绍&#xff0c;探讨它的优点、如…

Linux命令(35)之shutdown

Linux命令之shutdown 1.shutdown介绍 linux命令shutdown主要用来重启、关闭服务器。 2.shutdown用法 shutdown [参数] shutdown常用参数 参数说明-c取消即将执行的关机程序-k 仅仅向每个登录用户发出警告信息&#xff0c;并不真正关机 -h关机(halt)-H关机(halt)-P关机(powe…

chatgpt赋能python:Python中的figsize:使用和优化

Python中的figsize&#xff1a;使用和优化 在数据分析和可视化领域&#xff0c;Python被广泛使用。matplotlib是一个流行的Python可视化库&#xff0c;用于创建各种类型的图形。在matplotlib中&#xff0c;figsize是一个非常有用的参数&#xff0c;可以控制图形的大小。 什么…

3、数据库操作语句:MySql函数

1、数学函数 1&#xff09;单行函数可以嵌套 例如&#xff1a;select truncate(round(123.456,2),0) from dual;//结果&#xff1a;123 2&#xff09;三角函数 举例&#xff1a; ATAN2(M,N)函数返回两个参数的反正切值。 与ATAN(X)函数相比&#xff0c;ATAN2(M,N)需要两个参…

git图形化提交报错“error: cannot spawn C:\Program Files (x86)\Git\bin\ssh.exe: ”

一&#xff0c;问题现象 gitTortoise在push代码到服务器的时候出现如下错误&#xff1a; 报错信息&#xff1a; git.exe push --all --progress "origin" error: cannot spawn C:\Program Files (x86)\Git\bin\ssh.exe: No such file or directory fatal: unable t…

blfs:为lfs虚拟机增加桌面03

编译安装Qt5.15 我比较好奇&#xff0c;当前只安装了twm&#xff08;X.org提供的简单的窗口管理器&#xff09;&#xff0c;这个时候Qt的界面是怎么样的一个呈现。 Qt5.15安装 required和recommanded必装&#xff0c;optional中涉及到runtime的建议安装。其他的看需要再安装。…

彻底解决Springboot中路径参数带 (%2F)的问题

彻底解决Springboot中路径参数带/(%2F)的问题 背景 前两天突然出现了一个线上问题&#xff0c;有同事反应我提供的接口报400的错误。接口路径如下 PATCH /v1/basic/owners/{owner_code}/skus/{sku}&#xff0c;经过排查发现是sku参数中有/因此springboot转义后直接报错了。由…

chatgpt赋能python:Python画的图是放置在什么位置合适?

Python画的图是放置在什么位置合适&#xff1f; Python作为一门流行的编程语言&#xff0c;已经被广泛应用于数据科学和机器学习的领域。在这些领域&#xff0c;数据可视化是一个重要的环节。Python提供了许多强大的可视化工具&#xff0c;可以帮助我们更好地了解和分析数据。…

chatgpt赋能python:Python画图设置背景颜色

Python画图设置背景颜色 在Python中使用Matplotlib和Seaborn等库来生成数据可视化图表非常方便。但是&#xff0c;这些图表默认使用白色背景。有时&#xff0c;我们需要在制作图表时更改背景颜色以匹配我们的主题或品牌标识。本文将介绍如何在Matplotlib和Seaborn中设置不同的…

flutter开发 - 七牛云上传sdk插件qiniu_flutter_sdk使用

flutter七牛云上传sdk插件qiniu_flutter_sdk使用 最近在拆分代码&#xff0c;将上传组件设置成插件&#xff0c;下面记录下实现过程。 一、创建flutter_plugin上传插件 这里Android Studio使用创建plugin 填写一下信息 Project nameProject locationDescriptionProject typ…

chatgpt赋能python:Python画照片上的人——让照片更生动

Python画照片上的人——让照片更生动 随着社交媒体的普及&#xff0c;我们经常会在朋友圈或者Instagram上看到各种美丽的照片&#xff0c;不管是旅游照还是日常生活中的自拍&#xff0c;照片已经成为了人们生活中不可或缺的一部分。 为了让照片更加生动&#xff0c;有时候我们…