基础数据结构-链表 - 图1

1. 什么是链表?

1.1 链表的基本概念

链表在内存中的保存是不连续的,每个结点通过指针的方式联系在一起。以单向链表为例,它包含两个部分,一部分保存数据内容,一部分保存下一个结点的引用。
image.png

1.2 链表的特点

  • 内存中不连续存在
  • 通过指针建立联系
  • 大小不是固定的,不需要扩容的操作
  • 插入、删除元素比较快

    1.3 链表的分类

  • 单向链表

  • 双向链表
  • 循环链表

    1.3.1 单向链表

    单向链表,顾名思义就是记录的引用信息时单向的,一般的单向链表都是记录下一个结点的引用信息。
    image.png ```java package com.muzili.list;

import org.w3c.dom.Node;

import java.util.Objects;

/*

  • @author: muzili(李敷斌)
  • @date: 2021/7/13
  • @version: v0.0.1
  • @description: 单向链表 */ public class LinkedList {

    /**

    • 头结点 / private Node first; /*
    • 尾结点 / private Node last; /*
    • 链表大小 */ private int size;

      public Node getFirst() { return first; }

      public void setFirst(Node first) { this.first = first; }

      public Node getLast() { return last; }

      public void setLast(Node last) { this.last = last; }

      public int getSize() { return size; }

      public void setSize(int size) { this.size = size; }

      public LinkedList() { }

      public LinkedList(Node node) { this.first = node; this.last = node; this.size = 1; }

      /**

    • 添加到头部
    • @param data */ public void addFirst(T data){ Node node = new Node(data); node.next = first; first = node; if (size==0){

      1. last = node;

      } size++; }

      /**

    • 添加到尾部
    • @param data */ public void addLast(T data){ Node node = new Node(data); if (Objects.isNull(last) && Objects.isNull(first)){

      1. last = node;
      2. first = node;
      3. size++;
      4. return;

      }

      if (Objects.isNull(last)){

      1. last = node;
      2. size++;
      3. return;

      } last.next = node; last = node; size++; }

      /**

    • 添加原始到指定位置
    • @param loc
    • @param data */ public void addNth(int loc,T data){

      if (size==0){

      1. addFirst(data);
      2. return;

      }

      if (loc > size){

      1. addLast(data);
      2. return;

      }

      Node curr = first; for (int i = 1; i < loc; i++) {

      1. curr = curr.next;

      } Node node = new Node(data); node.next = curr.next; curr.next = node; size++; }

      /**

    • 删除第一个结点 */ public void removeFirst(){

      if (size==0){

      1. return;

      }

      first = first.next; size—; if (size==1){

      1. last = first;

      } }

  1. public void removeLast(){
  2. if (size==0){
  3. return;
  4. }
  5. Node cur = first;
  6. for (int i = 1; i < size - 1; i++) {
  7. cur = cur.next;
  8. }
  9. last = cur;
  10. cur.next = null;
  11. size--;
  12. }
  13. /**
  14. * 删除指定位置的元素
  15. * @param loc
  16. */
  17. public void removeNth(int loc){
  18. if (loc < 0 || loc > size){
  19. throw new IllegalArgumentException();
  20. }
  21. Node cur = first;
  22. for (int i = 1; i < loc; i++) {
  23. cur = cur.next;
  24. }
  25. cur.next = cur.next.next;
  26. }
  27. /**
  28. * 获取指定位置的元素值
  29. * @param loc
  30. * @return 元素值
  31. */
  32. public T getNth(int loc){
  33. if (loc >= size){
  34. throw new IllegalArgumentException();
  35. }
  36. Node cur = first;
  37. for (int i = 1; i < loc; i++) {
  38. cur = cur.next;
  39. }
  40. return (T) cur.getData();
  41. }
  42. /**
  43. * 结点
  44. * @param <T>
  45. */
  46. class Node<T>{
  47. private T data;
  48. private Node next;
  49. public Node(T data) {
  50. this.data = data;
  51. }
  52. public T getData() {
  53. return data;
  54. }
  55. public void setData(T data) {
  56. this.data = data;
  57. }
  58. public Node getNext() {
  59. return next;
  60. }
  61. public void setNext(Node next) {
  62. this.next = next;
  63. }
  64. }
  65. public static void main(String[] args) {
  66. LinkedList list = new LinkedList<String>();
  67. list.addLast("aaaa");
  68. list.addFirst("bbbb");
  69. list.addLast("ccc");
  70. list.addNth(4,"dddd");
  71. list.addNth(4,"ffff");
  72. list.addNth(4,"eeee");
  73. list.addNth(4,"gggg");
  74. list.addNth(4,"g4");
  75. System.out.println(list);
  76. list.removeLast();
  77. list.removeLast();
  78. list.removeNth(2);
  79. System.out.println(list);
  80. System.out.println(list.getNth(1));
  81. }

}

  1. <a name="BUFFb"></a>
  2. ### 1.3.2 双向链表
  3. 双向链表顾名思义,就是记录了两个方向的指针信息,双向链表记录上一个和下一个结点的信息。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/21857252/1626665423208-cb6de808-8283-4200-9b51-cfdaa619d1e8.png#align=left&display=inline&height=821&margin=%5Bobject%20Object%5D&name=image.png&originHeight=821&originWidth=1413&size=41140&status=done&style=none&width=1413)
  4. ```java
  5. package com.muzili.list;
  6. /**
  7. *
  8. * @author: muzili(李敷斌)
  9. * @date: 2021/7/16
  10. * @version: v0.0.1
  11. * @description: 双向链表
  12. */
  13. public class DoubleLinkedList<T> {
  14. /**
  15. * 头结点
  16. */
  17. private Node<T> first;
  18. /**
  19. * 尾结点
  20. */
  21. private Node<T> last;
  22. /**
  23. * 链表大小
  24. */
  25. private int size = 0;
  26. public Node<T> getFirst() {
  27. return first;
  28. }
  29. public void setFirst(Node<T> first) {
  30. this.first = first;
  31. }
  32. public Node<T> getLast() {
  33. return last;
  34. }
  35. public void setLast(Node<T> last) {
  36. this.last = last;
  37. }
  38. public int getSize() {
  39. return size;
  40. }
  41. public void setSize(int size) {
  42. this.size = size;
  43. }
  44. public DoubleLinkedList() {
  45. }
  46. public DoubleLinkedList(T data) {
  47. Node<T> node = new Node<>();
  48. node.data = data;
  49. first = node;
  50. last = node;
  51. size++;
  52. }
  53. /**
  54. * 将数据添加到头部
  55. * @param data
  56. */
  57. public void addFirst(T data){
  58. Node node = new Node();
  59. node.data = data;
  60. if (first!=null){
  61. node.next = first;
  62. first.prev = node;
  63. }
  64. first = node;
  65. if (size==0){
  66. last = node;
  67. }
  68. size++;
  69. }
  70. /**
  71. * 将数据添加到尾部
  72. * @param data
  73. */
  74. public void addLast(T data){
  75. if (size==0){
  76. addFirst(data);
  77. return;
  78. }
  79. Node node = new Node();
  80. node.data = data;
  81. node.prev = last;
  82. last.next = node;
  83. last = node;
  84. size++;
  85. }
  86. /**
  87. * 向指定位置添加数据
  88. * @param loc
  89. * @param data
  90. */
  91. public void addNth(int loc,T data){
  92. if (loc < 0 || loc > size){
  93. throw new IllegalArgumentException();
  94. }
  95. //如果loc为0添加到头部
  96. if (loc==0){
  97. addFirst(data);
  98. return;
  99. }
  100. //如果loc等于链表大小 添加到尾部
  101. if (loc == size){
  102. addLast(data);
  103. return;
  104. }
  105. Node node = new Node();
  106. node.data = data;
  107. //获取到待添加位置的元素
  108. Node curr = first;
  109. for (int i = 1; i <= loc; i++) {
  110. curr = curr.next;
  111. }
  112. node.next = curr;
  113. curr.prev = node;
  114. size++;
  115. }
  116. /**
  117. * 删除头部的元素
  118. */
  119. public void removeFirst(){
  120. if (size == 0){
  121. return;
  122. }
  123. first = first.next;
  124. size--;
  125. }
  126. /**
  127. * 删除尾部的元素
  128. */
  129. public void removeLast(){
  130. if (size==0){
  131. return;
  132. }
  133. last = last.prev;
  134. last.next = null;
  135. size--;
  136. }
  137. /**
  138. * 删除指定位置的元素
  139. * @param loc
  140. */
  141. public void removeNth(int loc){
  142. if (loc < 0|| loc >= size){
  143. throw new IllegalArgumentException();
  144. }
  145. //如果删除的元素位置为0 则删除头部元素
  146. if (loc==0){
  147. removeFirst();
  148. return;
  149. }
  150. //如果删除元素的位置为size-1 则删除尾部的元素
  151. if (loc == size-1){
  152. removeLast();
  153. return;
  154. }
  155. //找到指定位置的元素
  156. Node curr = first;
  157. for (int i = 1; i <= loc; i++) {
  158. curr = curr.next;
  159. }
  160. Node next = curr.next;
  161. Node prev = curr.prev;
  162. if (next!=null){
  163. next.prev = curr.prev;
  164. }
  165. if (prev!=null){
  166. prev.next = next;
  167. }
  168. size--;
  169. }
  170. /**
  171. * 获取指定位置的元素值
  172. * @param loc
  173. * @return
  174. */
  175. public T getNth(int loc){
  176. if (loc < 0|| loc >= size){
  177. throw new IllegalArgumentException();
  178. }
  179. Node<T> curr = first;
  180. for (int i = 1; i <= loc; i++) {
  181. curr = curr.next;
  182. }
  183. return curr.data;
  184. }
  185. class Node<T>{
  186. /**
  187. * 上一节点
  188. */
  189. private Node<T> prev;
  190. /**
  191. * 下一节点
  192. */
  193. private Node<T> next;
  194. /**
  195. * 数据值
  196. */
  197. private T data;
  198. public Node<T> getPrev() {
  199. return prev;
  200. }
  201. public void setPrev(Node<T> prev) {
  202. this.prev = prev;
  203. }
  204. public Node<T> getNext() {
  205. return next;
  206. }
  207. public void setNext(Node<T> next) {
  208. this.next = next;
  209. }
  210. public T getData() {
  211. return data;
  212. }
  213. public void setData(T data) {
  214. this.data = data;
  215. }
  216. }
  217. public static void main(String[] args) {
  218. DoubleLinkedList<String> list = new DoubleLinkedList<>();
  219. list.addFirst("11111");
  220. list.addLast("22222");
  221. list.addNth(0,"0000");
  222. list.addLast("3333");
  223. System.out.println(list.getNth(2));
  224. list.removeLast();
  225. list.removeNth(1);
  226. list.removeFirst();
  227. System.out.println(list);
  228. }
  229. }

