什么是链表?

动态数组有个明显缺点,可能造成内存空间的大量浪费。
能否实现用到多少内存就申请多少内存?链表可以实现这一点。
链表是一种链式存储的线性表,所有元素的内存地址不一定是连续的(是物理存储单元非连续、非顺序的存储结构)。
image.png

单向链表

只有next指针。

  1. private static class Node<E> {
  2. E element;
  3. Node next;
  4. public Node(E element, Node next) {
  5. this.element = element;
  6. this.next = next;
  7. }
  8. }

查询

  1. /**
  2. * 获取指定index位置的前一个元素
  3. * 最好O(1),获取第一个元素
  4. * 最坏O(n),获取最后一个元素
  5. * 平均O(n)
  6. * @param element
  7. */
  8. private Node getPreviousNode(int index) {
  9. Node prev = head;
  10. for (int i = 1; i < index; i++) {
  11. prev = prev.next;
  12. }
  13. return prev;
  14. }
  15. public E get(int index) {
  16. rangCheck(index);
  17. if (index == 0) {
  18. return head.element;
  19. }
  20. Node prev = getPreviousNode(index);
  21. return (E) prev.next.element;
  22. }

增加

  1. /**
  2. * 最好O(1),添加第一个元素
  3. * 最坏O(n),添加最后一个元素
  4. * 平均O(n)
  5. * @param element
  6. */
  7. public void add(int index, E element) {
  8. rangCheckForAdd(index);
  9. // 创建当前element节点 curNode
  10. // 找到 (index-1) 位置的节点 prev
  11. // 得到 index位置的节点(indexOldNode=prev.next)
  12. // 改变 prev.next 指向为新创建的curNode
  13. // curNode.next 指向 indexOldNode
  14. //index 0 1 2 3 4 5 6
  15. Node prev = getPreviousNode(index);
  16. prev.next = new Node(element, prev.next);
  17. size++;
  18. }

删除

  1. /**
  2. * 最好O(1),删除第一个元素
  3. * 最坏O(n),删除最后一个元素
  4. * 平均O(n)
  5. * @param index
  6. * @return
  7. */
  8. public E remove(int index) {
  9. rangCheck(index);
  10. if (index == 0) {
  11. E delValue = head.element;
  12. head = head.next;
  13. size--;
  14. return delValue;
  15. }
  16. Node prev = getPreviousNode(index);
  17. Node delNode = prev.next;
  18. E delValue = (E) delNode.element;
  19. prev.next = delNode.next;
  20. size--;
  21. return delValue;
  22. }

设置

  1. /**
  2. * 最好O(1),设置第一个元素
  3. * 最坏O(n),设置最后一个元素
  4. * 平均O(n)
  5. * @param index
  6. * @return
  7. */
  8. public E set(int index, E element) {
  9. rangCheck(index);
  10. if (index == 0) {
  11. E oldValue = head.element;
  12. head = new Node(element, head.next);
  13. return oldValue;
  14. }
  15. Node prev = getPreviousNode(index);
  16. Node<E> indexNode = prev.next;
  17. prev.next = new Node(element, indexNode.next);
  18. return indexNode.element;
  19. }

动态数组和链表的复杂度


动态数组 链表

最好 最坏 平均 最好 最坏 平均
add(int index, E element) O(1) O(n) O(n) O(1) O(n) O(n)
remove(int index) O(1) O(n) O(n) O(1) O(n) O(n)
set(int index, E element) O(1) O(1) O(1) O1) O(n) O(n)
get(int index) O(1) O(1) O(1) O(1) O(n) O(n)

资料上面说,链表的增加、删除的复杂度是O(1),实际上是指增加、删除那一瞬间的操作(移动指针的动作是O(1)),但是在增删动作之前还是要寻找元素的位置,寻找的过程平均是O(n),那么增加、删除整体完成就是O(n)。
**

双向链表

image.png

  1. public class CustLinkedList<E> extends AbstractCustList<E> {
  2. private Node<E> head;
  3. private Node<E> tail;
  4. public CustLinkedList() {
  5. }
  6. private static class Node<E> {
  7. E element;
  8. Node prev;
  9. Node next;
  10. public Node(Node prev, E element, Node next) {
  11. this.prev = prev;
  12. this.element = element;
  13. this.next = next;
  14. }
  15. }
  16. }

