【本节目标】
- ArrayList的缺陷
- 链表
- 链表相关oj
- LinkedList的模拟实现
- LinkedList的使用
- ArrayList和LinkedList的区别
1. ArrayList的缺陷
上节课已经熟悉了ArrayList的使用,并且进行了简单模拟实现。通过源码知道,ArrayList底层使用数组来存储元素:
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. 链表
2.1 链表的概念及结构
链表是一种物理存储结构上非连续的存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的
- 链表是物理上(内存)不一定连续,逻辑上连续的一种结构
- 现实中的结点一般都是从堆上申请出来的
- 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
实际中链表的结构非常多样,以下情况组合起来就有8种( 2 3 2^3 23)链表结构
-
单向或者双向
-
带头或者不带头
-
循环或者非循环
虽然有这么多的链表的结构,但是我们重点掌握两种:
-
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多
-
无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向非循环链表
2.2 链表的实现
public interface IList {
//头插法
void addFirst(int data);
//尾插法
void addLast(int data);
//任意位置插入,第一个数据节点为0号下标
void addIndex(int index,int data);
//查找是否包含关键字key是否在单链表当中
boolean contains(int key);
//删除第一次出现关键字为key的节点
void remove(int key);
//删除所有值为key的节点
void removeAllKey(int key);
//得到单链表的长度
int size();
//清空单链表
public void clear();
//显示单链表数据
public void display();
}
public class IndexException extends RuntimeException{
public IndexException() {
}
public IndexException(String msg) {
super(msg);
}
}
import java.util.List;
public class MySingleList implements IList{
static class ListNode {
public int val;
public ListNode next;
public ListNode(int val) {
this.val = val;
}
}
//链表的头节点,链表的属性
public ListNode head;
public void createList() {
ListNode node1 = new ListNode(12);
ListNode node2 = new ListNode(23);
ListNode node3 = new ListNode(34);
ListNode node4 = new ListNode(45);
node1.next = node2;
node2.next = node3;
node3.next = node4;
//方法执行完毕后,局部变量销毁
this.head = node1;
}
@Override
public void display() {
//1.怎么从第一个节点走到第二个节点
//head = head.next
//2.什么时候算把节点都遍历完成了
//head = null
ListNode cur = head;
while (cur != null) {
System.out.print(cur.val + " ");
cur = cur.next;
}
System.out.println();
}
/**
* 求当前列表有多少个节点
* @return
*/
@Override
public int size() {
int count = 0;
ListNode cur = head;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
/**
* 求当前列表是否存在key
* @param key
* @return
*/
@Override
public boolean contains(int key) {
ListNode cur = head;
while (cur != null) {
if (cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
@Override
public void addFirst(int data) {
//插入数据的时候一定要记住先绑后面
ListNode node = new ListNode(data);
node.next = head;
head = node;
}
@Override
public void addLast(int data) {
ListNode node = new ListNode(data);
if (head == null) {
head = node;
}else {
ListNode cur = head;
while (cur.next != null) {
cur = cur.next;
}
cur.next = node;
}
}
@Override
public void addIndex(int index, int data) throws IndexException {
if (index < 0 || index > size()) {
throw new IndexException("index不合法: " + index);
}
ListNode node = new ListNode(data);
if (head == null) {
head = node;
return;
}
if (index == 0) {
addFirst(data);
return;
}
if (index == size()) {
addLast(data);
return;
}
//中间插入
ListNode cur = searchPrevIndex(index);
node.next = cur.next;
cur.next = node;
}
private ListNode searchPrevIndex(int index) {
ListNode cur = head;
int count = 0;
while (count != index - 1){
cur = cur.next;
count++;
}
return cur;
}
@Override
public void remove(int key) {
if (head == null) {
return;
}
if (head.val == key) {
head = head.next;
return;
}
ListNode cur = findPrevKey (key);
if (cur == null) {
return;//没有你要删除的数字
}
cur.next = cur.next.next;
}
private ListNode findPrevKey (int key) {
ListNode cur = head;
while (cur.next != null) {
if (cur.next.val == key) {
return cur;
}else {
cur = cur.next;
}
}
return null;
}
/**
* 遍历一次列表,把所有的key删掉
* 定义两个指针:prev,cur
* prev表示当前可能要删除的节点的前驱
* cur表示当前可能要删除的节点
* prev.next = cur.next;
* @param key
*/
@Override
public void removeAllKey(int key) {
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;
}
}
//除了头结点都删除完成了
if (head.val == key) {
head = head.next;
}
}
@Override
public void clear() {
//当一个对象没有人引用的时候,就会被回收掉
//直接将head置为空
//head = null;
//或者所有节点置为空
ListNode cur = head;
ListNode curNext;
while (cur != null){
curNext = cur.next;
cur.next = null;
cur = curNext;
}
head = null;
}
}
public class Test {
public static void main(String[] args) {
MySingleList mySingleList = new MySingleList();
mySingleList.createList();
System.out.println("====");
mySingleList.display();
System.out.println("====");
mySingleList.addFirst(12);
mySingleList.addFirst(23);
mySingleList.addFirst(34);
mySingleList.addFirst(45);
mySingleList.addFirst(56);
mySingleList.display();
System.out.println("====");
mySingleList.addLast(12);
mySingleList.addLast(23);
mySingleList.addLast(34);
mySingleList.addLast(45);
mySingleList.addLast(56);
mySingleList.addLast(67);
mySingleList.display();
System.out.println("====");
mySingleList.addIndex(0,9999);
mySingleList.display();
System.out.println("====");
mySingleList.remove(23);
mySingleList.display();
System.out.println("====");
mySingleList.removeAllKey(23);
mySingleList.display();
System.out.println("====");
mySingleList.clear();
mySingleList.display();
System.out.println("====");
}
}
//输出结果
====
12 23 34 45
====
56 45 34 23 12 12 23 34 45
====
56 45 34 23 12 12 23 34 45 12 23 34 45 56 67
====
9999 56 45 34 23 12 12 23 34 45 12 23 34 45 56 67
====
9999 56 45 34 12 12 23 34 45 12 23 34 45 56 67
====
9999 56 45 34 12 12 34 45 12 34 45 56 67
====
====
3. 链表面试题
-
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode removeElements(ListNode head, int val) { if (head == null) { return null; } ListNode prev = head; ListNode cur = head.next; while (cur != null) { if (cur.val == val) { prev.next = cur.next; cur = cur.next; }else { prev = cur; cur = cur.next; } } //除了头结点都删除完成了 if (head.val == val) { head = head.next; } return head; } }
-
反转一个单链表OJ链接
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode reverseList(ListNode head) { if (head == null){ return head; } if (head.next == null){ return head; } ListNode cur = head.next; head.next = null; while (cur != null) { ListNode curNext = cur.next; cur.next = head; head = cur; cur = curNext; } return head; } }
-
给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点链表的中间结点
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode middleNode(ListNode head) { ListNode fast = head; ListNode slow = head; while (fast != null && fast.next != null) { fast = fast.next.next; slow = slow.next; } return slow; } }
-
输入一个链表,输出该链表中倒数第k个结点链表中倒数第k个结点
import java.util.*; /* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } }*/ public class Solution { public ListNode FindKthToTail(ListNode head, int k) { if (k<=0) { return null; } if (head == null) { return null; } ListNode fast = head; ListNode slow = head; for (int i = 0; i < k - 1; i++){ if(fast.next != null){ fast = fast.next; }else { return null; } } while(fast.next != null){ fast = fast.next; slow = slow.next; } return slow; } }
-
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的合并两个有序链表
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode mergeTwoLists(ListNode list1, ListNode list2) { ListNode newHead = new ListNode(-1); ListNode tmp = newHead; ListNode cur1 = list1; ListNode cur2 = list2; while (cur1 != null && cur2 != null) { if ( cur1.val < cur2.val) { tmp.next = cur1; cur1 = cur1.next; tmp = tmp.next; }else { tmp.next = cur2; cur2 = cur2.next; tmp = tmp.next; } } if(cur1 != null){ tmp.next = cur1; } if(cur2 != null){ tmp.next = cur2; } return newHead.next; } }
-
编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 链表分割
import java.util.*; /* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } }*/ public class Partition { public ListNode partition(ListNode pHead, int x) { if (pHead == null) { return null; } ListNode cur = pHead; ListNode bs = null; ListNode be = null; ListNode as = null; ListNode ae = null; while (cur!= null) { if (cur.val < x) { if (bs == null) { bs = cur; be = cur; }else { be.next = cur; be = be.next; } }else { if (as == null) { as = cur; ae = cur; }else { ae.next = cur; ae = ae.next; } } cur = cur.next; } if (bs == null){ return as; } be.next = as; if (as != null) { ae.next = null; } return bs; } }
-
import java.util.*; /* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } }*/ public class PalindromeList { public boolean chkPalindrome(ListNode A) { if (A == null) { return true; } //1.找中间节点 ListNode fast = A; ListNode slow = A; while (fast != null && fast.next != null) { fast = fast.next.next; slow = slow.next; } //2.翻转 ListNode cur = slow.next; while (cur != null) { ListNode curNext = cur.next; cur.next = slow; slow = cur; cur = curNext; } //3.一个往前一个往后 while (slow != A) { if(slow.val != A.val){ return false; } //偶数情况 if (A.next == slow){ return true; } slow = slow.next; A = A.next; } return true; } }
-
输入两个链表,找出它们的第一个公共结点相交链表
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { //两个链表相交一定是Y字形 int lenA = 0; int lenB = 0; ListNode pl = headA;//pl永远指向最长的链表 ListNode ps = headB;//ps永远指向最短的链表 //1.分别求链表长度 while (pl != null) { lenA ++; pl = pl.next; } while (ps != null) { lenB ++; ps = ps.next; } pl = headA; ps = headB; int len = lenA - lenB; if(len < 0) { pl = headB; ps = headA; len = lenB - lenA; } //保证pl永远指向最长的链表,ps永远指向最短的链表 //2.让pl先走len步 while(len != 0) { pl = pl.next; len--; } //保证pl,ps将来走到相遇节点的路程是一样的 while(pl!=null && pl != ps) { pl = pl.next; ps = ps.next; } if (pl == ps && pl == null){ return null; } return pl; } }
-
给定一个链表,判断链表中是否有环 OJ链接
/** * Definition for singly-linked list. * class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { public boolean hasCycle(ListNode head) { if (head == null || head.next == null) { return false; } ListNode slow = head; ListNode fast = head.next; while (slow != fast) { if (fast == null || fast.next == null) { return false; } slow = slow.next; fast = fast.next.next; } return true; } }
【思路】
快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表起始位置开始运行,如果链表带环则一定会在环中相遇,否则快指针率先走到链表的末尾
【扩展问题】
-
为什么快指针每次走两步,慢指针走一步可以?
假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当慢指针刚进环时,可能就和快指针相遇了,最差情况下两个指针之间的距离刚好就是环的长度。此时,两个指针每移动一次,之间的距离就缩小一步,不会出现每次刚好是套圈的情况,因此:在慢指针走到一圈之前,快指针肯定是可以追上慢指针的,即相遇
-
快指针一次走3步,走4步,…n步行吗?
-
假设:快指针每次走3步,满指针每次走一步,此时快指针肯定先进环,慢指针后来才进环。假设慢指针进环时候,快指针的位置如图所示:
此时按照上述方法来绕环移动,每次快指针走3步,慢指针走1步,是永远不会相遇的,快指针刚好将慢指针套圈了,因此不行。只有快指针走2步,慢指针走一步才可以,因为换的最小长度是1,即使套圈了两个也在相同的位置。
-
-
4. LinkedList的模拟实现
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 display();
public void clear();
}
public class IndexException extends RuntimeException{
public IndexException() {
}
public IndexException(String message) {
super(message);
}
}
public class MyLinkedList implements IList {
static class ListNode {
public int val;
public ListNode next;
public ListNode prev;
public ListNode(int val) {
this.val = val;
}
}
public ListNode head;
public ListNode last;
@Override
public void addFirst(int data) {
ListNode node = new ListNode(data);
if (head ==null){
head = node;
last = node;
}else {
node.next = head;
head.prev = node;
head = node;
}
}
@Override
public void addLast(int data) {
ListNode node = new ListNode(data);
if (head == null){
head = node;
last = node;
}else {
last.next = node;
node.prev = last;
last = node;
}
}
@Override
public void addIndex(int index, int data) {
if (index < 0 || index > size()) {
throw new IndexException("双向链表插入不合法" + index);
}
if (index == 0){
addFirst(data);
return;
}
if (index == size()){
addLast(data);
return;
}
ListNode cur = findIndex(index);
ListNode node = new ListNode(data);
node.next = cur;
node.prev = cur.prev;
cur.prev.next = node;
cur.prev = node;
}
private ListNode findIndex(int index) {
ListNode cur = head;
while(index != 0) {
cur = cur.next;
index--;
}
return cur;
}
@Override
public boolean contains(int key) {
ListNode cur = head;
while(cur!=null) {
if (cur.val==key){
return true;
}
cur = cur.next;
}
return false;
}
@Override
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 {
//只有一个节点 且是需要删除的节点
last = null;
}
}else {
cur.prev.next = cur.next;
//删除中间节点
if(cur.next != null) {
cur.next.prev = cur.prev;
//cur.prev.next = cur.next;
}else {
//删除尾巴节点
//cur.prev.next = cur.next;
last = last.prev;
}
}
return;
}
cur = cur.next;
}
}
@Override
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 {
//只有一个节点,且是需要删除的节点
last = null;
}
}else {
cur.prev.next = cur.next;
//删除中间节点
if (cur.next != null){
cur.next.prev = cur.prev;
}else {
//删除尾巴节点
last = last.prev;
}
}
}
cur = cur.next;
}
}
@Override
public int size() {
ListNode cur = head;
int count = 0;
while(cur!=null) {
count++;
cur = cur.next;
}
return count;
}
@Override
public void display() {
ListNode cur = head;
while(cur!=null) {
System.out.print(cur.val+" ");
cur = cur.next;
}
System.out.println();
}
@Override
public void clear() {
ListNode cur = head;
while (cur != null) {
ListNode curNext = cur.next;
cur.next = null;
cur.prev = null;
cur = curNext;
}
this.head = null;
this.last = null;
}
}
import java.util.LinkedList;
public class Test {
public static void main(String[] args) {
LinkedList<Integer> linkedList = new LinkedList<>();
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addFirst(1);
myLinkedList.remove(1);
myLinkedList.display();
myLinkedList.addFirst(12);
myLinkedList.addFirst(23);
myLinkedList.addFirst(34);
myLinkedList.addFirst(45);
myLinkedList.addFirst(56);
myLinkedList.addFirst(67);
myLinkedList.display();
myLinkedList.addLast(12);
myLinkedList.addLast(23);
myLinkedList.addLast(34);
myLinkedList.addLast(45);
myLinkedList.addLast(56);
myLinkedList.addLast(67);
myLinkedList.display();
myLinkedList.addIndex(2,99);
myLinkedList.display();
myLinkedList.remove(99);
myLinkedList.display();
myLinkedList.remove(67);
myLinkedList.display();
myLinkedList.remove(67);
myLinkedList.display();
myLinkedList.removeAllKey(23);
myLinkedList.display();
myLinkedList.clear();
myLinkedList.display();
}
}
67 56 45 34 23 12
67 56 45 34 23 12 12 23 34 45 56 67
67 56 99 45 34 23 12 12 23 34 45 56 67
67 56 45 34 23 12 12 23 34 45 56 67
56 45 34 23 12 12 23 34 45 56 67
56 45 34 23 12 12 23 34 45 56
56 45 34 12 12 34 45 56
Process finished with exit code 0
5.LinkedList的使用
5.1 什么是LinkedList
LinkedList的底层是双向链表结构(链表后面介绍),由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高
在集合框架中,LinkedList也实现了List接口,具体如下
【说明】
-
LinkedList实现了List接口
-
LinkedList的底层使用了双向链表
-
LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
-
LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)
-
LinkedList比较适合任意位置插入的场景
5.2 LinkedList的使用
-
LinkedList的构造
方法 解释 LinkedList() 无参构造 public LinkedList(Collection<? extends E> c) 使用其他集合容器中元素构造List 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); }
-
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 subList(int fromIndex, int toIndex) 截取部分 list public static void main(String[] args) { LinkedList<Integer> list = new LinkedList<>(); list.add(1); // add(elem): 表示尾插 list.add(2); list.add(3); list.add(4); list.add(5); list.add(6); list.add(7); System.out.println(list.size()); System.out.println(list); // 在起始位置插入0 list.add(0, 0); // add(index, elem): 在index位置插入元素elem System.out.println(list); list.remove(); // remove(): 删除第一个元素,内部调用的是removeFirst() list.removeFirst(); // removeFirst(): 删除第一个元素 list.removeLast(); // removeLast(): 删除最后元素 list.remove(1); // remove(index): 删除index位置的元素 System.out.println(list); // contains(elem): 检测elem元素是否存在,如果存在返回true,否则返回false if(!list.contains(1)){ list.add(0, 1); } list.add(1); System.out.println(list); System.out.println(list.indexOf(1)); // indexOf(elem): 从前往后找到第一个elem的位置 System.out.println(list.lastIndexOf(1)); // lastIndexOf(elem): 从后往前找第一个1的位置 int elem = list.get(0); // get(index): 获取指定位置元素 list.set(0, 100); // set(index, elem): 将index位置的元素设置为elem System.out.println(list); // subList(from, to): 用list中[from, to)之间的元素构造一个新的LinkedList返回 List<Integer> copy = list.subList(0, 3); System.out.println(list); System.out.println(copy); list.clear(); // 将list中元素清空 System.out.println(list.size()); }
-
LinkedList的遍历
public static void main(String[] args) { LinkedList<Integer> list = new LinkedList<>(); list.add(1); // add(elem): 表示尾插 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(); // 使用迭代器遍历---正向遍历 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(); }
6. ArrayList和LinkedList的区别
- 数组和链表的区别
- 顺序表和链表的区别
从插入、删除、查找、修改来说
不同点 | ArrayList | LinkedList |
---|---|---|
存储空间上 | 物理上一定连续 | 逻辑上连续,但物理上不一定连续 |
随机访问 | 支持O(1) | 不支持:O(N) |
头插 | 需要搬移元素,效率低O(N) | 只需修改引用的指向,时间复杂度为O(1) |
插入 | 空间不够时需要扩容 | 没有容量的概念 |
应用场景 | 元素高效存储+频繁访问 | 任意位置插入和删除频繁 |