目录

Java-Collection1ListArrayList顺序表LinkedList链表

Java Collection(1)——List——ArrayList(顺序表)&LinkedList(链表)

一.认识List

1.什么是List?

List是Java标准库中的一个接口,下面是List和其他接口/类的关系图

https://i-blog.csdnimg.cn/direct/64a1e7b89afd4e169631c12b4d828d05.jpeg#pic_center

2.List常用方法简介

1.boolean add(E e)

https://i-blog.csdnimg.cn/direct/84c8c181c15a4c5488309768858b5296.png#pic_center

2.void add(int index, E element)

https://i-blog.csdnimg.cn/direct/29a64e3af0a54c368f20b2b53648e6f7.png#pic_center

3.E remove(int index)

https://i-blog.csdnimg.cn/direct/6c62da90a5334366b5a6e3b4aa292b05.png#pic_center

4.boolean remove(Object o)

https://i-blog.csdnimg.cn/direct/669a7bdb44d0427eadd47a2c3dda6283.png#pic_center

5.E get(int index)

https://i-blog.csdnimg.cn/direct/f6a32e1e18044ae4b7247bee8d4fa2ba.png#pic_center

6.E set(int index, E element)

https://i-blog.csdnimg.cn/direct/a40479688c52422d9405fb3ed7b57b43.png#pic_center

7.void clear()

https://i-blog.csdnimg.cn/direct/fced23e5223049f38eb49212e6303385.png#pic_center

二.ArrayList(顺序表)

1.什么是ArrayList?

简介:

ArrayList是Java标准库中的一个类,实现了List接口。顺序表是用一段 物理地址连续 的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改

特点:

  1.  动态大小:
  2.  快速随机访问:通过下标访问元素,时间复杂度是O(1)
  3.  存储任意类型的数据:支持存储任意类型的数据,包括基本数据类型(自动装箱)
  4.  线程不安全:Vector(使用了synchronized)可以看作是ArrayList的线程安全版本,线程安全问题看我相关的博文

2.模拟实现ArrayList

1.创建ArrayList

实际上就是创建一个数组

https://i-blog.csdnimg.cn/direct/3ea6023f4d7741b785d8e926764a167d.png#pic_center

2.添加元素

https://i-blog.csdnimg.cn/direct/1de4c7e08cd44bbc899e0984df9f3efe.png#pic_center

3.在pos位置添加元素

https://i-blog.csdnimg.cn/direct/41358773bed34a22a3ae5298dff2d3bb.png#pic_center

4.判断某元素是否存在

https://i-blog.csdnimg.cn/direct/aaf2cb74c0fa49da886c62635c6b2095.png#pic_center

5.找到目标元素下标

https://i-blog.csdnimg.cn/direct/ff2068775ed343a2b2d792ea33f6af97.png#pic_center

6.判断输入下标是否合法

https://i-blog.csdnimg.cn/direct/32009ac9938545d9b9f89b4e6fff5f42.png#pic_center

7.获取pos位置的元素

https://i-blog.csdnimg.cn/direct/ede62da76a184eaa8175723c3444c389.png#pic_center

8.将pos位置元素改为value

https://i-blog.csdnimg.cn/direct/1706e14f3da444f4bf2111519ef89380.png#pic_center

9.删除第一次出现的目标元素

https://i-blog.csdnimg.cn/direct/46e6ea6758b74a9cafc1f16676633bc2.png#pic_center

10.清空顺序表

https://i-blog.csdnimg.cn/direct/1fa1e40dde854edfaf127160aa4005b3.png#pic_center

3.模拟实现的ArrayList的源码

