文章目录
- [一. ArrayList 的缺陷](#一. ArrayList 的缺陷)
- 二.链表
-
- [2.1 链表的概念及结构](#2.1 链表的概念及结构)
- [2.2 链表的结构](#2.2 链表的结构)
-
- [2.2.1 单向或者双向](#2.2.1 单向或者双向)
- [2.2.2 带头或者不带头](#2.2.2 带头或者不带头)
- [2.2.3 循环非循环](#2.2.3 循环非循环)
- [2.3 链表的实现](#2.3 链表的实现)
-
- [1. IList接口](#1. IList接口)
- [2. MySingleList 类中具体实现(不带头单向非循环链表)](#2. MySingleList 类中具体实现(不带头单向非循环链表))
-
- [1. 节点抽象成内部类](#1. 节点抽象成内部类)
- 手搓一个链表
- [2. 头插法](#2. 头插法)
- [3. 尾插法](#3. 尾插法)
- [4. 指定位置插入](#4. 指定位置插入)
- [5. 是否包含key值](#5. 是否包含key值)
- [6. 删除指定值的节点](#6. 删除指定值的节点)
- [7. 删除所有指定值所在节点](#7. 删除所有指定值所在节点)
- [8. 获取链表长度](#8. 获取链表长度)
- [9. 清空链表](#9. 清空链表)
- [10. 打印链表](#10. 打印链表)
- [三. 链表面试题](#三. 链表面试题)
- [四. MyLinkedList 的模拟实现(不带头双向非循环链表)](#四. MyLinkedList 的模拟实现(不带头双向非循环链表))
-
- 节点抽象成内部类
- 1.头插法
- 2.尾插法
- 3.指定位置插入
- 4.是否包含Key值
- [5. 删除指定值的节点](#5. 删除指定值的节点)
- 6.删除所有指定值的节点
- [7. 获取链表长度](#7. 获取链表长度)
- [8. 清空链表](#8. 清空链表)
- 9.打印链表
- [五. LinkedList 的使用](#五. LinkedList 的使用)
-
- [5.1 什么是LinkedList](#5.1 什么是LinkedList)
- [5.2 LinkedList 的使用](#5.2 LinkedList 的使用)
- [5.2.1 LinkedList 的构造](#5.2.1 LinkedList 的构造)
- [5.2.2 LinkedList的其他常用方法介绍](#5.2.2 LinkedList的其他常用方法介绍)
- [5.3 LinkedList的遍历](#5.3 LinkedList的遍历)
-
- [5.3.1 for遍历](#5.3.1 for遍历)
- [5.3.2 foreach](#5.3.2 foreach)
- [5.3.3 迭代器遍历(正向反向)](#5.3.3 迭代器遍历(正向反向))
- [六. ArrayList和LinkedList的区别](#六. ArrayList和LinkedList的区别)
一. ArrayList 的缺陷
上篇文章已经熟悉了ArrayList的使用,并且进行了简单模拟实现。通过源码知道,ArrayList底层使用数组来存储元素
ArrayList源码:
java
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// ...
// 默认容量是10
private static final int DEFAULT_CAPACITY = 10;
//...
// 数组:用来存储元素
transient Object[] elementData; // non-private to simplify nested class access
// 有效元素个数
private int size;
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低 ,因此ArrayList不适合做任意位置插入和删除比较多的场景 。因此:java集合中又引入了LinkedList,即链表结构。
二.链表
2.1 链表的概念及结构
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的

1.从图中可以看出,链式结构在逻辑上是连续的 ,但是在物理上不一定连续
2.现实中的节点 一般都是从堆上申请出来的
3.从堆上申请的空间 ,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
2.2 链表的结构
以下情况组合起来就有8种链表结构:
2.2.1 单向或者双向

2.2.2 带头或者不带头

2.2.3 循环非循环

虽然有这么多的链表的结构,但是我们重点掌握两种:
- 无头单向非循环链表 :结构简单 ,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多 。
- 无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表
2.3 链表的实现
1. IList接口
java
public interface IList {
public void addFirst(int data);
//尾插法
public void addLast(int data);
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data);
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key);
//删除第一次出现关键字为key的节点
public void remove(int key);
//删除所有值为key的节点
public void removeAllKey(int key);
//得到单链表的长度
public int size();
public void clear();
public void display();
}
再次回顾:接口中的方法默认都是 public abstract 修饰的,这里不写public也可以
正式使用LinkList 之前还是一样 自己实现一个链表 方便我们更好的理解链表
2. MySingleList 类中具体实现(不带头单向非循环链表)
1. 节点抽象成内部类

手搓一个链表
就是
java
public void createList() {
ListNode node1 = new ListNode(12);
ListNode node2 = new ListNode(23);
ListNode node3 = new ListNode(34);
ListNode node4 = new ListNode(45);
ListNode node5 = new ListNode(56);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
this.head = node1;
}
2. 头插法
java
public void addFirst(int data) {
ListNode node = new ListNode(data);
node.next=head;
head=node;
}
3. 尾插法
java
public void addLast(int data) {
ListNode node = new ListNode(data);
if(head==null){
node=head;
return ;
}
ListNode cur =head;
while(cur.next!=null){
cur=cur.next;
}
cur.next=node;
}
4. 指定位置插入
首先要判断插入的位置是否合法 , 写一个自定义异常
java
public class IndexException extends RuntimeException{
public IndexException(){
}
public IndexException(String message){
super(message);
}
}

java
public void addIndex(int index, int data) {
if(index==0){
addFirst(data);
return ;
}
int len=size();//获取链表长度
if(index==len){
addLast(data);
}
ListNode node=new ListNode(data);
ListNode cur=head;
try{
while(index-1!=0){
cur=cur.next;
index--;
}
node.next=cur.next;
cur.next=node;
}catch (IndexException e){
System.out.println("index位置不合法!!!!");
e.printStackTrace();
}
}
5. 是否包含key值

java
public boolean contains(int key) {
//还是需要cur 不能让头结点移动,要不然找不到头了
ListNode cur=head;
while (cur!=null){
if(cur.val==key){
return true;
}
cur=cur.next;
}
return false;
}
6. 删除指定值的节点


java
public void remove(int key) {
ListNode node=new ListNode(key);
if(head==null){
//空链表 直接return 没啥可删的
return;
}
if(head.val==key){
//如果要删的是头 把头定位下一个节点就行了
head=head.next;
return ;
}
ListNode cur = findNodeOfKey(key);
if(cur==null){
return; //没找到要删除的值
}
ListNode del = cur.next;
//要是找到了,就架空del 删除del
cur.next=del.next;
}
public ListNode findNodeOfKey(int key){
ListNode cur=head;
while (cur.next!=null){
if(cur.next.val==key){
return cur;
}
cur=cur.next;
}
return null;
}
7. 删除所有指定值所在节点


java
public void removeAllKey(int key) {
if(head == null) { //头为空 直接返回
return;
}
ListNode prev = head;
ListNode cur = head.next;//cur 代表当前节点是否为你要删除的节点
while (cur != null) {
if(cur.val == key) { //判断一下是否为指定值
prev.next = cur.next;
cur = cur.next;// 是指定值就 架空cur 删除cur 并且cur向后走一位
}else {
prev = cur;
cur = cur.next; //不是指定值就 两个都往后走
}
}
if(head.val == key) {
head = head.next; //头为指定值 删头
}
}
8. 获取链表长度

java
public int size() {
int len = 0;
ListNode cur = head;
while (cur != null) {
len++;
cur = cur.next;
}
return len;
}
9. 清空链表
两种方法: ①直接head置为空 ,有点暴力
②head cur 一个一个置为空, 更温和一点

java
public void clear() {
ListNode cur = head;
while (cur != null) {
ListNode curN = cur.next;
cur.next = null;
cur = curN;
}
head = null;
}
10. 打印链表
java
public void display() {
ListNode cur = head;
while (cur != null) {
System.out.print(cur.val+" ");
cur = cur.next;
}
System.out.println();
}
}
三. 链表面试题
关于链表的面试题 都在刷题专栏 test1,像练习链表题的同学可以移步刷题专栏的第一篇文章
以上我们讲的都是不带头单向非循环链表
集合类中都是双向链表,大家注意区分,内部类的实现有所区别,双向链表有三个域,前驱域,数据域,后驱域
四. MyLinkedList 的模拟实现(不带头双向非循环链表)
节点抽象成内部类
java
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;
1.头插法


2.尾插法
java
public void addLast(int data) {
//尾插 遍历整个链表 最后一个节点的next 指向node
ListNode node=new ListNode(data);
if(head==null){
head=node;
return ;
}
ListNode cur=head;
while(cur.next!=null){
cur=cur.next;
}
cur.next=node;
}
3.指定位置插入
java
public void addIndex(int index, int data) {
int len=size();
if(index<0 || index>len){
System.out.println("index位置不合法");
return;
}
if(index==0){
addFirst(data);
return ;
}
if(index==len){
addLast(data);
return ;
}
ListNode cur=head;
while(index-1!=0){
cur=cur.next;
index--;
}
ListNode node=new ListNode(data);
node.next=cur.next;
cur.next=node;
}
4.是否包含Key值
java
public boolean contains(int key) {
ListNode cur=head;
while(cur!=null){
if(cur.val==key){
return true;
}
cur=cur.next;
}
return false;
}
5. 删除指定值的节点
java
public void remove(int key) {
if(head==null){
return ;
}
//头是需要删除的值 删除 头
if(head.val==key){
head=head.next;
return ;
}
ListNode cur=findKeyNode(key);
ListNode del=cur.next;
//cur是需要删除的节点的前一个
if(cur==null){
return ;//没有需要删除的节点
}
cur.next=del.next;//架空删除 哪个需要删除的节点
}
private ListNode findKeyNode(int key){
ListNode cur=head;
while(cur.next!=null){
if(cur.next.val==key){
return cur; //获得需要删除的节点的前一个结点 单链表走过了 无法找到前一个结点
}
cur=cur.next;
}
return null;
}
6.删除所有指定值的节点
java
public void removeAllKey(int key) {
if(head==null){
return ;
}
if(head.val == key) {
head = head.next;
}
ListNode prev=head;
ListNode cur=head.next;
while(cur!=null){
if(cur.val==key){
prev.next=cur.next;
cur=cur.next;
}else{
prev=cur;
cur=cur.next;
}
}
}
7. 获取链表长度
java
public int size() {
int count=0;
ListNode cur=head;
while(cur!=null){
count++;
cur=cur.next;
}
return 0;
}
8. 清空链表
java
public void clear() {
//不是把头置为空 太暴力了 我们一个一个置空
ListNode cur=head;
while(cur!=null) {
ListNode curN = cur.next;
cur.next = null;
cur = curN;
}
head=null;
}
9.打印链表
java
public void display() {
//打印
ListNode cur=head;
while(cur!=null){
System.out.print(cur.val+" ");
cur=cur.next;
}
System.out.println();
}
五. LinkedList 的使用
5.1 什么是LinkedList
LinkedList的底层是双向链表结构,由于链表没有将元素存储在连续的空间 中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。

在集合框架中,LinkedList也实现了List接口,具体如下:
【说明】
- LinkedList实现了List接口
- LinkedList的底层使用了双向链表
- LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
- LinkedList在任意位置插入和删除元素时效率比较高,时间复杂度为O(1)
- LinkedList比较适合任意位置插入的场景
5.2 LinkedList 的使用
5.2.1 LinkedList 的构造

java
public static void main(String[] args) {
// 构造一个空的LinkedList
List<Integer> list1 = new LinkedList<>();//无参构造
List<String> list2 = new java.util.ArrayList<>();
list2.add("JavaSE");
list2.add("JavaWeb");
list2.add("JavaEE");
// 使用ArrayList构造LinkedList
List<String> list3 = new LinkedList<>(list2);// 使用集合容器中的元素构造
}
通配符 (讲解ArrayList的博客中有介绍)list2 由List创建 属于LinkedList 的子类所以可以作为参数
5.2.2 LinkedList的其他常用方法介绍

简单演示几个方法 大家也可以自己点击源码查看其他方法的逻辑
5.3 LinkedList的遍历
5.3.1 for遍历
就是MyLinkedList里的display()方法
5.3.2 foreach
java
LinkedList<Integer> list = new LinkedList<>();
list.add(1); // add(): 默认尾插
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
System.out.println(list.size());
// foreach遍历
for (int e:list) {
System.out.print(e + " ");
}
System.out.println();
5.3.3 迭代器遍历(正向反向)
java
// 使用迭代器遍历---正向遍历
ListIterator<Integer> it = list.listIterator();
while(it.hasNext()){
System.out.print(it.next()+ " ");
}
System.out.println();
// 使用反向迭代器---反向遍历
ListIterator<Integer> rit = list.listIterator(list.size());
while (rit.hasPrevious()){
System.out.print(rit.previous() +" ");
}
System.out.println();
六. ArrayList和LinkedList的区别