只有一个元素

image.png

查询

  1. public E get(int index) {
  2. if (index == 0) {
  3. return head.element;
  4. } else if (index == size - 1) {
  5. return tail.element;
  6. } else {
  7. return getNode(index).element;
  8. }
  9. }
  10. private Node<E> getNode(int index) {
  11. rangCheck(index);
  12. if (index < (size >> 1)) {
  13. //从头部开始查找
  14. return getNodeFormHead(index);
  15. } else {
  16. //从尾部查找
  17. return getNodeFormTail(index);
  18. }
  19. }
  20. private Node<E> getNodeFormHead(int index) {
  21. // 0 1 2 3
  22. // 1->2->3->4
  23. //index=3 , i=0 ,node =2
  24. //index=3 , i=1 ,node =3
  25. //index=3 , i=2 ,node =4
  26. Node<E> node = head;
  27. for (int i = 0; i < index; i++) {
  28. node = node.next;
  29. }
  30. return node;
  31. }
  32. private Node<E> getNodeFormTail(int index) {
  33. // 0 1 2 3
  34. // 1->2->3->4
  35. //index=1 , i=3 ,node =3
  36. //index=1 , i=2 ,node =2
  37. Node<E> node = tail;
  38. for (int i = size - 1; i > index; i--) {
  39. node = node.prev;
  40. }
  41. return node;
  42. }

添加

  1. public void add(E element) {
  2. addLast(element);
  3. }
  4. public void add(int index, E element) {
  5. rangCheckForAdd(index);
  6. if (index == 0) {
  7. addFirst(element);
  8. } else if (index == size) {
  9. addLast(element);
  10. } else {
  11. // 0 1 2 3
  12. // 1->2->3->4 在 index=2的位置插入 100
  13. // 1->2->100->3->4 在 index=2的位置插入 100
  14. // 找到原位置的节点
  15. Node<E> oldNode = getNode(index);
  16. Node prev = oldNode.prev;
  17. Node newNode = new Node(prev, element, oldNode);
  18. prev.next = newNode;
  19. oldNode.prev = newNode;
  20. size++;
  21. }
  22. }
  23. public void addFirst(E element) {
  24. Node first = head;
  25. Node<E> newNode = new Node<>(null, element, first);
  26. head = newNode;
  27. if (first == null) {
  28. tail = newNode;
  29. } else {
  30. first.prev = newNode;
  31. }
  32. size++;
  33. }
  34. public void addLast(E element) {
  35. Node last = tail;
  36. Node<E> newNode = new Node<>(last, element, null);
  37. tail = newNode;
  38. if (last == null) {
  39. head = newNode;
  40. } else {
  41. last.next = newNode;
  42. }
  43. size++;
  44. }

删除

  1. public E remove(int index) {
  2. Node<E> oldNode = getNode(index);
  3. E element = oldNode.element;
  4. Node prev = oldNode.prev;
  5. Node next = oldNode.next;
  6. if (prev == null) {
  7. head = next;
  8. } else {
  9. prev.next = next;
  10. oldNode.prev = null;
  11. }
  12. if (next == null) {
  13. tail = prev;
  14. } else {
  15. next.prev = prev;
  16. oldNode.next = null;
  17. }
  18. oldNode.element = null;
  19. size--;
  20. return element;
  21. }

设置

  1. public E set(int index, E element) {
  2. Node<E> oldNode = getNode(index);
  3. oldNode.element = element;
  4. return oldNode.element;
  5. }

双向链表 VS 单向链表

image.png

双向链表 VS 动态数组

  • 动态数组:开辟、销毁内存空间的次数相对较少,但可能造成内存空间的浪费(可以通过缩容来解决)。
  • 双向链表:开辟、销毁内存空间的次数相对较多,但是不会造成内存空间的浪费。
  • 如果频繁在尾部进行增加、删除操作,动态数组双向链表都可以
  • 如果频繁在头部进行添加、删除操作,建议使用双向链表
  • 如果频繁在任意位置进行添加、删除操作,建议使用双向链表
  • 如果频繁在任意位置进行查询,建议使用动态数组

有了双向链表,单向链表是否没用了?

  • 并不是,在哈希表中使用的就是单向链表。

单向循环链表

image.png

只有一个节点

image.png

双向循环链表

image.png

只有一个节点

image.png

静态链表

image.png