线性表 :顺序表(ArrayList) 和 单链表(LinkedList) 详解

线性表:(List)

  • 是n个具有 相同特性 的数据元素的 有限序列 。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串

  • 线性表在逻辑上线性结构 ,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以 数组链式结构 的形式存储

常用方法列表:(顺序表 和 链表都支持)

返回类型方法解释
booleanadd(E e)尾插
voidadd(int index, E e)将 元素e 插到 位置index
Eremove(int index)删除 index位置 元素
booleanremove(Object o)删除第一次出现的 指定元素(如果存在)
intsize()返回列表中的元素个数
Eget(int index)返回 index 下标处的元素
Eset(int index, E e)用 e 替换列表中下标为 index 位置的元素
voidclear()清空列表
booleanisEmpty()返回列表是否为空
booleancontains(Object o)返回列表中是否包含 o 这个对象
intindexOf(Object o)返回列表中 o 第一次出现的下标(若不存在,返回 -1)
intlastIndexOf(Object o)返回列表中 o 最后一次出现的下标(若不存在,返回 -1)
List < E >subList(int fromIndex, int toIndex)从线性表中截取一段线性表 [fromIndex,toIndex),不影响原线性表
voidsort(Comparator cmp)对线性表进行排序,以传入的比较器进行元素的比较
Iterator< E >Iterator()返回迭代器,进行从前往后的遍历
ListIterator< E >listIterator()返回列表迭代器(列表从 0 开始)
ListIterator< E >listIterator(int index)从 index 开始,返回列表迭代器(列表从 1 开始)
Object[]List.toArray()List 转 数组
ListArrays.asList()数组 转 List

在这里插入图片描述
在这里插入图片描述

1、顺序表:

(1)神马是顺序表:(ArrayList)
是用一段 物理地址连续 的存储单元依次存储数据元素的 线性结构(逻辑上也连续)
一般采用 数组 存储,也就是在数组上增删查改
在这里插入图片描述
① 从 Java语法 的角度:

List 是接口 ; ArrayList 是类,实现了 List

② 从 数据结构 的角度:

List 表示线性表, ArrayList 表示顺序表
通过 ArrayList 实现 List 。。 表达了顺序表是一种线性表

(2)分类:

静态 顺序表: 使用 定长数组 存储
动态 顺序表: 使用 动态开辟的数组 存储

(3)接口的实现:(下面这个为一个简单的顺序表)

在这里插入图片描述

在这里插入图片描述

ArrayList 相对 List 额外的方法:

方法解释
ArrayList()构造一个初始容量为十的空列表
ArrayList(Collection<? extends E> c)构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
ArrayList(int initialCapacity)构造具有指定初始容量的空列表

为了更好的对这些方法的理解,手动实现 ArrayList 的一些方法:

github链接:(感兴趣的小伙伴可以看一下)
https://github.com/JACK-QBS/DataStructure/blob/master/%E7%BA%BF%E6%80%A7%E8%A1%A8/MyArrayList.java

  1. 顺序表中间/头部的插入删除,时间复杂度为O(N)
  2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
  3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

为了解决顺序表的弊端,我们引入了单链表:

2、单链表:

(1)神马是单链表 :
是一种 物理存储结构非连续 存储结构,数据元素的 逻辑顺序 是通过链表中的 引用链接次序 实现的 。(逻辑连续)

(2)表现形式:(一共是 8 种)
聪明的你尝试自己组合一下。。。
单向 、 双向
带头 、 不带头
循环 、 非循环

下来我们简单介绍两种形式的:(面试问的最多!)
链表 由一个一个的 节点组成
你问我节点是干嘛的?
节点 当然是用来 存储数据 的了。。。

data :数据,不一定是整型的
next :下一个节点的引用(地址)

单向 不带头 非循环链表

(头节点可能会随时改变)
在这里插入图片描述
写一个简单的 单向 不带头 非循环链表

/**
 * 单链表
 */
class Node {
    public int data;
    public Node next; // 表示下个节点的引用
    public Node(int data) {
        this.data = data;
    }
}
public class SingleLinkedList {
    public static void main(String[] args) {
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        singleLinkedList.addFirst(6);
        singleLinkedList.addLast(1);
        singleLinkedList.addFirst(4);
        singleLinkedList.addLast(8);
        singleLinkedList.addLast(1);
        singleLinkedList.addLast(6);
        singleLinkedList.display();
        //singleLinkedList.remove(1);
        //singleLinkedList.display();
        System.out.println(singleLinkedList.contains(6));
        singleLinkedList.removeAllKey(6);
        singleLinkedList.display();
    }

