参考链接

前言

数组作为数据存储结构有一定的缺陷。在无序数组中,搜索性能差,在有序数组中,插入效率又很低,而且这两种数组的删除效率都很低,并且数组在创建后,其大小是固定了,设置的过大会造成内存的浪费,过小又不能满足数据量的存储。

  本篇博客我们将讲解一种新型的数据结构——链表。我们知道数组是一种通用的数据结构,能用来实现栈、队列等很多数据结构。而链表也是一种使用广泛的通用数据结构,它也可以用来作为实现栈、队列等数据结构的基础,基本上除非需要频繁的通过下标来随机访问各个数据,否则很多使用数组的地方都可以用链表来代替。

  但是我们需要说明的是,链表是不能解决数据存储的所有问题的,它也有它的优点和缺点。本篇博客我们介绍几种常见的链表,分别是单向链表、双端链表、有序链表、双向链表以及有迭代器的链表。并且会讲解一下抽象数据类型(ADT)的思想,如何用 ADT 描述栈和队列,如何用链表代替数组来实现栈和队列。

1、链表(Linked List)

 使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。

2、单向链表(Single-Linked List)

 单链表是链表中结构最简单的。一个单链表的节点(Node)分为两个部分,第一个部分(data)保存或者显示关于节点的信息,另一个部分存储下一个节点的地址。最后一个节点存储地址的部分指向空值。
  单向链表只可向一个方向遍历,一般查找一个节点的时候需要从第一个节点开始每次访问下一个节点,一直访问到需要的位置。而插入一个节点,对于单向链表,我们只提供在链表头插入,只需要将当前插入的节点设置为头节点,next指向原头节点即可。删除一个节点,我们将该节点的上一个节点的next指向该节点的下一个节点。
链表 - 图1

1、单向链表的代码实现

  1. package datastrcrt;
  2. import datastrcrt.SingleLinkedList;
  3. public class SingleLinkedList {
  4. private int size;//链表节点的个数
  5. private Node head;//头节点
  6. public SingleLinkedList()
  7. {
  8. size = 0;
  9. head = null;
  10. }
  11. //链表的每个节点类
  12. public class Node {
  13. public Object data;
  14. private Node next;
  15. public Node(Object data)
  16. {
  17. this.data = data;
  18. }
  19. }
  20. //在链表头添加元素
  21. public void addhead(Object data)
  22. {
  23. Node newhead = new Node(data);
  24. if(size == 0)
  25. {
  26. head = newhead;
  27. }else
  28. {
  29. newhead.next = head;
  30. head = newhead;
  31. }
  32. size++;
  33. }
  34. //在链表头删除元素
  35. public Object delehead()
  36. {
  37. if(size == 0)
  38. {
  39. System.out.println("请先在链表中添加元素");
  40. return null;
  41. }
  42. Object data = head.data;
  43. head = head.next;
  44. size--;
  45. return data;
  46. }
  47. //查找指定的元素
  48. public Node find(Object obj)
  49. {
  50. Node current = head;
  51. if(size == 0)
  52. {
  53. System.out.println("请先在链表中添加元素");
  54. return null;
  55. }
  56. while(current!=null)
  57. {
  58. if(current.data.equals(obj))
  59. {
  60. return current;
  61. }else
  62. {
  63. current = current.next;
  64. }
  65. }
  66. return null;
  67. }
  68. //删除指定的元素
  69. public Node delete(Object obj)
  70. {
  71. Node current = head;
  72. Node precurrent = head;
  73. while(current!=null)
  74. {
  75. if(current.data.equals(obj)&&current.equals(head))
  76. {
  77. head = current.next;
  78. size--;
  79. return current;
  80. }else if(current.data.equals(obj)&&!current.equals(head))
  81. {
  82. precurrent.next = current.next;
  83. size--;
  84. return current;
  85. }
  86. precurrent = current;
  87. current = current.next;
  88. }
  89. return current;
  90. }
  91. //判断链表是否为空
  92. public boolean isEmpty()
  93. {
  94. return (size==0);
  95. }
  96. //显示节点信息
  97. public void display()
  98. {
  99. if(size > 0)
  100. {
  101. Node node = head;
  102. if(size == 1){//当前链表只有一个节点
  103. System.out.println("["+node.data+"]");
  104. return;
  105. }
  106. while(node!=null)
  107. {
  108. if(node.equals(head))
  109. {
  110. System.out.print("["+node.data+"->");
  111. }else if(node.next == null)
  112. {
  113. System.out.print(node.data+"]");
  114. }else
  115. {
  116. System.out.print(node.data+"->");
  117. }
  118. node = node.next;
  119. }
  120. System.out.println();
  121. }else
  122. {
  123. System.out.println("[]");
  124. }
  125. }
  126. }

