前言:
顺序表和链表是属于数据结构中比较基础的知识,我们需要对其进行掌握。在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题:
1.删除链表中等于给定值 val 的所有节点
题解:
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。