概念

链表是线性数据结构,是最简单的真正的动态数据结构.

特点

链表的数据存储在节点(node)中.

  • 优点:真正的动态,不需要处理固定容量问题
  • 缺点:丧失了随机访问的能力

    数组和链表对比

    数组

    数组最好用于索引有语意的情况
    最大的优点: 支持快速查询

    链表

    链表不适合用于索引有语意的情况
    最大的优点: 动态

    链表的实现

    ```java package cn.wllsrx.zoe.java.linkedlist;

/**

  • 增删改查时间复杂度都为O(n) *
  • 增删查只对链表头进行操作时间复杂度为O(1)
  • @author zoe **/ public class LinkedList {

    private class Node {

    1. public E e;
    2. public Node next;
    3. public Node(E e, Node next) {
    4. this.e = e;
    5. this.next = next;
    6. }
    7. public Node(E e) {
    8. this(e, null);
    9. }
    10. public Node() {
    11. this(null, null);
    12. }
    13. @Override
    14. public String toString() {
    15. return e.toString();
    16. }

    }

    /**

    • 虚拟头节点 */ private Node dummyHead; private int size;

      public LinkedList() { dummyHead = new Node(null, null); size = 0; }

      /**

    • 获取链表中的元素个数 *
    • @return 个数 */ public int getSize() { return size; }

      /**

    • 判断链表是否为空 *
    • @return 为空true 反之false */ public boolean isEmpty() { return size == 0; }

      /**

    • 在链表index位置添加元素 时间复杂度O(n) *
    • @param index 索引0开始
    • @param e 元素 */ public void add(int index, E e) { if (index < 0 || index > size) {

      1. throw new IllegalArgumentException("Add failed. illegal index");

      } //dummyHead是索引0前面的一个元素 Node prev = dummyHead; //遍历index前一个位置的元素 for (int i = 0; i < index; i++) {

      1. prev = prev.next;

      } prev.next = new Node(e, prev.next); size++;

      }

      /**

    • 在链表的头添加新的元素e 时间复杂度O(n) *
    • @param e 元素 */ public void addFirst(E e) { add(0, e); }

      /**

    • 在链表末尾添加元素 时间复杂度O(1) *
    • @param e 元素 */ public void addLast(E e) { add(size, e); }

      /**

    • 获取索引位置的元素 时间复杂度O(n) *
    • @param index 索引
    • @return 元素 */ public E get(int index) { //检查索引下标 this.checkIndex(index); //获取索引所在位置的元素,dummyHead.next是链表的第一个元素 Node cur = dummyHead.next; for (int i = 0; i < index; i++) {

      1. cur = cur.next;

      } return cur.e; }

      /**

    • 获取链表第一个元素 *
    • @return E */ public E getFirst() { return get(0); }

      /**

    • 获取链表最后一个元素 *
    • @return E */ public E getLast() { return get(size - 1); }

      /**

    • 修改索引index位置的元素为e 时间复杂度O(n) *
    • @param index 索引
    • @param e 元素 */ public void set(int index, E e) { this.checkIndex(index); Node cur = dummyHead.next; for (int i = 0; i < index; i++) {

      1. cur = cur.next;

      } cur.e = e; }

      /**

    • 删除索引位置的元素 时间复杂度O(n) *
    • @param index 索引
    • @return 被删除元素 */ public E remove(int index) { this.checkIndex(index); //索引位置的前一个元素 Node pre = dummyHead; for (int i = 0; i < index; i++) {

      1. pre = pre.next;

      } //被删除索引位置的元素 Node del = pre.next; pre.next = del.next; del.next = null; size—; return del.e; }

      /**

    • 删除链表第一个元素 时间复杂度O(1) *
    • @return 被删除元素 */ public E removeFirst() { return remove(0); }

      /**

    • 删除链表最后一个元素 时间复杂度O(n) *
    • @return 被删除元素 */ public E removeLast() { return remove(size - 1); }

      /**

    • 检查索引是否合法 *
    • @param index 索引 */ private void checkIndex(int index) { if (index < 0 || index > size) {

      1. throw new IllegalArgumentException("set failed. illegal index");

      } }

      /**

    • 判断元素是否存在 时间复杂度O(n) *
    • @param e 元素
    • @return 是否存在 */ public boolean contains(E e) { Node cur = dummyHead.next; while (cur != null) {
      1. if (cur.e.equals(e)) {
      2. return true;
      3. }
      4. cur = cur.next;
      } return false; }
  1. @Override
  2. public String toString() {
  3. StringBuilder builder = new StringBuilder();
  4. for (Node cur = dummyHead.next; cur != null; cur = cur.next) {
  5. builder.append(cur).append("->");
  6. }
  7. builder.append("null");
  8. return builder.toString();
  9. }
  10. public static void main(String[] args) {
  11. LinkedList<Integer> linkedList = new LinkedList<>();
  12. for (int i = 0; i < 6; i++) {
  13. linkedList.addFirst(i);
  14. System.out.println(linkedList);
  15. }
  16. linkedList.add(2, 888);
  17. System.out.println(linkedList);
  18. linkedList.remove(2);
  19. System.out.println(linkedList);
  20. linkedList.removeFirst();
  21. System.out.println(linkedList);
  22. linkedList.removeLast();
  23. System.out.println(linkedList);
  24. }

}

  1. <a name="3b94686a189018195ae9b7398eff1a73"></a>
  2. ## 使用链表实现栈
  3. <a name="cf8e8ae558b2d5517081b4777d961a68"></a>
  4. ### 栈接口
  5. ```java
  6. public interface Stack<E> {
  7. /**
  8. * 获取栈中元素个数
  9. *
  10. * 时间复杂度 O(1)
  11. * @return int
  12. */
  13. int getSize();
  14. /**
  15. * 判断栈是否为空
  16. *
  17. * 时间复杂度 O(1)
  18. * @return boolean
  19. */
  20. boolean isEmpty();
  21. /**
  22. * 在栈中添加元素e(入栈)
  23. *
  24. * 时间复杂度 O(1)均摊
  25. * @param e 元素
  26. */
  27. void push(E e);
  28. /**
  29. * 从栈中取出元素(出栈)
  30. *
  31. * 时间复杂度 O(1)均摊
  32. * @return E
  33. */
  34. E pop();
  35. /**
  36. * 查看栈顶元素
  37. *
  38. * 时间复杂度 O(1)
  39. * @return E
  40. */
  41. E peek();
  42. }