2、用单向链表实现栈的代码实现

线性表的一种特殊的存储结构。与学习过的线性表的不同之处在于栈只能从表的固定一端对数据进行插入和删除操作,另一端是封死的。
栈的pop()方法和push()方法,对应于链表的在头部删除元素deleteHead()以及在头部增加元素addHead()。
链表 - 图2

  1. public class StackSingleLink {
  2. private SingleLinkedList link;
  3. public StackSingleLink(){
  4. link = new SingleLinkedList();
  5. }
  6. //添加元素
  7. public void push(Object obj){
  8. link.addHead(obj);
  9. }
  10. //移除栈顶元素
  11. public Object pop(){
  12. Object obj = link.deleteHead();
  13. return obj;
  14. }
  15. //判断是否为空
  16. public boolean isEmpty(){
  17. return link.isEmpty();
  18. }
  19. //打印栈内元素信息
  20. public void display(){
  21. link.display();
  22. }
  23. }

3、双端链表

对于单项链表,我们如果想在尾部添加一个节点,那么必须从头部一直遍历到尾部,找到尾节点,然后在尾节点后面插入一个节点。这样操作很麻烦,如果我们在设计链表的时候多个对尾节点的引用,那么会简单很多。链表 - 图3

1、双端链表的代码实现

  1. package datastrcrt;
  2. import datastrcrt.SingleLinkedList;
  3. public class SingleLinkedList {
  4. private int size;//链表节点的个数
  5. private Node head;//头节点
  6. private Node tail;//尾节点
  7. public SingleLinkedList()
  8. {
  9. size = 0;
  10. head = null;
  11. }
  12. //链表的每个节点类
  13. public class Node {
  14. public Object data;
  15. private Node next;
  16. public Node(Object data)
  17. {
  18. this.data = data;
  19. }
  20. }
  21. //在链表头添加元素
  22. public void addhead(Object data)
  23. {
  24. Node newhead = new Node(data);
  25. if(size == 0)
  26. {
  27. head = newhead;
  28. tail = newhead;
  29. }else
  30. {
  31. newhead.next = head;
  32. head = newhead;
  33. }
  34. size++;
  35. }//在链表尾部添加元素
  36. public void addtail(Object obj)
  37. {
  38. Node newtail = new Node(obj);
  39. if(size == 0)
  40. {
  41. head = newtail;
  42. tail = newtail;
  43. }else
  44. {
  45. tail.next = newtail;
  46. tail = newtail;
  47. }
  48. size++;
  49. }
  50. //在链表头删除元素
  51. public Object delehead()
  52. {
  53. if(size == 0)
  54. {
  55. System.out.println("请先在链表中添加元素");
  56. return null;
  57. }
  58. Object data = head.data;
  59. head = head.next;
  60. size--;
  61. return data;
  62. }
  63. //查找指定的元素
  64. public Node find(Object obj)
  65. {
  66. Node current = head;
  67. if(size == 0)
  68. {
  69. System.out.println("请先在链表中添加元素");
  70. return null;
  71. }
  72. while(current!=null)
  73. {
  74. if(current.data.equals(obj))
  75. {
  76. return current;
  77. }else
  78. {
  79. current = current.next;
  80. }
  81. }
  82. return null;
  83. }
  84. //删除指定的元素
  85. public Node delete(Object obj)
  86. {
  87. Node current = head;
  88. Node precurrent = head;
  89. while(current!=null)
  90. {
  91. if(current.data.equals(obj)&&current.equals(head))
  92. {
  93. head = current.next;
  94. size--;
  95. return current;
  96. }else if(current.data.equals(obj)&&!current.equals(head))
  97. {
  98. precurrent.next = current.next;
  99. size--;
  100. return current;
  101. }
  102. precurrent = current;
  103. current = current.next;
  104. }
  105. return current;
  106. }
  107. //判断链表是否为空
  108. public boolean isEmpty()
  109. {
  110. return (size==0);
  111. }
  112. //获得链表的节点个数
  113. public int getSize(){
  114. return size;
  115. }
  116. //显示节点信息
  117. public void display()
  118. {
  119. if(size > 0)
  120. {
  121. Node node = head;
  122. if(size == 1){//当前链表只有一个节点
  123. System.out.println("["+node.data+"]");
  124. return;
  125. }
  126. while(node!=null)
  127. {
  128. if(node.equals(head))
  129. {
  130. System.out.print("["+node.data+"->");
  131. }else if(node.next == null)
  132. {
  133. System.out.print(node.data+"]");
  134. }else
  135. {
  136. System.out.print(node.data+"->");
  137. }
  138. node = node.next;
  139. }
  140. System.out.println();
  141. }else
  142. {
  143. System.out.println("[]");
  144. }
  145. }
  146. }

