线性数据结构

为什么链表很重要
- 真正的动态数据结构
- 最简单的动态数据结构
- 更深入的理解引用(或者指针)
- 更深入的理解递归
- 辅助组成其他数据结构
链表 Linked List
数组和链表的对比
- 数组最好用于索引有语义的情况。scores[2]
- 最大的优点:支持快速查询
- 链表不适合用于索引有语意的情况
- 最大的优点:动态
链表实现
节点的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
private class Node {
public E e;
public Node next;
public Node(E e, Node next) { this.e = e; this.next = next; }
public Node(E e) { this(e, null); }
public Node() { this(null, null); }
@Override public String toString() { return e.toString(); } }
|
链表中添加元素

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
| package demo.structure.linked;
public class LinkedList<E> {
private class Node {
public E e;
public Node next;
public Node(E e, Node next) { this.e = e; this.next = next; }
public Node(E e) { this(e, null); }
public Node() { this(null, null); }
@Override public String toString() { return e.toString(); } }
private Node head;
private int size;
public LinkedList() { head = null; size = 0; }
public int getSize() { return size; }
public boolean isEmpty() { return size == 0; }
public void addFirst(E e) {
head = new Node(e, head); size++; }
public void add(int index, E e) { if (index < 0 || index > size) { throw new IllegalArgumentException("Add failed.Illegal index."); } if (index == 0) { addFirst(e); } else { Node previous = head; for (int i = 0; i < index; i++) { previous = previous.next; }
previous.next = new Node(e, previous.next); size++; } }
public void addLast(E e) { add(size, e); } }
|
DummyHead虚拟头节点实现链表
不存在的一个虚拟头节点,这个节点是没有意义的,只是为了实现逻辑方便


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
| package demo.structure.linked;
public class LinkedList<E> {
private class Node {
public E e;
public Node next;
public Node(E e, Node next) { this.e = e; this.next = next; }
public Node(E e) { this(e, null); }
public Node() { this(null, null); }
@Override public String toString() { return e.toString(); } }
private Node dummyHead;
private int size;
public LinkedList() { dummyHead = new Node(null, null); size = 0; }
public int getSize() { return size; }
public boolean isEmpty() { return size == 0; }
public void add(int index, E e) { if (index < 0 || index > size) { throw new IllegalArgumentException("Add failed.Illegal index."); } Node previous = dummyHead; for (int i = 0; i < index; i++) { previous = previous.next; } previous.next = new Node(e, previous.next); size++; }
public void addFirst(E e) { add(0, e); }
public void addLast(E e) { add(size, e); }
public E get(int index) { if (index < 0 || index > size) { throw new IllegalArgumentException("Get failed.Illegal index."); } Node current = dummyHead.next; for (int i = 0; i < index; i++) { current = current.next; } return current.e; }
public E getFirst() { return get(0); }
public E getLast() { return get(size - 1); }
public void set(int index, E e) { if (index < 0 || index > size) { throw new IllegalArgumentException("Set failed.Illegal index."); } Node current = dummyHead.next; for (int i = 0; i < index; i++) { current = current.next; } current.e = e; }
public boolean contains(E e) { Node current = dummyHead.next; while (current != null) { if (current.e.equals(e)) { return true; } current = current.next; } return false; }
public E remove(int index) { if (index < 0 || index > size) { throw new IllegalArgumentException("Remove failed.Illegal index."); } Node previous = dummyHead; for (int i = 0; i < index; i++) { previous = previous.next; } Node resultNode = previous.next; previous.next = resultNode.next; resultNode.next = null; size--; return resultNode.e; }
public E removeFirst() { return remove(0); }
public E removeLast() { return remove(size - 1); }
@Override public String toString() { StringBuilder res = new StringBuilder();
for (Node current = dummyHead.next; current != null; current = current.next) { res.append(current).append("->"); } res.append("NULL"); return res.toString(); } }
|
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package demo.structure.linked;
public class LinkedListMain { public static void main(String[] args) { LinkedList<Integer> linkedList = new LinkedList<>(); System.out.println("-------------链表添加--------------"); for (int i = 0; i < 5; i++) { linkedList.addFirst(i); System.out.println(linkedList); }
linkedList.add(2, 666); System.out.println(linkedList);
System.out.println("-------------链表删除--------------"); linkedList.remove(2); System.out.println(linkedList);
linkedList.removeFirst(); System.out.println(linkedList);
linkedList.removeLast(); System.out.println(linkedList); } }
|
执行结果

