1.ArrayList的缺陷
2.单链表的实现
3.LinkedList的使用(模拟实现)
我们之前介绍过ArrayList了,它的底层是数组,数组是一段连续的空间,当我们想要插入或者删除数据的时候,插入元素,就要让插入位置的元素整体都往后移动,删除元素同样要让后面的元素往前移动,当要进行元素很多的插入或者删除的时候,ArrayList是效率很低的,所以当我们要大量插入元素或者大量删除元素,不推荐使用ArrayList
(由于其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景)
因此:java集合中又引入了LinkedList,即链表结构。
(1)链表的概念:链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的
链表我们之前也介绍过,它在物理空间上是不连续,但在逻辑上连续,也就是说它的元素所在的存储空间中不是连在一起的,不是说元素1空间后面就是元素2的空间,但它在逻辑上连续,元素1跟元素2相当于拿一根绳子连接,虽然不是紧挨着的,但通过元素1就能找到元素2,这就像我们生活中的火车,或火车的车厢都是链在一起的
链表中的元素我们称为节点,一个节点包含着本身的值,和下一个节点的信息(下一个节点的地址),分别是value和next(对于单链表而言)
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
(1)带头不带头
(2)单向双向
(3)循环非循环
我们把他们排列组合一下就能排列出8中情况,我们重要学习的链表结构有两种
1.不带头单向非循环
2.不带头双向非循环
自然这两种是面试笔试中常考的,当然也是比较难懂的,因为给的条件不多嘛~
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多
无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。
这里解释一下这几个词的含义
带头 | | 不带头:头的意思就是头节点的意思,头节点的值一般没有什么含义,它与第一个元素链接,它拥有第一个元素的地址,只要有头,我们就能轻松找到第一个元素,你可以把它当做一个虚拟节点(虚拟节点的操作经常用来解题)
单向 || 双向:什么叫做单向,我们不是说了嘛,每一个节点它都包含下一个节点的地址,单向顾名思义就是一个方向,你只能从1找到2,而不能返回来从2找到1,因为1有2的地址,2只有3的地址,没有1的地址,所以说它是单向的;那么双向就是可以从1找到2,也可以从2找到1啦,双向链表的节点中,包含了两个地址,一个是下一个节点的地址,另外一个是上一个节点的地址
上图就是单链表和双链表的图解
循环 | | 非循环:循环的意思就是这个链表围成了一个圈圈,下面给图来看
这图一目了然吧~上图就是循环,非循环自然不多解释
接下来来自定义实现一个单链表
下面直接给出源码
package LinkedList内容;
import ArrayList内容.PosIndexNotLegalException;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 86152
* Date: 2022-11-12
* Time: 20:46
*/
public class SingleList {
//创建节点:包含值和地址两部分
static class Node{
public int val;
public Node next;
public Node(int val){
this.val = val;
}
}
public Node head;//创建头节点
//用穷举的方式创建链表
public void createList(){
Node node1 = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
Node node4 = new Node(4);
node1.next = node2;
node2.next = node3;
node3.next = node4;
head = node1;
}
//头插法:时间复杂度O(1)
public void addHead(int value){
Node node = new Node(value);
node.next = head;
head = node;
}
//尾插法:时间复杂度O(n)
public void addTail(int value){
Node node = new Node(value);
//处理没有节点的情况
if(head == null){
head = node;
}else {
Node cur = head;
while (cur.next != null){
cur = cur.next;
}
cur.next = node;
}
}
//遍历打印链表
public void display(){
Node cur = this.head;
while (cur != null){
System.out.print(cur.val + " ");
cur = cur.next;
}
System.out.println();
}
//判断链表中是否存在key值
public boolean contains(int key){
Node cur = head;
while (cur != null){
if(cur.val == key){
return true;
}
cur = cur.next;
}
return false;
}
//链表长度
public int size(){
int count = 0;
Node cur = head;
while (cur != null){
count++;
cur = cur.next;
}
return count;
}
//在指定位置插入数据,要判断位置合法性
private void checkIndex(int index){
if(index < 0 || index > size()){
throw new IndexNotLegalException();
}
}
public void addIndex(int index, int value){
checkIndex(index);
if(index == 0){
addHead(value);
return;
}
if(index == size()){
addTail(value);
return;
}
Node node = new Node(value);
Node cur = head;
for(int i = 0; i < index-1; i++){
cur = cur.next;
}
node.next = cur.next;
cur.next = node;
}
//删除第一次出现key的节点
//先找到前驱
private Node prevKey(int key){
Node cur = this.head;
while (cur.next != null){
if(cur.next.val == key){
return cur;
}
cur = cur.next;
}
return null;
}
public void remove(int key){
//头节点要单独处理
if(head.val == key){
head = head.next;
return;
}
Node cur = prevKey(key);
if(cur == null){
return;
}
Node del = cur.next;
cur.next = del.next;
//cur.next = cur.next.next;
}
//删除所有值为key的节点
//先处理其他节点,最后再处理头节点
//对于头节点的处理得放在后面处理,放在前面最后不好处理
public void removeAllKey(int key){
if(head == null){
return;
}
Node prev = head;
Node cur = head.next;
while (cur != null){
if(cur.val == key){
prev.next = cur.next;
cur = cur.next;
}else {
prev = cur;
cur = cur.next;
}
}
if(head.val == key){
head = head.next;
}
}
//清空链表的所有节点
public void clear(){
//this.head = null; 简单粗暴
Node cur = head;
while (cur != null){
Node curNext = cur.next;
cur.next = null;
cur = curNext;
}
head = null;
}
//递归实现链表的打印
public void printList(Node head){
if(head == null){
return;
}
if(head.next == null){
System.out.print(head.val+" ");
return;
}
printList(head.next);
System.out.print(head.val+" ");
}
}
很多方法的细节都写在了注释里了~
我们下面来介绍一下LinkedList,我们在写题目的时候,通常少不了使用
LinkedList底层就是一个双向链表
双向链表的实现这里就不实现了,无非就是在单链表的基础上多加上一个prev,然后还有last(这是尾节点,和头节点head对应)
LinkedList的底层是双向链表结构,由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。
【说明】
LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
下面介绍一下LinkedList的使用
这边直接给代码了,解释写在了注释里面
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 86152
* Date: 2022-11-14
* Time: 17:36
*/
public class LinkedListTest {
//三种遍历方法
public static void main(String[] args) {
List list = new LinkedList<>();
list.add(1);
list.add(1);
list.add(1);
list.add(1);
list.add(1);
list.add(19);
list.add(10);
System.out.println("======for=========");
for(int i = 0; i < list.size(); i++){
System.out.print(list.get(i) + " ");
}
System.out.println();
System.out.println("========foreach========");
for(Integer x : list){
System.out.print(x + " ");
}
System.out.println();
System.out.println("==========迭代器========");
ListIterator it = list.listIterator();
while (it.hasNext()){
System.out.print(it.next() + " ");
}
System.out.println();
//反向迭代器打印
ListIterator rit = list.listIterator(list.size());
while (rit.hasPrevious()){
System.out.print(rit.previous() + " ");
}
}
/**
* LinkedList常用api
* @param args
*/
public static void main2(String[] args) {
LinkedList list = new LinkedList<>();
list.add(1);
list.add(10);
list.add(18);
list.add(13);
list.add(2);
list.add(3);//add默认尾插
System.out.println(list);
list.add(0,2);//add的构造方法,支持在指定元素插入
System.out.println(list);
//删除操作remove
//list.remove();//不带参数的remove默认删除第一个元素
//list.remove(1);//带一个参数表示删除指定位置元素
//list.removeFirst();
//list.removeLast();
//System.out.println(list);
//查询contains 判断元素是否存在
System.out.println(list.contains(18));
//找到第一次出现元素的下标
System.out.println(list.indexOf(2));//找第一次出现的位置
System.out.println(list.lastIndexOf(3));//找最后一次出现的位置
//获取指定位置元素
System.out.println(list.get(0));
//更新指定位置元素
list.set(0,99);
System.out.println(list);
//截取元素
System.out.println(list.subList(0, 2));//区间是前闭后开
//还可以用截取的元素重新创建一个链表
List list2 = list.subList(0,3);
System.out.println(list2);
//清空元素
list.clear();
System.out.println(list.size());
}
/**
* 构造方法
* @param args
*/
public static void main1(String[] args) {
//一种是无参的构造方法
LinkedList list = new LinkedList<>();
//第二种:LinkedList的构造方法可以传入ArrayList
ArrayList arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
LinkedList linkedList = new LinkedList<>(arrayList);
System.out.println(linkedList);
}
}
(部分图片来源:比特高博)