前言:
顺序表和链表是属于数据结构中比较基础的知识,我们需要对其进行掌握。在JAVA原生标准库中分别为ArrayList和LinkedList。下图是整个数据结构之间的结构框图
1.ArrayList
背后用来存储数据的是一个数组,所以用ArrayList来进行相关操作时都是对该数组进行操作。ArrayList是一个线性表并且ArrayList实现了List接口。
(1)构造方法:
主要就这两种:
可以看到第一种构造方法,并没有指定顺序表的大小,在源码中亦是如此:
我们平时一般都是直接用的ArrayList的无参构造方法,在使用add方法的时候又是正确的,这是为什么?虽然在这里没有进行顺序表大小的分配,但是在add操作时会进行大小的分配:
可以看到当s == elementData.length时,elementData进行了扩容,然后就可以往里添加元素了。
(2)操作:
解释:
add操作:
ps1:对顺序表进行添加元素,必然在添加到某个时候,就满了(因为背后就是一个数组),下图是add中扩容的原理(grow方法)。
通过grow方法,当数组放满了,会对数组进行1.5倍的扩容 。
addAll操作:
?代表通配符,传入的参数c是否为E的子类或者本身,然后就是c必须实现Collection接口,否则这个方法不能使用。
(3)OJ题:
杨辉三角:
题析:题目告诉我们最终要返回的是一个二维的顺序表(二维数组),所以这个题要使用顺序表来做。首先我们从第一行看,只有一个元素1,与其他行都与众不同,所以这一行(也是一个一维的顺序表)直接放入二维顺序表中。然后观察后面所有行,最前面都有一个元素1,最后面都有一个元素1,中间的元素是由上一行元素和上一行元素的前一个元素相加得来,此时这个题就迎刃而解了。
题解:
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> lists = new ArrayList<>();
if(numRows == 0) {
return lists;
}
List<Integer> list0 = new ArrayList<>();
list0.add(1);
lists.add(list0);
//从第一行开始放
for(int i = 1; i < numRows; i++) {
List<Integer> list = new ArrayList<>();
//开头
list.add(1);
//中间
for(int j = 1; j < i; j++) {
int value1 = lists.get(i-1).get(j);
int value2 = lists.get(i-1).get(j-1);
list.add(value1+value2);
}
//结尾
list.add(1);
lists.add(list);
}
return lists;
}
2.LinkedList
(1)链表结构:
链表结构有很多种:带头/不带头,单向/双向,循环/非循环,而JAVA标准库中的LinkedList是不带头双向非循环的链表。链表的背后是由很多节点构成的,这些节点又由数据域(存储元素)和指针域(存储下一个节点)构成,默认最后一个节点不指向任何一个节点,所以为最后一个节点的指针域为null。
(2)构造方法:
一般只使用无参构造的方法。它不需要分配大小,它的大小是动态变化的。
(3)操作:
解释:
addFirst操作:
它的时间复杂度是O(1),不论是双向还是单向链表。
addLast操作:
如果链表是单向的话,时间复杂度就是O(n)(n为链表的大小);如果是双向的话就是O(1)。这里LinkedList进行尾插的操作所需要的时间复杂度就是O(1)。
(4)OJ题:
题解:
public ListNode removeElements(ListNode head, int val) {
if(head == null) {
return head;
}
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;
}
}
//这句代码写在最前面就需要while进行,以防头节点是要移除的节点,下一个也是要移除的。
if(head.val == val) {
head = head.next;
}
return head;
}
2.反转一个单链表。
题析:
题解:
public ListNode reverseList(ListNode head) {
if(head == null) {
return null;
}
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;
}
3.返回中间节点。
题析:
题解:
public ListNode middleNode(ListNode head) {
if(head == null) {
return head;
}
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
4.链表的回文结构
题析:
题解:
public boolean chkPalindrome(ListNode A) {
if(A == null) {
return true;
}
ListNode fast = A;
ListNode slow = A;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
//开始翻转
ListNode cur = slow.next;
while(cur != null) {
ListNode curNext = cur.next;
cur.next = slow;
slow = cur;
cur = curNext;
}
while(slow != A) {
if(slow.val != A.val) {
return false;
}
//这种情况是偶数个元素的判断
if(A.next == slow) {
return true;
}
slow = slow.next;
A = A.next;
}
return true;
}
3. 顺序表与链表的对比
(1)如果某个背景下需要进行频繁的随机元素访问,那么选用顺序表
(2)如果要进行频繁的插入或者删除某个元素,那么选用链表。
(3)顺序表的大小是事先指定好了的,如果满了则进行扩容,而链表不需要指定大小,添加一个元素,链表的长度加1。