链表实现栈

  1. /**
  2. * 使用链表实现栈
  3. *
  4. * @author zoe
  5. **/
  6. public class LinkedListStack<E> implements Stack<E> {
  7. private final LinkedList<E> list;
  8. public LinkedListStack() {
  9. list = new LinkedList<>();
  10. }
  11. @Override
  12. public int getSize() {
  13. return list.getSize();
  14. }
  15. @Override
  16. public boolean isEmpty() {
  17. return list.isEmpty();
  18. }
  19. @Override
  20. public void push(E e) {
  21. list.addFirst(e);
  22. }
  23. @Override
  24. public E pop() {
  25. return list.removeFirst();
  26. }
  27. @Override
  28. public E peek() {
  29. return list.getFirst();
  30. }
  31. @Override
  32. public String toString() {
  33. return "Stack: top " +
  34. list;
  35. }
  36. public static void main(String[] args) {
  37. LinkedListStack<Integer> linkedListStack = new LinkedListStack<>();
  38. for (int i = 0; i < 7; i++) {
  39. linkedListStack.push(i);
  40. System.out.println(linkedListStack);
  41. }
  42. linkedListStack.pop();
  43. System.out.println(linkedListStack);
  44. }
  45. }

链表实现栈和数组实现栈的耗时比较

