数据存储在“节点”中java
class Node { E e; Node next; }
链表与数组在添加元素方面有很大的不一样。数组在末尾添加元素很简单,而链表在头部添加元素很简单。缘由是:数组维护者size
,而链表维护者head
。原理以下:
![]()
public class LinkedList<E> { // 节点 private class Node { // 存储的元素 public E e; // 下一个节点 public Node next; public Node(E e, Node node) { this.e = e; this.next = node; } 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; } // 练习用:在链表index位置添加一个元素e public void add(E e, int index) { if (index < 0 || index > size) { throw new IllegalArgumentException("index is illegal"); } if (index == 0) { // 头部添加 addFirst(e); } else { // 插入 // 须要插入元素位置的上一个元素 Node prev = head; for (int i = 0; i < index - 1; i++) { // 让prev指向插入元素的前一个元素 prev = prev.next; } // Node node = new Node(e); // node.next = prev.next; // prev.next = node; // 上面三句话等价于 prev.next = new Node(e, prev.next); size++; } } // 在链表头部添加一个元素 public void addFirst(E e) { // Node node = new Node(e); // node.next = head; // head = node; // 上面三句话等价于 head = new Node(e, head); size++; } // 在链表尾部添加元素 public void addLast(E e) { add(e, size); } }
but
,有没有发现,上面的代码中有一个很不方便的地方,那就是咱们每次在add
操做的时候都会去作一次index
是否为0
的判断。
解决办法:
若是每次add
操做,不用去判断,而是直接添加就行了。咱们能够增长一个虚拟头节点!这个节点什么都不作,仅仅是head
以前的那个节点。(是否是和循环队列咱们故意浪费一个空间有点相似?)node
public class LinkedList<E> { // 节点 private class Node { // 存储的元素 public E e; // 下一个节点 public Node next; public Node(E e, Node node) { this.e = e; this.next = node; } 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; } // 练习用:在链表index位置添加一个元素e public void add(E e, int index) { if (index < 0 || index > size) { throw new IllegalArgumentException("index is illegal"); } // 须要插入元素位置的上一个元素 Node prev = dummyHead; for (int i = 0; i < index; i++) { // 让prev指向插入元素的前一个元素 prev = prev.next; } prev.next = new Node(e, prev.next); size++; } // 在链表头部添加一个元素 public void addFirst(E e) { add(e, 0); } // 在链表尾部添加元素 public void addLast(E e) { add(e, size); } }
修改:数组
// 练习用:在index位置上设置元素的值为e public void set(int index, E e) { if (index < 0 || index > size) { throw new IllegalArgumentException("set failed, index is illegal"); } Node cur = dummyHead.next; for (int i = 0; i < index; i++) { cur = cur.next; } cur.e = 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; }
查询数据结构
// 练习用:获取index位置的元素 public E get(int index) { if (index < 0 || index > size) { throw new IllegalArgumentException("get failed, index is illegal"); } 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); } @Override public String toString() { StringBuilder res = new StringBuilder(); for (Node cur = dummyHead.next; cur != null; cur = cur.next) { res.append(cur.e + "->"); } res.append("NULL"); return res.toString(); }
// 练习用:删除index位置上的元素 public E remove(int index) { if (index < 0 || index > size) { throw new IllegalArgumentException("remove failed, index is illegal"); } // 要删除节点的上一个节点 Node prev = dummyHead; for (int i = 0; i < index; i++) { prev = prev.next; } Node delNode = prev.next; prev.next = delNode.next; delNode.next = null; size--; return delNode.e; } // 删除第一个元素 public E removeFirst() { return remove(0); } // 删除最后一个元素 public E removeLast() { return remove(size - 1); }
添加操做app
addLast(e)
:O(n)
addFirst(e)
:O(1)
add(e, index)
:O(n/2) = O(n)
删除操做ide
removeLast(e)
:O(n)
removeFirst(e)
:O(1)
remove(e, index)
:O(n/2) = O(n)
修改操做ui
set(index, e)
:O(n)
查找操做this
get(index)
:O(n)
contains(e)
:O(n)
综上:spa
操做 | 复杂度 |
---|---|
增 | O(n) |
删 | O(n) |
改 | O(n) |
查 | O(n) |
链表的效率那么低,咱们为何还要用链表? 若是咱们只对链表头部进行增、删、查操做呢?没错O(1)!这就是咱们用链表的缘由。