1.3.3 循环链表

循环链表是一种特殊的结构,它的最后一个结点指针不是null而是指向第一个结点。循环链表可分为循环单向链表,循环双向链表。
image.png

  1. package com.muzili.list;
  2. /**
  3. *
  4. * @author: muzili(李敷斌)
  5. * @date: 2021/7/19
  6. * @version: v0.0.1
  7. * @description: 循环链表
  8. */
  9. public class CurcularLinkedList<T> {
  10. /**
  11. * 头结点
  12. */
  13. private Node<T> first;
  14. /**
  15. * 尾结点
  16. */
  17. private Node<T> last;
  18. /**
  19. * 链表长度
  20. */
  21. private int size=0;
  22. public Node<T> getFirst() {
  23. return first;
  24. }
  25. public void setFirst(Node<T> first) {
  26. this.first = first;
  27. }
  28. public Node<T> getLast() {
  29. return last;
  30. }
  31. public void setLast(Node<T> last) {
  32. this.last = last;
  33. }
  34. public int getSize() {
  35. return size;
  36. }
  37. public void setSize(int size) {
  38. this.size = size;
  39. }
  40. public CurcularLinkedList() {
  41. }
  42. public CurcularLinkedList(T data) {
  43. Node node = new Node();
  44. node.data = data;
  45. first = node;
  46. last = node;
  47. size++;
  48. }
  49. /**
  50. * 添加到头部
  51. * @param data
  52. */
  53. public void addFirst(T data){
  54. Node node = new Node();
  55. node.data = data;
  56. if (size >= 1){
  57. node.next = first;
  58. }
  59. first = node;
  60. if (size==0){
  61. last = node;
  62. last.next = first;
  63. }
  64. }
  65. /**
  66. * 添加到尾部
  67. * @param data
  68. */
  69. public void addLast(T data){
  70. Node node = new Node();
  71. node.data = data;
  72. if (size == 0){
  73. first = node;
  74. last = node;
  75. last.next = first;
  76. size++;
  77. return;
  78. }
  79. last.next = node;
  80. last = node;
  81. last.next = first;
  82. size++;
  83. }
  84. /**
  85. * 添加到指定位置
  86. * @param loc
  87. * @param data
  88. */
  89. public void addNth(int loc,T data){
  90. if (loc > size){
  91. throw new IllegalArgumentException();
  92. }
  93. if (size == 0){
  94. addFirst(data);
  95. return;
  96. }
  97. if (loc == size){
  98. addLast(data);
  99. return;
  100. }
  101. Node curr = first;
  102. for (int i = 1; i < loc; i++) {
  103. curr = curr.next;
  104. }
  105. Node node = new Node();
  106. node.data = data;
  107. node.next = curr.next;
  108. curr.next = node;
  109. }
  110. /**
  111. * 删除头结点
  112. */
  113. public void removeFirst(){
  114. if (size == 0){
  115. throw new IllegalArgumentException();
  116. }
  117. first = first.next;
  118. last.next = first;
  119. size --;
  120. if (size <= 1){
  121. last = first;
  122. }
  123. }
  124. /**
  125. * 删除最后一个元素
  126. */
  127. public void removeLast(){
  128. if (size == 0){
  129. throw new IllegalArgumentException();
  130. }
  131. //获取最后一个元素的前一个元素
  132. Node curr = first;
  133. for (int i = 1; i < size - 1; i++) {
  134. curr = curr.next;
  135. }
  136. curr.next = null;
  137. last = curr;
  138. curr.next = first;
  139. size --;
  140. }
  141. /**
  142. * 删除指定位置的元素
  143. * @param loc
  144. */
  145. public void removeNth(int loc){
  146. if (size==0 || loc < 0){
  147. throw new IllegalArgumentException();
  148. }
  149. if (loc == 0){
  150. removeFirst();
  151. return;
  152. }
  153. if (loc == size-1){
  154. removeLast();
  155. return;
  156. }
  157. //获取待删除元素的前一个元素
  158. Node curr = first;
  159. for (int i = 1; i < loc - 1; i++) {
  160. curr = curr.next;
  161. }
  162. curr.next = curr.next.next;
  163. size --;
  164. }
  165. /**
  166. * 约瑟夫问题解决
  167. * @param factor
  168. */
  169. public void josephRemove(int factor){
  170. Node<T> start = first;
  171. int count = 0;
  172. while (size > 1){
  173. count ++;
  174. Node curr = start;
  175. for (int i = 1; i < factor - 1; i++) {
  176. curr = curr.next;
  177. }
  178. System.out.println("第"+count+"次淘汰的元素内容:"+curr.next.data);
  179. curr.next = curr.next.next;
  180. start = curr.next;
  181. size --;
  182. }
  183. System.out.println("最后留下的的元素是:"+start.data);
  184. }
  185. public static void main(String[] args) {
  186. CurcularLinkedList<Integer> linkedList = new CurcularLinkedList<Integer>(1);
  187. linkedList.addLast(2);
  188. linkedList.addLast(3);
  189. linkedList.addLast(4);
  190. linkedList.addLast(5);
  191. linkedList.addLast(6);
  192. linkedList.josephRemove(5);
  193. }
  194. /**
  195. * 获取指定位置的元素
  196. * @param loc
  197. * @return
  198. */
  199. public T getNth(int loc){
  200. if (size == 0 || loc >= size){
  201. throw new IllegalArgumentException();
  202. }
  203. if (loc == 0){
  204. return getFirst().data;
  205. }
  206. if (loc == size - 1){
  207. return getLast().data;
  208. }
  209. Node<T> curr = first;
  210. for (int i = 0; i <= loc; i++) {
  211. curr = curr.next;
  212. }
  213. return curr.data;
  214. }
  215. class Node<T>{
  216. /**
  217. * 数据内容
  218. */
  219. private T data;
  220. /**
  221. * 下一个结点
  222. */
  223. private Node<T> next;
  224. public T getData() {
  225. return data;
  226. }
  227. public void setData(T data) {
  228. this.data = data;
  229. }
  230. public Node<T> getNext() {
  231. return next;
  232. }
  233. public void setNext(Node<T> next) {
  234. this.next = next;
  235. }
  236. }
  237. }

2. 链表VS数组

时间复杂度 数组 链表
插入、删除 O(n) O(1)
随机查找 O(1) O(n)

主要区别:

  • 数组使用比较简单,存储空间是连续的;链表的存储空间是不连续的,结点与结点间通过指针建立联系。
  • 数组的大小是固定的;链表的大小不是固定的。
  • 数组可能会出现下标越界问题。
  • 声明数组时如果内存不够容易内存溢出。
  • 数组如果声明不当可能导致内存的浪费,因为即使数组声明的空间没有使用也会占用内存。

3. 链表的使用场景

  • HashMap:Java1.8中,HashMap的底层结构是,数组+链表+红黑树。
  • 红黑树
  • B+tree
  • LRU淘汰算法(最少使用淘汰算法):通过限制链表大小,并且通过时间进行排序,超出链表大小的结点被淘汰。