2、用双端链表实现队列的代码实现

  1. package com.ys.link;
  2. public class QueueLinkedList {
  3. private DoublePointLinkedList dp;
  4. public QueueLinkedList(){
  5. dp = new DoublePointLinkedList();
  6. }
  7. public void insert(Object data){
  8. dp.addTail(data);
  9. }
  10. public void delete(){
  11. dp.deleteHead();
  12. }
  13. public boolean isEmpty(){
  14. return dp.isEmpty();
  15. }
  16. public int getSize(){
  17. return dp.getSize();
  18. }
  19. public void display(){
  20. dp.display();
  21. }
  22. }

四、双向链表

我们知道单向链表只能从一个方向遍历,那么双向链表它可以从两个方向遍历。

链表 - 图4

1、双向链表的代码实现

  1. package datastrcrt;
  2. import datastrcrt.SingleLinkedList;
  3. public class SingleLinkedList {
  4. private int size;//链表节点的个数
  5. private Node head;//头节点
  6. private Node tail;//尾节点
  7. public SingleLinkedList()
  8. {
  9. size = 0;
  10. head = null;
  11. }
  12. //链表的每个节点类
  13. public class Node {
  14. public Object data;
  15. public Node next;
  16. public Node prev;
  17. public Node(Object data)
  18. {
  19. this.data = data;
  20. }
  21. }
  22. //在链表头添加元素
  23. public void addhead(Object data)
  24. {
  25. Node newhead = new Node(data);
  26. if(size == 0)
  27. {
  28. head = newhead;
  29. tail = newhead;
  30. }else
  31. {
  32. head.prev = newhead;
  33. newhead.next = head;
  34. head = newhead;
  35. }
  36. size++;
  37. }//在链表尾部添加元素
  38. public void addtail(Object obj)
  39. {
  40. Node newtail = new Node(obj);
  41. if(size == 0)
  42. {
  43. head = newtail;
  44. tail = newtail;
  45. }else
  46. {
  47. newtail.prev = tail;
  48. tail.next = newtail;
  49. tail = newtail;
  50. }
  51. size++;
  52. }
  53. //在链表头删除元素
  54. public Object delehead()
  55. {
  56. Node temp = head;
  57. if(size == 0)
  58. {
  59. System.out.println("请先在链表中添加元素");
  60. return null;
  61. }
  62. head = head.next;
  63. head.prev = null;
  64. size--;
  65. return temp;
  66. }
  67. //删除链表尾
  68. public Node deleteTail()
  69. {
  70. Node temp = tail;
  71. if(size == 0)
  72. {
  73. System.out.println("请先在链表中添加元素");
  74. return null;
  75. }
  76. tail = tail.prev;
  77. tail.next = null;
  78. size--;
  79. return temp;
  80. }
  81. //查找指定的元素
  82. public Node find(Object obj)
  83. {
  84. Node current = head;
  85. if(size == 0)
  86. {
  87. System.out.println("请先在链表中添加元素");
  88. return null;
  89. }
  90. while(current!=null)
  91. {
  92. if(current.data.equals(obj))
  93. {
  94. return current;
  95. }else
  96. {
  97. current = current.next;
  98. }
  99. }
  100. return null;
  101. }
  102. //删除指定的元素
  103. public Node delete(Object obj)
  104. {
  105. Node current = head;
  106. Node precurrent = head;
  107. while(current!=null)
  108. {
  109. if(current.data.equals(obj)&&current.equals(head))
  110. {
  111. head = current.next;
  112. size--;
  113. return current;
  114. }else if(current.data.equals(obj)&&!current.equals(head))
  115. {
  116. precurrent.next = current.next;
  117. size--;
  118. return current;
  119. }
  120. precurrent = current;
  121. current = current.next;
  122. }
  123. return current;
  124. }
  125. //判断链表是否为空
  126. public boolean isEmpty()
  127. {
  128. return (size==0);
  129. }
  130. //获得链表的节点个数
  131. public int getSize(){
  132. return size;
  133. }
  134. //显示节点信息
  135. public void display()
  136. {
  137. if(size > 0)
  138. {
  139. Node node = head;
  140. if(size == 1){//当前链表只有一个节点
  141. System.out.println("["+node.data+"]");
  142. return;
  143. }
  144. while(node!=null)
  145. {
  146. if(node.equals(head))
  147. {
  148. System.out.print("["+node.data+"->");
  149. }else if(node.next == null)
  150. {
  151. System.out.print(node.data+"]");
  152. }else
  153. {
  154. System.out.print(node.data+"->");
  155. }
  156. node = node.next;
  157. }
  158. System.out.println();
  159. }else
  160. {
  161. System.out.println("[]");
  162. }
  163. }
  164. }