引言:数据结构——链表
LinkedList 在线性表家族中,
对于数组、栈、队列来说,他们虽然实现了动态,但底层却还是静态的
但是对于链表来说,它是真正的动态 ,但失去了随机读取的功能
方法如下
代码如下:
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 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 "Node{" + "e=" + e + ", next=" + next + '}' ; } } private Node head; private int size; public LinkedList () { this .head = null ; this .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("无效的index" ); } if (index==0 ){ addFirst(e); }else { Node prev = head; for (int i=0 ; i < index - 1 ;i++){ prev = prev.next; } prev.next = new Node(e,prev.next); size++; } } public void addLast (E e) { add(size,e); } }
上述的代码并没有写完,因为在使用过程中,带有虚拟头结点 的链表是非常多的,所以我们下面来写使用带有虚拟头结点的链表
真正的代码如下:
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 package linkedList;import java.util.StringJoiner;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 "Node{" + "e=" + e + ", next=" + next + '}' ; } } private Node dummyHead; private int size; public LinkedList () { this .dummyHead = new Node(null ,null ); this .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("无效的index" ); } Node prev = dummyHead; for (int i=0 ; i < index ;i++){ prev = prev.next; } prev.next = new Node(e,prev.next); size++; } public void addFirst (E e) { add(0 ,e); size++; } public void addLast (E e) { add(size,e); } public E get (int index) { if (index<0 ||index>size){ throw new IllegalArgumentException("无效的index" ); } Node cur = dummyHead.next; for (int i=0 ;i<index;i++){ cur = cur.next; } return cur.e; } public E getFirst () { return get(0 ); } public E getLast () { return get(size); } public void set (int index , E e) { if (index<0 ||index>size){ throw new IllegalArgumentException("无效的index" ); } Node cur = dummyHead.next; for (int i=0 ;i<index;i++){ cur = cur.next; } cur.e = e; } public boolean contains (E e) { Node cur = dummyHead.next; while (cur!=null ){ if (cur.e.equals(e)){ return true ; } cur = cur.next; } return false ; } @Override public String toString () { StringJoiner sj = new StringJoiner("->" ,"LinkedList: dummyHead->[" ,"]->null" ); Node cur = dummyHead.next; while (cur!=null ){ sj.add(cur.e+"" ); cur = cur.next; } return sj.toString(); } public E remove (int index) { if (index<0 ||index>size){ throw new IllegalArgumentException("无效的index" ); } Node prev = dummyHead; for (int i = 0 ;i < index;i++){ prev = prev.next; } Node ret = prev.next; prev.next = prev.next.next; ret.next = null ; size--; return ret.e; } public E removeFirst () { return remove(0 ); } public E removeLast () { return remove(size-1 ); } }
复杂度分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 方法名 | 时间复杂度 | -------------------------------------- add() | O(n) | addFirst() | O(1) | addLast() | O(n) | -------------------------------------- remove() | O(n) | removeFirst() | O(1) | removeLast() | O(n) | -------------------------------------- get() | O(n) | isEmpty() | O(1) | contains() | O(n) | -------------------------------------- set() | O(n) | 注意:O(n/2) = O(n)
由此可见,除了少数对头操作的方法外,几乎所有的方法,时间复杂度都是O(n)
剖析虚拟头结点的作用 LeetCode
中有一道题
删除链表中等于给定值 val 的所有节点。
1 2 3 4 5 6 7 8 9 10 11 12 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ 示例: 输入: 1->2->6->3->4->5->6, val = 6 输出: 1->2->3->4->5
这里有两种不同的解决办法
不用虚拟头结点 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 class Solution { public ListNode removeElements (ListNode head, int val) { while (head!=null && head.val==val){ ListNode del = head; head = head.next; del.next = null ; } if (head==null ){ return null ; } ListNode prev = head; while (prev.next!=null ){ if (prev.next.val == val){ ListNode delNode = prev.next; prev.next = prev.next.next; delNode.next = null ; } else prev = prev.next; } return head; } }
使用虚拟头结点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public ListNode removeElements (ListNode head, int val) { ListNode dummyHead = new ListNode(0 ); dummyHead.next = head; ListNode prev = dummyHead; while (prev.next!=null ){ if (prev.next.val == val){ ListNode delNode = prev.next; prev.next = prev.next.next; delNode.next = null ; } else prev = prev.next; } return dummyHead.next; } }
区别 使用虚拟头结点,可以不用再讨论head的特殊性,因为此时所有的结点都有一个前置的结点
返回的时候返回虚拟头结点的next即可
所以我们可以有如下总结
如果只是操作头结点,则我们可以不使用虚拟头结点
如果要对中间的结点进行操作,使用虚拟头结点可以避免分类讨论
两者可以互换,但要注意条件
参考资料
参考资料:来自慕课liuyubobobo的教程