数组实现栈

  1. public class ArrayStack<E> implements Stack<E> {
  2. Array<E> array;
  3. public ArrayStack(int capacity) {
  4. array = new Array<>(capacity);
  5. }
  6. public ArrayStack() {
  7. array = new Array<>();
  8. }
  9. @Override
  10. public int getSize() {
  11. return array.getSize();
  12. }
  13. @Override
  14. public boolean isEmpty() {
  15. return array.isEmpty();
  16. }
  17. public int getCapacity() {
  18. return array.getCapacity();
  19. }
  20. @Override
  21. public void push(E e) {
  22. //在数组末尾添加元素
  23. array.addLast(e);
  24. }
  25. @Override
  26. public E pop() {
  27. return array.removeLast();
  28. }
  29. @Override
  30. public E peek() {
  31. return array.getLast();
  32. }
  33. @Override
  34. public String toString() {
  35. StringBuilder builder = new StringBuilder();
  36. builder.append("Stack: ");
  37. builder.append("[");
  38. for (int i = 0; i < array.getSize(); i++) {
  39. builder.append(array.get(i));
  40. if (i != array.getSize() - 1) {
  41. builder.append(",");
  42. }
  43. }
  44. builder.append("] top");
  45. return builder.toString();
  46. }
  47. public static void main(String[] args) {
  48. ArrayStack<Integer> stack = new ArrayStack<>();
  49. for (int i = 0; i < 5; i++) {
  50. //入栈
  51. stack.push(i);
  52. System.out.println(stack);
  53. }
  54. //出栈
  55. stack.pop();
  56. System.out.println(stack);
  57. }
  58. }

耗时对比

  1. public class TestStack {
  2. public static void main(String[] args) {
  3. int opCount = 100000;
  4. ArrayStack<Integer> arrayStack = new ArrayStack<>();
  5. System.out.println("ArrayStack, time: " + testStack(arrayStack, opCount) + "s");
  6. LinkedListStack<Integer> linkedListStack = new LinkedListStack<>();
  7. System.out.println("LinkedListStack, time: " + testStack(linkedListStack, opCount) + "s");
  8. }
  9. public static double testStack(Stack<Integer> stack, int opCount) {
  10. long startTime = System.nanoTime();
  11. SecureRandom random = new SecureRandom();
  12. for (int i = 0; i < opCount; i++) {
  13. stack.push(random.nextInt(Integer.MAX_VALUE));
  14. }
  15. for (int i = 0; i < opCount; i++) {
  16. stack.pop();
  17. }
  18. long endTime = System.nanoTime();
  19. return (endTime - startTime) / 1000000000.0;
  20. }
  21. }

使用链表实现队列

链表接口

  1. public interface Queue<E> {
  2. /**
  3. * 入队操作
  4. *
  5. * 时间复杂度 O(1) 均摊 ArrayQueue 和 LoopQueue同
  6. * @param e 元素
  7. */
  8. void enqueue(E e);
  9. /**
  10. * 出队操作
  11. *
  12. * 时间复杂度 O(n) ArrayQueue复杂度为O(n) LoopQueue复杂度O(1) 均摊
  13. * 出队拿出数组中第一个元素后,剩下的元素都要前移
  14. * @return 元素
  15. */
  16. E dequeue();
  17. /**
  18. * 查看队列队首的元素
  19. *
  20. * 时间复杂度 O(1) ArrayQueue 和 LoopQueue同
  21. * @return 元素
  22. */
  23. E getFront();
  24. /**
  25. * 获取队列的数量
  26. *
  27. * 时间复杂度 O(1) ArrayQueue 和 LoopQueue同
  28. * @return size
  29. */
  30. int getSize();
  31. /**
  32. * 判断队列是否为空
  33. *
  34. * 时间复杂度 O(1) ArrayQueue 和 LoopQueue同
  35. * @return true为空, false反之
  36. */
  37. boolean isEmpty();
  38. }