class MyArrayList<T>{
    private int[] array;
    private int usedSize;
    private static final int DefaultSize = 2;
    //无参构造方法
    public MyArrayList(){
        this.array = new int[DefaultSize];
    }
    //有参构造方法
    public MyArrayList(int initCapacity){
        this.array = new int[initCapacity];
    }
    //打印
    public void show(){
        for (int i = 0; i < this.usedSize; i++) {
            System.out.print(this.array[i]+" ");
        }
        System.out.println();
    }
    //判断是否满了
    public Boolean isFull(){
        if (this.array.length == this.usedSize)
            return true;
        else
            return false;
    }
    //从最后一个位置添加元素
    public void add(int data){
        if (isFull()){
            this.array = Arrays.copyOf(this.array,this.array.length*2);
        }
        this.array[this.usedSize] = data;
        this.usedSize++;
    }
    //在pos位置添加元素
    public void add(int pos,int data){
        if (pos < 0 || pos > this.usedSize)
            throw new exception(pos+"位置不合法");
        if (isFull()){
            this.array = Arrays.copyOf(this.array,this.array.length*2);
        }
        for (int i = this.usedSize-1; i >= pos ; i--) {
            this.array[i+1] = this.array[i];
        }
        this.array[pos] = data;
        this.usedSize++;
    }
    //判断某元素是否存在
    public boolean contains(int find){
        for (int i = 0; i < this.usedSize; i++) {
            if (this.array[i] == find){
                return true;
            }
        }
        return false;
    }
    //找到目标元素下标
    public int indexOf(int find){
        for (int i = 0; i < this.usedSize; i++) {
            if (this.array[i] == find){
                return i;
            }
        }
        return -1;
    }
    //判断输入下标是否合法
    public  Boolean check(int pos){
        if (pos < 0 || pos > this.usedSize - 1){
            return false;
        }
        return true;
    }
    //获取pos位置的元素
    public int get(int pos){
        if (!check(pos)){
            throw new exception(pos+"位置不合法");
        }
        return this.array[pos];
    }
    //将pos位置元素改为value
    public void set(int pos,int value){
        if (!check(pos)){
            throw new exception(pos+"位置不合法");
        }
        this.array[pos] = value;
    }
    //删除第一次出现的目标元素
    public void del(int toDel){
        int dex = indexOf(toDel);
        if (dex == -1){
            System.out.println("没有要删除的元素");
            return;
        }
        for (int i = dex; i < this.usedSize-1; i++) {
            this.array[i] = this.array[i+1];
        }
        //引用类型
        /*this.array[usedSize-1] = null;*/
        this.usedSize--;
    }
    //获取顺序表长度
    public int size(){
        return this.usedSize;
    }
    //清空顺序表
    public void clear(){
        //引用类型
        /*for (int i = 0; i < this.usedSize; i++) {
            this.array[i] = null;
        }*/
        this.usedSize = 0;
    }
}

三.LinkedList(链表)

1.什么是线性表?

简介:

线性表 是n个具有 相同特性 的数据元素的有限序列,线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并 不一定是连续的空间 (如下图)

https://i-blog.csdnimg.cn/direct/fc75dd4c3c0c4825842bbd09203e2188.png#pic_center

每个节点有两个域,一个存放 数据/值 (value),一个存放 下一个节点的地址 (address)。通过上一个节点能够找到下一个节点,而且这些节点在物理结构上不一定是连续的,这就叫做线性表/链表。

2.链表的分类

从方向上来讲有三种类型

  1.  单向链表(如上图):每个节点只有一个指向下一个节点的指针,最后一个节点的指针指向null。单向链表只能从头到尾进行遍历
  2.  双向链表:每个节点有两个指针,一个指向前一个节点,一个指向下一个节点。双向链表可以从任意一个节点开始,向前或向后进行遍历
  3.  循环链表:链表的最后一个节点指向头节点,形成一个环。循环链表可以是单向的,也可以是双向的

从头节点的区别来讲有两个类型

  1.  带头链表:带头链表是指在链表的第一个节点之前增加一个特殊的节点,称为头结点。这个头结点通常不存储实际的数据,仅作为链表的起始点
  2.  不带头链表(如上图):链表中所有节点的结构是一致的,第一个节点是不确定的(后面讲到头插法就明白了)

通过以上排列组合,一共可以得到六种类型的链表

  1.  单向带头链表
  2.  单向不带头链表
  3.  双向带头链表
  4.  双向不带头链表
  5.  循环带头链表
  6.  循环不带头链表

本文主要介绍 单向不带头链表 ,只要搞清楚这一个类型的原理,其他类型都是同理

3.单向不带头链表模拟实现

1.创建链表

https://i-blog.csdnimg.cn/direct/47cdd17bc26540eebca8f1c9144eb10b.png#pic_center

2.求链表长度

https://i-blog.csdnimg.cn/direct/73d33f50aa494dcaabd94fa947ed7dfe.png#pic_center

3.头插法

https://i-blog.csdnimg.cn/direct/ce0adb9393554a669e4960869e9f3d59.png#pic_center

4.尾插法

https://i-blog.csdnimg.cn/direct/67180ebf4c8e4259b94652eb70bacf9c.png#pic_center

头插法不需要判断当前链表是否为空,但是尾插法需要

因为不论链表是否为空,头插法不会触发空指针异常;但当链表为空时,尾插法如果不进行判断,就会触发空指针异常

5.在pos位置添加元素

https://i-blog.csdnimg.cn/direct/fa642631091041859df43bbd7223843b.png#pic_center

6.删除出现的第一个key目标