链表的时间复杂度分析
- 添加操作 – O(n)
- addLast(e) – O(n)
- addFirst(e) – O(1)
- add(index, e) – O(n/2)=O(n)
- 删除操作 – O(n)
- removeLast(e) – O(n)
- removeFirst(e) – O(1)
- remove(index, e) – O(n/2)=O(n)
- 修改操作 – O(n)
- 查找操作 – O(n)
- get(index) – O(n)
- contains(e) – O(n)
- 只查链表头的元素:O(1)
- 如果只对链表头进行操作:O(1)
链表实现栈
Interface Stack<E>
<—–implement—– LinkedListStack<E>
- void push(E)
- E pop()
- E peek()
- int getSize()
- boolean isEmpty()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| package demo.structure.stack;
import demo.structure.linked.LinkedList;
public class LinkedListStack<E> implements Stack<E> {
private LinkedList<E> list;
public LinkedListStack() { list = new LinkedList<>(); }
@Override public int getSize() { return list.getSize(); }
@Override public boolean isEmpty() { return list.isEmpty(); }
@Override public void push(E e) { list.addFirst(e); }
@Override public E pop() { return list.removeFirst(); }
@Override public E peek() { return list.getFirst(); }
@Override public String toString() { return "Stack: top " + list; } }
|
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package demo.structure.stack;
public class LinkedListStackMain {
public static void main(String[] args) { LinkedListStack<Integer> stack = new LinkedListStack<>(); for (int i = 0; i < 5; i++) { stack.push(i); System.out.println(stack); } System.out.println("------pop------"); stack.pop(); System.out.println(stack); } }
|
执行结果

数组实现的栈与链表实现的栈性能比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| package demo.structure.stack;
import java.util.Random;
public class Main {
private static double testStack(Stack<Integer> stack, int opCount) { long startTime = System.nanoTime(); Random random = new Random(); for (int i = 0; i < opCount; i++) { stack.push(random.nextInt(Integer.MAX_VALUE)); } for (int i = 0; i < opCount; i++) { stack.pop(); } long endTime = System.nanoTime(); return (endTime - startTime) / 1000000000.0; }
public static void main(String[] args) { int opCount = 1000000;
ArrayStack<Integer> arrayStack = new ArrayStack<>(); double time1 = testStack(arrayStack, opCount); System.out.println("ArrayStack,time:" + time1 + " s");
LinkedListStack<Integer> linkedListStack = new LinkedListStack<>(); double time2 = testStack(linkedListStack, opCount); System.out.println("LinkedListStack,time:" + time2 + " s");
} }
|
十万元素的执行结果

一百万元素的执行结果

从上面的对比看到,元素越多,链表栈耗时则非常多,因为LinkedListStack中包含更多的new操作,元素越多,则结果差异越大。
链表实现队列

- 改进列表
- head:头节点
- tail:尾节点
- 从两端出插入元素都是容易的
- 从tail删除元素不容易
- 从head端删除元素,从tail端插入元素,删除的这一端是队首,插入的这一端是队尾
- 只对链表两侧进行操作,不对链表中间进行操作
- 由于没有dummyHead,要注意链表为空的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
| package demo.structure.queue;
public class LinkedListQueue<E> implements Queue<E> {
private class Node {
public E e;
public Node next;
public Node(E e, Node next) { this.e = e; this.next = next; }
public Node(E e) { this(e, null); }
public Node() { this(null, null); }
@Override public String toString() { return e.toString(); } }
private Node head, tail;
private int size;
public LinkedListQueue() { head = null; tail = null; size = 0; }
@Override public int getSize() { return size; }
@Override public boolean isEmpty() { return size == 0; }
@Override public void enqueue(E e) { if (tail == null) { tail = new Node(e); head = tail; } else { tail.next = new Node(e); tail = tail.next; } size++; }
@Override public E dequeue() { if (isEmpty()) { throw new IllegalArgumentException("Cannot dequeue from an empty queue"); } Node resultNode = head; head = head.next; resultNode.next = null; if (head == null) { tail = null; } size--; return resultNode.e; }
@Override public E getFront() { if (isEmpty()) { throw new IllegalArgumentException("Queue is empty"); } return head.e; }
@Override public String toString() { StringBuilder res = new StringBuilder(); res.append("Queue: front "); Node current = head; while (current != null) { res.append(current).append("->"); current = current.next; } res.append("NULL tail"); return res.toString(); } }
|
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package demo.structure.queue;
public class LinkedListQueueMain {
public static void main(String[] args) { LinkedListQueue<Integer> queue = new LinkedListQueue<>(); for (int i = 0; i < 10; i++) { queue.enqueue(i); System.out.println(queue); if (i % 3 == 2) { queue.dequeue(); System.out.println(queue); } } } }
|
执行结果:

数组实现的线性队列和数组实现的循环队列和链表实现的线性队列性能对比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| package demo.structure.queue;
import java.util.Random;
public class Main {
public static void main(String[] args) { int opCount = 1000000; ArrayQueue<Integer> arrayQueue = new ArrayQueue<>(); double time1 = testQueue(arrayQueue, opCount); System.out.println("ArrayQueue, time:" + time1 + " s");
LoopQueue<Integer> loopQueue = new LoopQueue<>(); double time2 = testQueue(loopQueue, opCount); System.out.println("LoopQueue, time:" + time2 + " s");
LinkedListQueue<Integer> linkedListQueue = new LinkedListQueue<>(); double time3 = testQueue(linkedListQueue, opCount); System.out.println("LinkedListQueue, time:" + time3 + " s"); }
private static double testQueue(Queue<Integer> q, int opCount) { long startTime = System.nanoTime(); Random random = new Random(); for (int i = 0; i < opCount; i++) { q.enqueue(random.nextInt(Integer.MAX_VALUE)); } for (int i = 0; i < opCount; i++) { q.dequeue(); } long endTime = System.nanoTime(); return (endTime - startTime) / 1000000000.0; } }
|
一万元素的执行结果

十万元素的执行结果

链表性能问题
我们在学习Java基础集合中,在我们学到数组和链表的差异时,都是如下的结论:
- 数组是一种查询快、增删慢的模型
- 链表是一种增删快、查询慢的模型
但是从上介绍的动态数组实现栈和链表实现栈测试的结果来看,分析如下:
虽然猛地看上去,如果我们只在链表头添加元素,时间复杂度是O(1)的。同时,因为使用链表不需要resize,所以凭直觉,链表的性能应该更好。
但实际上,当数据量达到一定程度,链表的性能是更差的。
这是因为,对于链表来说,每天添加一个元素都需要重新创建一个Node类的对象,也就是都需要进行一次new的内存操作。而对内存的操作,是非常慢的。
从在“数组实现的栈与链表实现的栈性能比较”中测试的结果,明显的看出,使用链表慢于动态数组。
那么,为什么即使有resize,对于大规模数据,动态数组还是会快于链表?
因为对于动态数组来说,一方面,每次热size容量倍增,这将使得,对于大规模数据,实际上触发resize的次数是非常少的。更重要的是,resize的过程,试一次申请一大片内存空间。但是对于链表来说,每次只是申请一个空间。
申请一次10万的空间,是远快于申请10万次1个的空间的。而相较于堆内存空间的操作,动态数组的热size过程虽然还需要赋值,把旧数组的元素拷贝给新数组。但这个拷贝过程,是远远快于对内存的操作。
反转链表
LeetCode206题,Reverse Linked List 反转一个链表

节点不动,只需要改变指针的指向

递归的方式实现链表的反转

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| package demo.structure.linked;
public class ReverseLinkedList {
public ListNode reverseList(ListNode head) { ListNode pre = null; ListNode cur = head; while (cur != null) { ListNode next = cur.next; cur.next = pre; pre = cur; cur = next; } return pre; }
public ListNode reverseListR(ListNode head) { if (head == null || head.next == null) { return head; } ListNode reverseListNode = reverseListR(head.next); head.next.next = head; head.next = null; return reverseListNode; } }
|