链表实现队列

  1. public class LinkedListQueue<E> implements Queue<E> {
  2. private class Node {
  3. public E e;
  4. public Node next;
  5. public Node(E e, Node next) {
  6. this.e = e;
  7. this.next = next;
  8. }
  9. public Node(E e) {
  10. this(e, null);
  11. }
  12. public Node() {
  13. this(null, null);
  14. }
  15. @Override
  16. public String toString() {
  17. return e.toString();
  18. }
  19. }
  20. private Node head, tail;
  21. private int size;
  22. public LinkedListQueue() {
  23. head = null;
  24. tail = null;
  25. size = 0;
  26. }
  27. @Override
  28. public void enqueue(E e) {
  29. //如果队尾为空,说明队首也为空
  30. if (tail == null) {
  31. //直接在队尾添加节点
  32. tail = new Node(e);
  33. head = tail;
  34. } else {
  35. tail.next = new Node(e);
  36. tail = tail.next;
  37. }
  38. size++;
  39. }
  40. @Override
  41. public E dequeue() {
  42. this.checkSize();
  43. //出队是删除队首,也就是head
  44. Node ret = head;
  45. //把head指向之前head的下一个节点
  46. head = ret.next;
  47. //之前的head的下一个节点改为空
  48. ret.next = null;
  49. //修改head指向后队列可能为空
  50. if (head == null) {
  51. tail = null;
  52. }
  53. size--;
  54. return ret.e;
  55. }
  56. private void checkSize() {
  57. if (isEmpty()) {
  58. throw new IllegalArgumentException("cannot dequeue from an empty queue");
  59. }
  60. }
  61. @Override
  62. public E getFront() {
  63. this.checkSize();
  64. return head.e;
  65. }
  66. @Override
  67. public int getSize() {
  68. return size;
  69. }
  70. @Override
  71. public boolean isEmpty() {
  72. return size == 0;
  73. }
  74. @Override
  75. public String toString() {
  76. StringBuilder builder = new StringBuilder();
  77. builder.append("Queue: front ");
  78. Node cur = head;
  79. while (cur!=null){
  80. builder.append(cur).append("->");
  81. cur = cur.next;
  82. }
  83. builder.append("Null tail");
  84. return builder.toString();
  85. }
  86. public static void main(String[] args) {
  87. LinkedListQueue<Integer> linkedListQueue = new LinkedListQueue<>();
  88. for (int i = 0; i < 10; i++) {
  89. //向队列中添加元素
  90. linkedListQueue.enqueue(i);
  91. System.out.println(linkedListQueue);
  92. if (i % 3 == 2) {
  93. linkedListQueue.dequeue();
  94. System.out.println(linkedListQueue);
  95. }
  96. }
  97. }
  98. }

数组队列,循环队列和链表队列耗时比较

数组队列

  1. public class ArrayQueue<E> implements Queue<E> {
  2. Array<E> array;
  3. public ArrayQueue(int capacity) {
  4. array = new Array<>(capacity);
  5. }
  6. public ArrayQueue() {
  7. array = new Array<>();
  8. }
  9. @Override
  10. public void enqueue(E e) {
  11. //向数组的末尾添加元素
  12. array.addLast(e);
  13. }
  14. @Override
  15. public E dequeue() {
  16. //取出第一个元素
  17. return array.removeFirst();
  18. }
  19. @Override
  20. public E getFront() {
  21. return array.getFirst();
  22. }
  23. @Override
  24. public int getSize() {
  25. return array.getSize();
  26. }
  27. @Override
  28. public boolean isEmpty() {
  29. return array.isEmpty();
  30. }
  31. public int getCapacity() {
  32. return array.getCapacity();
  33. }
  34. @Override
  35. public String toString() {
  36. StringBuilder builder = new StringBuilder();
  37. builder.append("Queue: ");
  38. builder.append("front [");
  39. for (int i = 0; i < array.getSize(); i++) {
  40. builder.append(array.get(i));
  41. if (i != array.getSize() - 1) {
  42. builder.append(",");
  43. }
  44. }
  45. builder.append("] tail");
  46. return builder.toString();
  47. }
  48. public static void main(String[] args) {
  49. ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
  50. for (int i = 0; i < 10; i++) {
  51. //向队列中添加元素
  52. arrayQueue.enqueue(i);
  53. System.out.println(arrayQueue);
  54. if (i % 3 == 2) {
  55. arrayQueue.dequeue();
  56. System.out.println(arrayQueue);
  57. }
  58. }
  59. }
  60. }