    public Node head;// 标识头节点
    //头插法
    public void addFirst(int data) {
        Node node = new Node(data);
        node.next = this.head;
        this.head = node;
    }

    // 打印
    public void display() {
        Node cur = this.head;
        while(cur != null) {
            System.out.print(cur.data+" ");
            cur = cur.next;
        }
        System.out.println();
    }

    // 尾插法
    public void addLast(int data) {
        Node node = new Node(data);
        if(this.head == null) {
            this.head = node;
        } else {
            Node cur = this.head;
            while(cur.next != null) {
                cur = cur.next;
            }
            cur.next = node;
        }
    }

    // 检查下标是否合法
    public boolean checkIndex(int index) {
        if(index == 0 || index > this.getLength()) {
            System.out.println("下标不合法!");
            return false;
        }
        return true;
    }

    // 任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data) {
        if(!checkIndex(index)) {
            return;
        }
        if(index == 0) {
            addFirst(data);
            return;
        }
        if(index == this.getLength()) {
            addLast(data);
            return;
        }
        Node cur = searchPrev(index);//cur 此时保存的就是 index-1 位置节点的引用
        Node node = new Node(data);
        node.next = cur.next;
        cur.next = node;


    }
    public int getLength() {
        int count = 0;
        Node cur = this.head;
        while(cur != null) {
            count++;
            cur = cur.next;
        }
        return count;
    }
    //查找index的位置,找到并返回引用
    public Node searchPrev(int index) {
        Node cur = this.head;
        int count = 0;
        while(count < index-1) {
            cur = cur.next;
            count++;
        }
        return cur;

    }

    // 找前驱
    public Node searchPrevNode(int key) {
        Node cul = this.head;
        while (cul.next != null) {
            if(cul.next.data == key) {
                return cul;
            }
            cul = cul.next;
        }
        return null;
    }
    //删除第一次出现关键字 key 的节点
    public void remove(int key) {
        // 头节点是要删除的节点
        if(this.head == null) return;
        if(this.head.data == key) {
            this.head = this.head.next;
            return;
        }
        Node cul = searchPrevNode(key);
        if(cul == null) {
            System.out.println("没有你要删除的数字!");
            return;
        }
        Node del = cul.next; // 所要删除的节点
        cul.next = del.next;
    }
    
    // 判断是否包含某个元素
    public boolean contains(int toFind) {
        Node cul = this.head;
        while (cul != null) {
            if(cul.data == toFind) {
                return true;
            }
            cul = cul.next;
        }
        return false;
    }

    // 删除所有值为 key 的节点
    public void removeAllKey(int key) {
        if(this.head == null) return;
        Node prev = this.head;
        Node cul = this.head.next;
        while(cul != null) {
            if (cul.data == key) {
                prev.next = cul.next;
                cul = cul.next;
            } else {
                prev = cul;
                cul = cul.next;
            }
        }
        if(this.head.data == key) {
            this.head = this.head.next;
        }
    }
}

手动实现 LinkedList,双向带头非循环链表

Github 链接:

https://github.com/JACK-QBS/DataStructure/blob/master/%E7%BA%BF%E6%80%A7%E8%A1%A8/linkedList.java

3、顺序表和链表的区别:(面试)

区别:

顺序表:(物理上和逻辑上都是连续的)

  • (1)空间连续、随机访问(时间复杂度是 O(1))(底层是数组)
  • (2)中间前面 部分的 插入删除 时间复杂度是 O(n)
  • (3)线程不安全
  • (4)增容代价大
  • (5)顺序表总是要保留一段空间,不使用

链表: (逻辑上连续,物理上不一定连续)

  • (1)以结点为单位存储, 随机访问(时间复杂度为 O(n)
  • (2)链表 定位到结点 的前提下,插入 / 删除 元素是 O(1),但定位结点往往是 O(n)
  • (3)线程不安全
  • (4)没有增容问题,插入一个开辟一个空间
  • (5)链表的每个结点都要浪费一点空间,保存地址

在这里插入图片描述

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值