https://i-blog.csdnimg.cn/direct/d59d353d19504bb4b86f2018d96836bb.png#pic_center

7.删除出现的所有key目标

https://i-blog.csdnimg.cn/direct/4485e2a7b7ff4c9b92393da197a11202.png#pic_center

8.清空链表

https://i-blog.csdnimg.cn/direct/d86749aee673491ea2f55680780758bc.png#pic_center

4.模拟实现的单向链表源码

public class MySingleList {
    //内部类
    static class ListNode{
        public int val;
        public ListNode next;

        public ListNode(int val){
            this.val = val;
        }
    }
    //
    private ListNode head;
    //创建链表
    public void createList(){
        ListNode node1 = new ListNode(1);
        ListNode node2 = new ListNode(2);
        ListNode node3 = new ListNode(3);
        ListNode node4 = new ListNode(4);
        ListNode node5 = new ListNode(5);

        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;
        this.head = node1;
    }
    //打印链表
    public void disPlay(){
        ListNode cur = head;
        if (head == null){
            System.out.println("链表为空");
            return;
        }
        while (cur != null){
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }
    //求链表长度
    public int getSize(){
        int count = 0;
        ListNode cur = head;
        while (cur != null){
            cur = cur.next;
            count++;
        }
        return count;
    }
    //查找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);
        node.next = head;
        head = node;
    }
    //尾插法
    public void addLast(int data){
        ListNode node = new ListNode(data);
        ListNode cur = head;
        if (cur == null){
            head = node;
            return;
        }
        while (cur.next != null){
            cur = cur.next;
        }
        cur.next = node;
    }
    //在pos位置添加元素,第一个节点下标为0,假如输入pos=2,那么该节点插入后就是链表的第2个节点
    public void addPos(int pos,int data){
        if (pos < 0 || pos > getSize()){
            System.out.println("插入位置不合法");
            return;
        }
        //pos == 0,直接使用头插法
        if(pos == 0){
            addFirst(data);
            return;
        }
        //pos == getSize(),使用尾插法
        if (pos == getSize()){
            addLast(data);
            return;
        }
        ListNode cur = toFindAddPosSubOne(pos);
        ListNode node = new ListNode(data);
        if (cur != null) {
            node.next = cur.next;
            cur.next = node;
        }
    }
    //第一个节点下标为0,找到pos下标的前一个节点,并且返回该节点的地址
    private ListNode toFindAddPosSubOne(int pos){
        if (pos < 0 || pos > getSize()){
            System.out.println("输入的位置不合法");
            return null;
        }
        ListNode cur = head;
        while ( pos - 1 != 0){
            cur = cur.next;
            pos--;
        }
        return cur;
    }
    //删除出现的第一个key目标
    public void delFirstKey(int key){
        if(head == null){
            System.out.println("链表为空");
            return;
        }
        if (head.val == key){
            head = head.next;
        }
        ListNode cur = toFindDel(key);
        if (cur == null){
            System.out.println("没有你要删除的数字");
            return;
        }
        cur.next = cur.next.next;
    }
    //找到要删除节点的前一个节点
    private ListNode toFindDel(int key){
        ListNode cur = head;
        while (cur != null){
            if (cur.next.val == key){
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }
    //删除出现的所有key目标
    public void delAllKey(int key){
        if(head == null){
            System.out.println("链表为空");
            return;
        }
        while (head.val == key){
            head = head.next;
        }
        ListNode cur = head;
        ListNode del = cur.next;
        int count = 0;
        while (del != null){
            if (del.val != key){
                del = del.next;
                cur = cur.next;
            }else {
                cur.next = del.next;
                del = del.next;
                count++;
            }
        }
        if (count == 0){
            System.out.println("没有你要删除的数字");
        }
    }
    //清空链表
    public void clear(){
        head = null;
    }
}

四.顺序表VS链表

存储方式

  1.  顺序表:使用连续的内存空间存储元素
  2.  链表:使用非连续的内存空间存储元素

访问方式

  1.  顺序表:支持随机访问,通过下标/索引在O(1)时间内访问
  2.  链表:不支持随机访问,通过遍历链表访问元素(O(n)时间复杂度)

删除和插入

  1.  顺序表:需要移动大量元素,例如:删除下标为2的元素,后面的元素都需要向前覆盖
  2.  链表:头插法/尾插法,删除头/尾节点时间复杂度是O(1),虽然其他位置的插入和删除时间复杂度也是O(n),但不需要大量地移动元素

容量

  1.  顺序表:自动扩容
  2.  链表:没有容量这个概念

适用场景

  1.  顺序表:元素高效存储+频繁访问
  2.  链表:任意位置插入和删除频繁