循环队列

  1. public class LoopQueue<E> implements Queue<E> {
  2. private E[] data;
  3. private int front, tail;
  4. private int size;
  5. @SuppressWarnings("unchecked")
  6. public LoopQueue(int capacity) {
  7. data = (E[]) new Object[capacity + 1];
  8. front = 0;
  9. tail = 0;
  10. size = 0;
  11. }
  12. public int getCapacity() {
  13. return data.length - 1;
  14. }
  15. public LoopQueue() {
  16. this(10);
  17. }
  18. @Override
  19. public void enqueue(E e) {
  20. if ((tail + 1) % data.length == front) {
  21. resize(getCapacity() * 2);
  22. }
  23. data[tail] = e;
  24. tail = (tail + 1) % data.length;
  25. size++;
  26. }
  27. @SuppressWarnings("unchecked")
  28. private void resize(int newCapacity) {
  29. E[] newData = (E[]) new Object[newCapacity + 1];
  30. for (int i = 0; i < size; i++) {
  31. //新数组第一个元素对应的data是front,第二个元素是front+1,第三个是front+2,依次类推
  32. //循环队列可能会产生数组越界
  33. newData[i] = data[(i + front) % data.length];
  34. }
  35. data = newData;
  36. front = 0;
  37. tail = size;
  38. }
  39. @Override
  40. public E dequeue() {
  41. if (isEmpty()) {
  42. throw new IllegalArgumentException("Cannot dequeue from an empty queue");
  43. }
  44. E ret = data[front];
  45. data[front] = null;
  46. front = (front + 1) % data.length;
  47. size--;
  48. //缩容
  49. if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {
  50. resize(getCapacity() / 2);
  51. }
  52. return ret;
  53. }
  54. @Override
  55. public E getFront() {
  56. if (isEmpty()) {
  57. throw new IllegalArgumentException("Cannot dequeue from an empty queue");
  58. }
  59. return data[front];
  60. }
  61. @Override
  62. public int getSize() {
  63. return size;
  64. }
  65. @Override
  66. public boolean isEmpty() {
  67. return front == tail;
  68. }
  69. @Override
  70. public String toString() {
  71. StringBuilder builder = new StringBuilder();
  72. builder.append(String.format("Queue: size = %d,capacity = %d\n", size, getCapacity()));
  73. builder.append("fount [");
  74. for (int i = front; i != tail; i = (i + 1) % data.length) {
  75. builder.append(data[i]);
  76. if (i != size - 1) {
  77. builder.append(",");
  78. }
  79. }
  80. builder.append("] tail");
  81. return builder.toString();
  82. }
  83. public static void main(String[] args) {
  84. LoopQueue<Integer> loopQueue = new LoopQueue<>();
  85. for (int i = 0; i < 10; i++) {
  86. //向队列中添加元素
  87. loopQueue.enqueue(i);
  88. System.out.println(loopQueue);
  89. if (i % 3 == 2) {
  90. loopQueue.dequeue();
  91. System.out.println(loopQueue);
  92. }
  93. }
  94. }
  95. }

耗时对比

  1. public class TestQueue {
  2. public static void main(String[] args) {
  3. int opCount = 100000;
  4. System.out.println("数组队列ArrayQueue耗时: " + testQueue(new ArrayQueue<>(), opCount) + "s");
  5. System.out.println("循环队列LoopQueue耗时: " + testQueue(new LoopQueue<>(), opCount) + "s");
  6. System.out.println("链表队列LinkedListQueue耗时: " + testQueue(new LinkedListQueue<>(), opCount) + "s");
  7. }
  8. private static double testQueue(Queue<Integer> q, int opCount) {
  9. long startTime = System.nanoTime();
  10. SecureRandom random = new SecureRandom();
  11. for (int i = 0; i < opCount; i++) {
  12. //入队操作
  13. q.enqueue(random.nextInt(Integer.MAX_VALUE));
  14. }
  15. for (int i = 0; i < opCount; i++) {
  16. //出队操作
  17. q.dequeue();
  18. }
  19. long endTime = System.nanoTime();
  20. return (endTime - startTime) / 1000000000.0;
  21. }
  22. }

链表应用

移除链表元素(leetcode 203)

1. 要求

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点
示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
示例 2:
输入:head = [], val = 1
输出:[]
示例 3:
输入:head = [7,7,7,7], val = 7
输出:[]

提示:
列表中的节点在范围 [0, 104] 内
1 <= Node.val <= 50
0 <= k <= 50

2. Java不使用虚拟头节点实现

  1. public class LinkSolution {
  2. private static class ListNode {
  3. int val;
  4. ListNode next;
  5. ListNode() {
  6. }
  7. ListNode(int val) {
  8. this.val = val;
  9. }
  10. ListNode(int val, ListNode next) {
  11. this.val = val;
  12. this.next = next;
  13. }
  14. //使用arr作为参数,创建链表,当前得listNode为链表头节点
  15. public ListNode(int[] arr) {
  16. if (arr == null || arr.length == 0) {
  17. throw new IllegalArgumentException("arr can not be empty");
  18. }
  19. //将数组第一个元素作为头节点
  20. this.val = arr[0];
  21. //当前节点
  22. ListNode cur = this;
  23. //循环遍历从第二个元素开始放到节点的下一节点
  24. for (int i = 1; i < arr.length; i++) {
  25. cur.next = new ListNode(arr[i]);
  26. //将加入的节点赋值给当前节点,遍历添加这一节点的下一节点
  27. cur = cur.next;
  28. }
  29. }
  30. @Override
  31. public String toString() {
  32. StringBuilder stringBuilder = new StringBuilder();
  33. ListNode cur = this;
  34. while (cur != null){
  35. stringBuilder.append(cur.val).append("->");
  36. cur = cur.next;
  37. }
  38. stringBuilder.append("null");
  39. return stringBuilder.toString();
  40. }
  41. }
  42. public static ListNode removeElements(ListNode head, int val) {
  43. //处理头节点,所有头节点的下一节点也与val相等时循环处理
  44. while (head != null && head.val == val) {
  45. //若头节点与val相等,删除头节点
  46. ListNode delNode = head;
  47. //头节点改为将要删除的头节点的下一节点
  48. head = head.next;
  49. //删除节点的下一节点设为null
  50. delNode.next = null;
  51. //上面过程可直接写为head = head.next;
  52. //head = head.next;
  53. }
  54. if (head == null) {
  55. return null;
  56. }
  57. //待删除的前一个节点.prev是头节点,一定不是待删除的节点,
  58. //前面已经对头节点做过处理,走到这一步说明头节点的val一定与val不相等
  59. ListNode prev = head;
  60. //循环头节点的下一个节点之后所有节点,判断是否删除,逻辑与头节点的删除逻辑相同
  61. while (prev.next != null) {
  62. if (prev.next.val == val) {
  63. ListNode delNode = prev.next;
  64. prev.next = delNode.next;
  65. delNode.next = null;
  66. //上面三行可直接写为
  67. //prev.next = prev.next.next;
  68. } else {
  69. //不需要删除直接将prev直接后挪一个节点
  70. prev = prev.next;
  71. }
  72. }
  73. return head;
  74. }
  75. }

3. go实现

  1. type ListNode struct {
  2. Val int
  3. Next *ListNode
  4. }
  5. func removeElements(head *ListNode, val int) *ListNode {
  6. for head != nil && head.Val == val {
  7. head = head.Next
  8. }
  9. if head == nil {
  10. return head
  11. }
  12. pre := head
  13. for pre.Next != nil {
  14. if pre.Next.Val == val {
  15. pre.Next = pre.Next.Next
  16. } else {
  17. pre = pre.Next
  18. }
  19. }
  20. return head
  21. }

4. Java使用虚拟头节点实现

  1. public static ListNode removeElements1(ListNode head, int val) {
  2. //创建虚拟头节点
  3. ListNode dummyHead = new ListNode(-1);
  4. //虚拟头节点得下一节点指向真实头节点
  5. dummyHead.next = head;
  6. //从第一个元素之前得节点开始,不需要处理头节点
  7. ListNode prev = dummyHead;
  8. while (prev.next != null) {
  9. if (prev.next.val == val) {
  10. prev.next = prev.next.next;
  11. } else {
  12. prev = prev.next;
  13. }
  14. }
  15. return dummyHead.next;
  16. }

5.所写方法测试

  1. public static void main(String[] args) {
  2. int[] nums = {1,2,6,3,4,5,6};
  3. ListNode head = new ListNode(nums);
  4. System.out.println(head);
  5. System.out.println("不使用虚拟头节点: " + LinkSolution.removeElements(head,6));
  6. System.out.println("使用虚拟头节点: " + LinkSolution.removeElements1(head,6));
  7. }

项目demo