数据结构与算法|线性结构

秒速五厘米 2024-04-22 22:01 146阅读 0赞

数据结构与算法|线性结构

  • 第二章 线性结构
    • 2.1 多项式表示
    • 2.2 什么是线性表
    • 2.3 线性表的实现方式
      • 2.3.1 线性表的顺序存储实现
      • 2.3.2 线性表的链式存储实现
          1. 单链表实现
          1. 双链表实现

上篇:第一章、绪论

第二章 线性结构

线性结构是数据结构中最基础的,也是最简单的一种数据结构,其中典型的叫做 “线性表” ,那什么是线性表呢?


2.1 多项式表示

举个简单的例子:一元多项式及其运算

在这里插入图片描述

主要运算:多项式相加、相减、相乘等,怎样用程序设计语言来表示这两个多项式以及实现对应的操作?

【分析】如何表示多项式?

对应多项式来讲,它的关键信息主要有这么几个,一个是它的项数,包括它的最高指数,另外一个是要表现多项式的每一项,而多项式的每一项的关键信息,又是每一项的系数和指数

  • 多项式项数 n n n
  • 各项系数 a i a_{i} ai 及指数 i i i

方法一:顺序存储结构直接表示

数组各分量对应多项式各项,这个分量主要表示每一项的系数,指数可以对应该分量的下标

例如: f ( x ) = 4 x 3 − 3 x 2 + 1 f(x)=4x^{3}-3x^{2}+1 f(x)=4x3−3x2+1
表示成:
在这里插入图片描述

两个多项式相加:两个数组对应分量相加

问题:那如何表示以下多项式?

f ( x ) = x + 3 x 2000 f(x)=x+3x^{2000} f(x)=x+3x2000

如果还是按照上面的顺序存储结构表示该多项式,很显然我们至少得用 2001 个分量得这样得一个数字来表示,而这 2001 个分量,其实只有 2 项是非零的,这样的一种表示方法显然会造成空间的巨大浪费,而且从 0~2001 ,很多计算都是无效的(0),那么有没有更好的方法,为什么一定要把所有零项、非零项、系数为零的,非零的,全部要表示进来呢?有没有可能只表示非零项?所以这就引入了第二种方法:顺序存储结构表示非零项

方法二:顺序存储结构表示非零项

它的基本思路是这样的,只表示非零项,每个非零项 a i x i a_{i}x^{i} aixi 涉及两个信息:系数 a i a_{i} ai 和指数 i i i,可以将一个多项式看成一个 ( a i , i ) (a_{i},i) (ai,i) 二元组合的集合。也可以用数组表示,但是与方法一不同,方法一每一个分量是系数,而现在每个分量不仅要包含系数,也要包含指数,那用什么样的数组呢?

结构数组表示:数组分量是由系数 a i a_{i} ai、指数 i i i 组成的结构,对应一个非零项

例如: P ( x ) 1 = 9 x 12 + 15 x 8 + 3 x 2 P(x)_{1}=9x^{12}+15x^{8}+3x^{2} P(x)1=9x12+15x8+3x2 和 P ( x ) 2 = 26 x 19 − 4 x 8 − 13 x 6 + 82 P(x)_{2}=26x^{19}-4x^{8}-13x^{6}+82 P(x)2=26x19−4x8−13x6+82 ,可以很简单的使用如下数组来表示

在这里插入图片描述

比方说这个,它就是一个结构,每一个分量包含了系数和指数,这样的表示方法,显然我们只需要表示非零项就可以,很多为零的项就可以省下不写,对于前面的 f ( x ) = x + 3 x 2000 f(x)=x+3x^{2000} f(x)=x+3x2000 ,只需要用两个分量就能表示,显然节省了很多空间,只要每一项都按照指数大小有序进行存储运算起来也很方便。这种方式不仅节省空间,在效率上也是比较高的,但是还是有其它的方法来表示。

方法三:链表结构存储非零项

链表中包含每个结点存储多项式的一个非零项,包括系数和指数两个数据域以及一个指针域,把它们串起来,同样,排列的时候,仍然可以按照指数递降(或者递增)的顺序进行排列
在这里插入图片描述

  1. public class Node {
  2. private int coef;
  3. private int expon;
  4. private Node next;
  5. public Node(int coef,int expon) {
  6. this.coef = coef;
  7. this.expon = expon;
  8. }
  9. }

P ( x ) 1 = 9 x 12 + 15 x 8 + 3 x 2 P(x)_{1}=9x^{12}+15x^{8}+3x^{2} P(x)1=9x12+15x8+3x2 和 P ( x ) 2 = 26 x 19 − 4 x 8 − 13 x 6 + 82 P(x)_{2}=26x^{19}-4x^{8}-13x^{6}+82 P(x)2=26x19−4x8−13x6+82 这两个多项式用指针表示如图所示:

在这里插入图片描述

由上可以看出,同样一个问题,可以用多种方式去实现,其次是有序线性序列的组织和管理。


2.2 什么是线性表

线性表(Linear List):是具有相同数据类型n 个元素的有序集合

线性表的逻辑结构表示:
( a 0 , a 1 , . . . , a i , a i + 1 , . . . , a n − 1 ) (a_{0},a_{1},…,a_{i},a_{i+1},…,a_{n-1}) (a0,a1,…,ai,ai+1,…,an−1)

线性表的基本特征:

  • 表中的元素个数 n 称为表的长度,n=0 是称为空表
  • 当 1 < i < n 1 < i <n 1<i<n 时

    • ① 第 i i i 个元素 a [ i ] a[i] a[i] 的直接前驱是 a [ i − 1 ] a[i-1] a[i−1], a [ 0 ] a[0] a[0] 无直接前驱
    • ② 第 i i i 个元素 a [ i ] a[i] a[i] 的直接后继是 a [ i + 1 ] a[i+1] a[i+1], a [ n − 1 ] a[n-1] a[n−1] 无直接后继
  • 所有元素的类型必须相同,且不能出现缺项
  • 每个数据元素既可以是基本的数据类型,也可以是复杂的数据类型
  • 线性表中数据元素与位置相关,即每个数据元素有唯一的序号

用图形表示的逻辑结构:

433bff81c082faad8a5200a2465e338b.png_pic_center
线性表中每个元素 a i a_{i} ai 的唯一位置通过序号或索引 i i i 表示,为了算法设计方便,将逻辑序号和存储序号统一,均假设从 0 开始,这样含 n n n 个元素的线性表的元素序号 i i i 满足 0 ≤ i ≤ n − 1 0 \le i \le n-1 0≤i≤n−1。

Java 语言中,接口 java.util.List<E> 用于表示线性表,ADT 定义如下:

ADT List {
数据对象:
D = { a i      ∣      0 ≤ i ≤ n − 1 ,    n ≥ 0 ,    a i 为 E 类型 } D=\left \{ a_{i} \;\;|\;\; 0 \le i \le n-1, \; n \ge 0, \; a_{i} 为 E 类型 \right \} D={ ai∣0≤i≤n−1,n≥0,ai为E类型}
数据关系:
r = { < a i , a i + 1 >    ∣    a i ,    a i + 1 ∈ D ,    i = 0 , . . . , n − 2 } r=\left \{ \;|\; a_{i}, \; a_{i+1} \in D, \; i=0,…,n-2 \right \} r={ ∣ai,ai+1∈D,i=0,…,n−2}
基本运算(11个):
List():创建线性表
boolean add(E e):将指定的元素追加到此列表的末尾
void add(int index, E element):将指定的元素插入此列表中的指定位置
boolean contains(Object o):如果此列表包含指定元素,则返回 true
E get(int index):返回此列表中指定位置的元素
int indexOf(Object o):返回此列表中指定位置的元素
boolean isEmpty():如果此列表不包含元素,则返回 true
E remove(int index):删除该列表中指定位置的元素
boolean remove(Object o):从列表中删除指定元素的第一个出现(如果存在)
E set(int index, E element):用指定的元素替换此列表中指定位置的元素
int size():返回此列表中的元素数
String toString():将线性表转换为字符串
}

以下实现方式为了避免内容过于冗余,只挑选了部分基本运算进行演示。


2.3 线性表的实现方式

2.3.1 线性表的顺序存储实现

利用数组的连续存储空间顺序存放线性表的各元素

在这里插入图片描述

Java 中最典型的集合类 ArrayList 就是以这种方式实现的,在 ArrayList 中维护了一个 Object 类型的数组 elementData,关于 ArrayList 的源码分析可见博客:ArrayList-源码解读

当然我们也可以自己用数组去实现一个线性表,先创建一个 ArrLinearList 类,因为不知道线性表具体会存放哪种数据类型,可以泛型 Element 来表示任意类型的数据,如下:

  1. public class ArrLinearList<Element> {
  2. }

(1)初始化

既然底层是数组,那么必然要有数组类型(Object[])的属性 data 来存放数据,通过 size 属性来记录线性表的大小。

在初始化的时候自然是要创建一个数组对象赋给 data,初始时线性表的长度 size0,在 Java 可以将初始化的逻辑放在构造方法中,代码如下:

  1. public class ArrLinearList<Element> {
  2. // 数据元素
  3. private Object[] data;
  4. // 线性表大小
  5. private int size;
  6. // 初始容量大小
  7. private static final int INIT_CAPACITY = 10;
  8. /**
  9. * 无参构造方法
  10. */
  11. public ArrLinearList() {
  12. init(INIT_CAPACITY);
  13. }
  14. /**
  15. * 有参构造方法
  16. * @param capacity 指定设置容量大小
  17. */
  18. public ArrLinearList(int capacity) {
  19. // 如果指定的容量大小小于或等于零,则初始化容量大小为默认容量大小
  20. if (capacity <= 0) {
  21. capacity = INIT_CAPACITY;
  22. }
  23. // 初始化
  24. init(capacity);
  25. }
  26. /**
  27. * 初始化
  28. * @param maxSize 数组的最大容量
  29. */
  30. private void init(int maxSize) {
  31. // 创建一个大小为 10 的 Object 数组
  32. data = new Object[maxSize];
  33. // 初始化时线性表的大小为 0
  34. size = 0;
  35. }
  36. }

(2)根据位序 index,查询相应的元素

因为底层时数组,可以通过数组的坐标直接得到位于 index 上的元素

  1. /**
  2. * 根据位序 index,返回相应元素
  3. * @param index 为序
  4. * @return 元素
  5. */
  6. // 压制不安全的类型转换的警告
  7. @SuppressWarnings("unchecked")
  8. public Element get(int index) {
  9. // 校验坐标是否合法
  10. if (index >= size || index < 0) {
  11. // 位置不合法
  12. throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
  13. }
  14. return (Element) data[index];
  15. }

(3)通过元素 Element,查找所在位置

分为两种情况,首先是当 elementnull 时,则查询数组中第一个为 null 元素的坐标位置,如果 element 不为 null,则遍历数组,通过 equals() 方法去比较,如果相同则返回坐标位置

  1. /**
  2. * 通过元素 Element,查找所在位置
  3. * @param element 元素
  4. * @return 所在位置
  5. */
  6. public int indexOf(Object element) {
  7. if (element == null) {
  8. // 遍历数组
  9. for (int i = 0; i < size; i++) {
  10. if (data[i] == null)
  11. return i;
  12. }
  13. } else {
  14. for (int i = 0; i < size; i++) {
  15. if (element.equals(data[i]))
  16. return i;
  17. }
  18. }
  19. return -1;
  20. }

(4)在指定位序 index 上插入一个新的元素

往线性表中 index 位置上添加一个元素(0 <= index <= size),分为两种情况

  • 情况一:index 等于线性表的大小,表示在末位添加元素,直接将 element 放到 index 位置上,线性表大小加一
  • 情况二:index 小于线性表的大小,则需要先将位于 index 以及之后的所有元素往后挪动一位,再将元素 element 放到 index 位置上,线性表大小加一

在这里插入图片描述

还有个需要考虑的问题就是当添加的元素数量大于 data 数组的容量时要怎么扩容,通常情况下的做法就是重新创建一个新的数组,将原数组的数据复制到新数组中,再进行元素的添加,原理如下:

在这里插入图片描述

  1. /**
  2. * 将指定元素添加到线性表的末位
  3. * @param element 元素
  4. */
  5. public void add(Element element) {
  6. add(size, element);
  7. }
  8. /**
  9. * 在指定位序上插入一个新的元素
  10. * @param index 位序
  11. * @param element 元素
  12. */
  13. public void add(int index, Element element) {
  14. // 如果插入元素的坐标大于线性表的大小
  15. if (index > size || index < 0) {
  16. // 位置不合法
  17. throw new RuntimeException("Index: "+index+", Size: "+size);
  18. }
  19. // 判断当前线性表的大小是否等于容量大小
  20. if (size == data.length) {
  21. // 如果插入当前元素后线性表的大小大于容器的大小,则进行扩容
  22. ensureCapacity();
  23. }
  24. // 判断插入位置是否小于线性表长度(头部或者中间插入)
  25. if (index < size) {
  26. // 先将线性表位于 index 以及之后的元素都往后挪一位
  27. for (int i = size; i > index; i--) {
  28. data[i] = data[i-1];
  29. }
  30. }
  31. // 在 index 位序上插入元素
  32. data[index] = element;
  33. // 线性表大小加一
  34. size++;
  35. }
  36. /**
  37. * 自动扩容方法
  38. */
  39. public void ensureCapacity() {
  40. // 新容量的大小是原容量大小的 1.5 倍
  41. int newCapacity = data.length + (data.length >> 1);
  42. // 创建新数组
  43. Object[] newDate = new Object[newCapacity];
  44. // 将原数组中的内容复制到新数组中
  45. for (int i = 0; i < data.length; i++) {
  46. newDate[i] = data[i];
  47. }
  48. // 将线性表存放数据的数组指向新的数组
  49. data = newDate;
  50. }

(5)删除指定位序 index 的元素

在线性表中如果需要删除指定位置 index 上的元素,只需要将 index 之后的元素都向前挪动一位即可,线性表大小减一

在这里插入图片描述

  1. /**
  2. * 删除指定位序的元素
  3. * @param index 位序
  4. */
  5. public void remove(int index) {
  6. // 如果删除元素的坐标大于等于或者小于线性表的大小
  7. if (index >= size || index < 0) {
  8. // 位置不合法
  9. throw new RuntimeException("Index: "+index+", Size: "+size);
  10. }
  11. // 判断删除的元素是否为末端元素
  12. if (index == size-1) {
  13. // 删除的是最后一个元素
  14. data[index] = null;
  15. } else {
  16. for (int i = index; i < size; i++) {
  17. data[i] = data[i+1];
  18. }
  19. }
  20. // 线性表大小减一
  21. size--;
  22. }
  23. /**
  24. * 从列表中删除指定元素的第一个出现(如果存在)
  25. * @param element 元素
  26. */
  27. public void remove(Element element) {
  28. int index = indexOf(element);
  29. if (index != -1) {
  30. remove(index);
  31. }
  32. }

(6)获取线性表的长度

上述代码每增加一个元素会让 size+1,每删除一个元素 size-1,所以直接获取 size 即为线性表的大小

  1. /**
  2. * 获取线性表大小
  3. * @return 线性表大小
  4. */
  5. public int size() {
  6. return size;
  7. }

完整代码示例:

  1. public class ArrLinearList<Element> {
  2. // 数据元素
  3. private Object[] data;
  4. // 线性表大小
  5. private int size;
  6. // 初始容量大小
  7. private static final int INIT_CAPACITY = 10;
  8. /**
  9. * 无参构造方法
  10. */
  11. public ArrLinearList() {
  12. init(INIT_CAPACITY);
  13. }
  14. /**
  15. * 有参构造方法
  16. * @param capacity 指定设置容量大小
  17. */
  18. public ArrLinearList(int capacity) {
  19. // 如果指定的容量大小小于或等于零,则初始化容量大小为默认容量大小
  20. if (capacity <= 0) {
  21. capacity = INIT_CAPACITY;
  22. }
  23. // 初始化
  24. init(capacity);
  25. }
  26. /**
  27. * 初始化
  28. * @param maxSize 数组的最大容量
  29. */
  30. private void init(int maxSize) {
  31. // 创建一个大小为 10 的 Object 数组
  32. data = new Object[maxSize];
  33. // 初始化时线性表的大小为 0
  34. size = 0;
  35. }
  36. /**
  37. * 根据位序 index,返回相应元素
  38. * @param index 为序
  39. * @return 元素
  40. */
  41. // 压制不安全的类型转换的警告
  42. @SuppressWarnings("unchecked")
  43. public Element get(int index) {
  44. // 校验坐标是否合法
  45. if (index >= size || index < 0) {
  46. // 位置不合法
  47. throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
  48. }
  49. return (Element) data[index];
  50. }
  51. /**
  52. * 通过元素 Element,查找所在位置
  53. * @param element 元素
  54. * @return 所在位置
  55. */
  56. public int indexOf(Object element) {
  57. if (element == null) {
  58. // 遍历数组
  59. for (int i = 0; i < size; i++) {
  60. if (data[i] == null)
  61. return i;
  62. }
  63. } else {
  64. for (int i = 0; i < size; i++) {
  65. if (element.equals(data[i]))
  66. return i;
  67. }
  68. }
  69. return -1;
  70. }
  71. /**
  72. * 将指定元素添加到线性表的末位
  73. * @param element 元素
  74. */
  75. public void add(Element element) {
  76. add(size, element);
  77. }
  78. /**
  79. * 在指定位序上插入一个新的元素
  80. * @param index 位序
  81. * @param element 元素
  82. */
  83. public void add(int index, Element element) {
  84. // 如果插入元素的坐标大于线性表的大小
  85. if (index > size || index < 0) {
  86. // 位置不合法
  87. throw new RuntimeException("Index: "+index+", Size: "+size);
  88. }
  89. // 判断当前线性表的大小是否等于容量大小
  90. if (size == data.length) {
  91. // 如果插入当前元素后线性表的大小大于容器的大小,则进行扩容
  92. ensureCapacity();
  93. }
  94. // 判断插入位置是否小于线性表长度(头部或者中间插入)
  95. if (index < size) {
  96. // 先将线性表位于 index 以及之后的元素都往后挪一位
  97. for (int i = size; i > index; i--) {
  98. data[i] = data[i-1];
  99. }
  100. }
  101. // 在 index 位序上插入元素
  102. data[index] = element;
  103. // 线性表大小加一
  104. size++;
  105. }
  106. /**
  107. * 自动扩容方法
  108. */
  109. public void ensureCapacity() {
  110. // 新容量的大小是原容量大小的 1.5 倍
  111. int newCapacity = data.length + (data.length >> 1);
  112. // 创建新数组
  113. Object[] newDate = new Object[newCapacity];
  114. // 将原数组中的内容复制到新数组中
  115. for (int i = 0; i < data.length; i++) {
  116. newDate[i] = data[i];
  117. }
  118. // 将线性表存放数据的数组指向新的数组
  119. data = newDate;
  120. }
  121. /**
  122. * 删除指定位序的元素
  123. * @param index 位序
  124. */
  125. public void remove(int index) {
  126. // 如果删除元素的坐标大于等于或者小于线性表的大小
  127. if (index >= size || index < 0) {
  128. // 位置不合法
  129. throw new RuntimeException("Index: "+index+", Size: "+size);
  130. }
  131. // 判断删除的元素是否为末端元素
  132. if (index == size-1) {
  133. // 删除的是最后一个元素
  134. data[index] = null;
  135. } else {
  136. for (int i = index; i < size; i++) {
  137. data[i] = data[i+1];
  138. }
  139. }
  140. // 线性表大小减一
  141. size--;
  142. }
  143. /**
  144. * 从列表中删除指定元素的第一个出现(如果存在)
  145. * @param element 元素
  146. */
  147. public void remove(Element element) {
  148. int index = indexOf(element);
  149. if (index != -1) {
  150. remove(index);
  151. }
  152. }
  153. /**
  154. * 用指定的元素替换此列表中指定位置的元素
  155. * @param index 指定位置
  156. * @param element 指定元素
  157. * @return 替换掉的元素
  158. */
  159. @SuppressWarnings("unchecked")
  160. public Element set(int index, Element element) {
  161. // 校验坐标是否合法
  162. if (index >= size || index < 0) {
  163. // 位置不合法
  164. throw new RuntimeException("Index: "+index+", Size: "+size);
  165. }
  166. Element oldElement =(Element) data[index];
  167. data[index] = element;
  168. return oldElement;
  169. }
  170. /**
  171. * 判断指定元素是否在线性表中
  172. * @return true 表示存在 false 表示不存在
  173. */
  174. public boolean contains(Element element) {
  175. return indexOf(element) != -1;
  176. }
  177. /**
  178. * 获取线性表大小
  179. * @return 线性表大小
  180. */
  181. public int size() {
  182. return size;
  183. }
  184. /**
  185. * 判断当前线性表是否为空
  186. * @return true 表示为空 false 表示不为空
  187. */
  188. public boolean isEmpty() {
  189. return size == 0;
  190. }
  191. /**
  192. * 返回当前线性表的字符串表示
  193. * @return 线性表字符串表示
  194. */
  195. public String toString() {
  196. StringBuilder str = new StringBuilder("[");
  197. for (int i = 0; i < size-1; i++) {
  198. str.append(this.data[i]).append(",");
  199. }
  200. if (size > 0) {
  201. str.append(this.data[size - 1]);
  202. }
  203. str.append("]");
  204. return str.toString();
  205. }
  206. }

2.3.2 线性表的链式存储实现

不要求逻辑上相邻的两个元素物理上也相邻,通过 “链” 建立起数据元素之间的逻辑关系。

插入、删除不需要移动数据元素,只需修改 “链” 即可。

在这里插入图片描述

链表主要可分为:单向链表双向链表循环链表

  • 单向链表:每个节点只设置一个指向后继节点的指针成员,这样的链表成为线性单向链接表,简称单链表
    在这里插入图片描述
  • 双向链表:每个节点设置两个指针成员,分别用以指向其前驱节点和后继节点,这样的链表称之为线性双向链接表,简称双链表
    在这里插入图片描述

Java 中非常典型的集合类 LinkedList ,其底层使用的就是双向链表,关于 LinkedList 的源码分析可见博客:LinkedList-源码解读

同样我们也可以自己用链表结构实现一个线性表,创建一个 LinkLinearList 类,使用泛型 Element 去约束数据类型

  1. public class LinkLinearList<Element> {
  2. }

1. 单链表实现

(1)初始化

因为底层采用的是链表结构,故在该类内部定义一个内部类 Node,存放链表的节点信息,元素 data 及其后继节点 next,在 LinkLinearList 中设置属性 headertail 分别记录头节点和尾节点,在构造方法中初始化线性表大小为 0

  1. public class LinkLinearList<Element> {
  2. // 头节点
  3. private Node<Element> header;
  4. // 尾节点
  5. private Node<Element> tail;
  6. // 线性表的长度
  7. private int size;
  8. /**
  9. * 节点
  10. * @param <Element> 数据元素类型
  11. */
  12. public static class Node<Element> {
  13. // 数据元素
  14. private Element data;
  15. // 后继节点
  16. private Node<Element> next;
  17. }
  18. /**
  19. * 无参构造方法
  20. */
  21. public LinkLinearList() {
  22. // 初始化线性表的长度
  23. size = 0;
  24. }
  25. }

(2)根据位序 index,查询相应的元素

因为底层是链表,如果想要获取某个 index 位置上的数据,只能遍历链表,得到 index 位置上的节点,再返回节点数据

  1. /**
  2. * 根据位序 index,返回相应元素
  3. * @param index 为序
  4. * @return 元素
  5. */
  6. public Element get(int index) {
  7. if (index >= size) {
  8. // 位序大于线性表的大小
  9. throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
  10. }
  11. // 返回指定位序节点下的数据元素
  12. return findNode(index).data;
  13. }
  14. // 通过位序获取节点
  15. public Node<Element> findNode(int index) {
  16. // 目标节点
  17. Node<Element> target = null;
  18. // 遍历链表(可采用二分法进行优化)
  19. Node<Element> node = header;
  20. for (int i = 0; i < size; i++) {
  21. if (i == index) {
  22. target = node;
  23. break;
  24. }
  25. node = node.next;
  26. }
  27. // 返回对应节点
  28. return target;
  29. }

(3)通过元素 Element,查找所在位置

分为两种情况,首先是当 elementnull 时,则查询l链表中第一个为 null 元素的坐标位置,如果 element 不为 null,则遍历链表,通过 equals() 方法去比较,如果相同则返回坐标位置

  1. /**
  2. * 通过元素 Element,查找所在位置
  3. * @param element 元素
  4. * @return 所在位置
  5. */
  6. public int indexOf(Object element) {
  7. int index = 0;
  8. Node<Element> node = header;
  9. if (element == null) {
  10. // 遍历链表
  11. do {
  12. if (node.data == null)
  13. return index;
  14. index++;
  15. node = node.next;
  16. } while (node != null);
  17. } else {
  18. // 遍历链表
  19. do {
  20. if (element.equals(node.data))
  21. return index;
  22. index++;
  23. node = node.next;
  24. } while (node != null);
  25. }
  26. return -1;
  27. }

(4)在指定位序 index 上插入一个新的元素

在链表中添加元素可分为三种情况:

  • 情况一:末位插入,这种情况比较简单,只需要将原末端节点指向新插入的节点就行了

在这里插入图片描述

  • 情况二:头部插入,新添加的节点成为头节点,该节点的后驱节点指向原头节点

在这里插入图片描述

  • 情况三:中间插入,原位置上的前驱节点变为新插入节点的前驱节点,原节点变为新插入节点的后驱节点

在这里插入图片描述

  1. /**
  2. * 将指定元素添加到线性表的末位
  3. * @param element 元素
  4. */
  5. public void add(Element element) {
  6. add(size, element);
  7. }
  8. /**
  9. * 在指定位序上插入一个新的元素
  10. * @param index 位序
  11. * @param element 元素
  12. */
  13. public void add(int index, Element element) {
  14. // 校验插入位置是否合法:位置范围必须在 0 <= index <= size 这个区间内
  15. if (!(index >= 0 && index <= size)) {
  16. // 位置不合法
  17. throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
  18. }
  19. // 末端插入
  20. if (index == size) {
  21. // 原尾部节点
  22. Node<Element> last = tail;
  23. // 新插入节点
  24. Node<Element> newNode = new Node<>();
  25. // 设置节点数据
  26. newNode.data = element;
  27. // 尾部节点变为新插入的节点
  28. tail = newNode;
  29. if (last == null) {
  30. // 如果原尾部节点为空,则表示当前链表没有元素,新插入的节点既是头节点也是尾节点
  31. header = newNode;
  32. } else {
  33. // 设置原尾部节点的下一个节点为新插入的节点
  34. last.next = newNode;
  35. }
  36. }
  37. // 头部或者中间插入
  38. else {
  39. // 获取 index-1 位序的节点
  40. Node<Element> beforeNode = findNode(index-1);
  41. // 获取原 index 位序的节点
  42. Node<Element> originNode = findNode(index);
  43. // 新插入节点
  44. Node<Element> newNode = new Node<>();
  45. // 设置节点数据
  46. newNode.data = element;
  47. // 设置新插入的节点的 next 为原节点
  48. newNode.next = originNode;
  49. if (beforeNode == null) {
  50. // 表明在头部插入节点
  51. header = newNode;
  52. } else {
  53. // 表明在中间插入节点,则原 index-1 位置上的节点 next 设置为当前插入的节点
  54. beforeNode.next = newNode;
  55. }
  56. }
  57. // 线性表大小加一
  58. size++;
  59. }

(5)删除指定位序 index 的元素

在链表中删除指定位序的数据就是删除该位置的节点,所以在删除时要注意原节点的前驱节点 beforeNode 应该变为原节点的后驱节点 afterNode,同时也应该要注意删除的是否时头节点或者尾节点
在这里插入图片描述

  1. /**
  2. * 删除指定位序的元素
  3. * @param index 位序
  4. */
  5. public void remove(int index) {
  6. // 校验插入位置是否合法:位置范围必须在 0 <= index <= size 这个区间内
  7. if (!(index >= 0 && index < size)) {
  8. // 位置不合法
  9. throw new RuntimeException("Index: "+index+", Size: "+size);
  10. }
  11. // 获取 index-1 (前)位序的节点
  12. Node<Element> beforeNode = findNode(index-1);
  13. // 获取原 index+1 (后)位序的节点
  14. Node<Element> afterNode = findNode(index+1);
  15. if (beforeNode == null) {
  16. // 如果删除的节点没有前驱节点,则说明删除的节点为头节点,则后置节点设置为头节点
  17. header = afterNode;
  18. } else {
  19. // 删除的节点不是头节点,前置节点的下一个节点设置为后置节点
  20. beforeNode.next = afterNode;
  21. }
  22. // 线性表大小减一
  23. size--;
  24. }
  25. /**
  26. * 从列表中删除指定元素的第一个出现(如果存在)
  27. * @param element 元素
  28. */
  29. public void remove(Element element) {
  30. int index = indexOf(element);
  31. if (index != -1) {
  32. remove(index);
  33. }
  34. }

(6)获取线性表的长度

上述代码每增加一个元素会让 size+1,每删除一个元素 size-1,所以直接获取 size 即为线性表的大小

  1. /**
  2. * 获取线性表大小
  3. * @return 线性表大小
  4. */
  5. public int size() {
  6. return size;
  7. }

完整代码示例:

  1. public class LinkLinearList<Element> {
  2. // 头节点
  3. private Node<Element> header;
  4. // 尾节点
  5. private Node<Element> tail;
  6. // 线性表的长度
  7. private int size;
  8. /**
  9. * 节点
  10. * @param <Element> 数据元素类型
  11. */
  12. public static class Node<Element> {
  13. // 数据元素
  14. private Element data;
  15. // 后继节点
  16. private Node<Element> next;
  17. }
  18. /**
  19. * 无参构造方法
  20. */
  21. public LinkLinearList() {
  22. // 初始化线性表的长度
  23. size = 0;
  24. }
  25. /**
  26. * 根据位序 index,返回相应元素
  27. * @param index 为序
  28. * @return 元素
  29. */
  30. public Element get(int index) {
  31. if (index >= size) {
  32. // 位序大于线性表的大小
  33. throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
  34. }
  35. // 返回指定位序节点下的数据元素
  36. return findNode(index).data;
  37. }
  38. // 通过位序获取节点
  39. public Node<Element> findNode(int index) {
  40. // 目标节点
  41. Node<Element> target = null;
  42. // 遍历链表(可采用二分法进行优化)
  43. Node<Element> node = header;
  44. for (int i = 0; i < size; i++) {
  45. if (i == index) {
  46. target = node;
  47. break;
  48. }
  49. node = node.next;
  50. }
  51. // 返回对应节点
  52. return target;
  53. }
  54. /**
  55. * 通过元素 Element,查找所在位置
  56. * @param element 元素
  57. * @return 所在位置
  58. */
  59. public int indexOf(Object element) {
  60. int index = 0;
  61. Node<Element> node = header;
  62. if (element == null) {
  63. // 遍历链表
  64. do {
  65. if (node.data == null)
  66. return index;
  67. index++;
  68. node = node.next;
  69. } while (node != null);
  70. } else {
  71. // 遍历链表
  72. do {
  73. if (element.equals(node.data))
  74. return index;
  75. index++;
  76. node = node.next;
  77. } while (node != null);
  78. }
  79. return -1;
  80. }
  81. /**
  82. * 将指定元素添加到线性表的末位
  83. * @param element 元素
  84. */
  85. public void add(Element element) {
  86. add(size, element);
  87. }
  88. /**
  89. * 在指定位序上插入一个新的元素
  90. * @param index 位序
  91. * @param element 元素
  92. */
  93. public void add(int index, Element element) {
  94. // 校验插入位置是否合法:位置范围必须在 0 <= index <= size 这个区间内
  95. if (!(index >= 0 && index <= size)) {
  96. // 位置不合法
  97. throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
  98. }
  99. // 末端插入
  100. if (index == size) {
  101. // 原尾部节点
  102. Node<Element> last = tail;
  103. // 新插入节点
  104. Node<Element> newNode = new Node<>();
  105. // 设置节点数据
  106. newNode.data = element;
  107. // 尾部节点变为新插入的节点
  108. tail = newNode;
  109. if (last == null) {
  110. // 如果原尾部节点为空,则表示当前链表没有元素,新插入的节点既是头节点也是尾节点
  111. header = newNode;
  112. } else {
  113. // 设置原尾部节点的下一个节点为新插入的节点
  114. last.next = newNode;
  115. }
  116. }
  117. // 头部或者中间插入
  118. else {
  119. // 获取 index-1 位序的节点
  120. Node<Element> beforeNode = findNode(index-1);
  121. // 获取原 index 位序的节点
  122. Node<Element> originNode = findNode(index);
  123. // 新插入节点
  124. Node<Element> newNode = new Node<>();
  125. // 设置节点数据
  126. newNode.data = element;
  127. // 设置新插入的节点的 next 为原节点
  128. newNode.next = originNode;
  129. if (beforeNode == null) {
  130. // 表明在头部插入节点
  131. header = newNode;
  132. } else {
  133. // 表明在中间插入节点,则原 index-1 位置上的节点 next 设置为当前插入的节点
  134. beforeNode.next = newNode;
  135. }
  136. }
  137. // 线性表大小加一
  138. size++;
  139. }
  140. /**
  141. * 删除指定位序的元素
  142. * @param index 位序
  143. */
  144. public void remove(int index) {
  145. // 校验插入位置是否合法:位置范围必须在 0 <= index <= size 这个区间内
  146. if (!(index >= 0 && index < size)) {
  147. // 位置不合法
  148. throw new RuntimeException("Index: "+index+", Size: "+size);
  149. }
  150. // 获取 index-1 (前)位序的节点
  151. Node<Element> beforeNode = findNode(index-1);
  152. // 获取原 index+1 (后)位序的节点
  153. Node<Element> afterNode = findNode(index+1);
  154. if (beforeNode == null) {
  155. // 如果删除的节点没有前驱节点,则说明删除的节点为头节点,则后置节点设置为头节点
  156. header = afterNode;
  157. } else {
  158. // 删除的节点不是头节点,前置节点的下一个节点设置为后置节点
  159. beforeNode.next = afterNode;
  160. }
  161. // 线性表大小减一
  162. size--;
  163. }
  164. /**
  165. * 从列表中删除指定元素的第一个出现(如果存在)
  166. * @param element 元素
  167. */
  168. public void remove(Element element) {
  169. int index = indexOf(element);
  170. if (index != -1) {
  171. remove(index);
  172. }
  173. }
  174. /**
  175. * 用指定的元素替换此列表中指定位置的元素
  176. * @param index 指定位置
  177. * @param element 指定元素
  178. * @return 替换掉的元素
  179. */
  180. public Element set(int index, Element element) {
  181. // 校验坐标是否合法
  182. if (index >= size || index < 0) {
  183. // 位置不合法
  184. throw new RuntimeException("Index: "+index+", Size: "+size);
  185. }
  186. Node<Element> node = findNode(index);
  187. Element oldElement = node.data;
  188. // 重置数据
  189. node.data = element;
  190. return oldElement;
  191. }
  192. /**
  193. * 判断指定元素是否在线性表中
  194. * @return true 表示存在 false 表示不存在
  195. */
  196. public boolean contains(Element element) {
  197. return indexOf(element) != -1;
  198. }
  199. /**
  200. * 获取线性表大小
  201. * @return 线性表大小
  202. */
  203. public int size() {
  204. return size;
  205. }
  206. /**
  207. * 判断当前线性表是否为空
  208. * @return true 表示为空 false 表示不为空
  209. */
  210. public boolean isEmpty() {
  211. return size == 0;
  212. }
  213. /**
  214. * 返回当前线性表的字符串表示
  215. * @return 线性表字符串表示
  216. */
  217. public String toString() {
  218. StringBuilder str = new StringBuilder("[");
  219. if (size > 0) {
  220. // 遍历链表
  221. Node<Element> cursor = header;
  222. str.append(cursor.data);
  223. while (cursor.next != null) {
  224. cursor = cursor.next;
  225. str.append(",").append(cursor.data);
  226. }
  227. }
  228. str.append("]");
  229. return str.toString();
  230. }
  231. }

2. 双链表实现

之前提到过 LinkedList 的底层就是使用双向链表实现的,与单向链表不同,双向链表的节点不仅需要记录后继节点,也需要记录前驱节点,实现方法基本类似。

(1)初始化

因为要记录前驱节点,所以相比单向链表多了一个 prev 属性来记录前驱节点,其余属性与单向链表相同

  1. public class DoubleLinkList<Element> {
  2. // 头节点
  3. private Node<Element> header;
  4. // 尾节点
  5. private Node<Element> tail;
  6. // 线性表的长度
  7. private int size;
  8. /**
  9. * 节点
  10. * @param <Element> 数据元素类型
  11. */
  12. public static class Node<Element> {
  13. // 数据元素
  14. private Element data;
  15. // 前驱节点
  16. private Node<Element> prev;
  17. // 后继节点
  18. private Node<Element> next;
  19. }
  20. /**
  21. * 无参构造方法
  22. */
  23. public DoubleLinkList() {
  24. // 初始化线性表的长度
  25. size = 0;
  26. }
  27. }

(2)根据位序 index,查询相应的元素

与单链表的逻辑差不都,都是对链表进行遍历,但是双链表不仅可以正向遍历,也能反向遍历

  1. /**
  2. * 根据位序 index,返回相应元素
  3. * @param index 为序
  4. * @return 元素
  5. */
  6. public Element get(int index) {
  7. if (index >= size) {
  8. // 位序大于线性表的大小
  9. throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
  10. }
  11. // 返回指定位序节点下的数据元素
  12. return findNode(index).data;
  13. }
  14. // 通过位序获取节点
  15. public Node<Element> findNode(int index) {
  16. // 二分法查找
  17. Node<Element> node;
  18. if (index < (size >> 1)) {
  19. node = header;
  20. for (int i = 0; i < index; i++) {
  21. node = node.next;
  22. }
  23. } else {
  24. node = tail;
  25. for (int i = size -1; i > index; i--) {
  26. node = node.prev;
  27. }
  28. }
  29. return node;
  30. }

(3)通过元素 Element,查找所在位置

  1. /**
  2. * 通过元素 Element,查找所在位置
  3. * @param element 元素
  4. * @return 所在位置
  5. */
  6. public int indexOf(Object element) {
  7. int index = 0;
  8. Node<Element> node = header;
  9. if (element == null) {
  10. // 遍历链表
  11. do {
  12. if (node.data == null)
  13. return index;
  14. index++;
  15. node = node.next;
  16. } while (node != null);
  17. } else {
  18. // 遍历链表
  19. do {
  20. if (element.equals(node.data))
  21. return index;
  22. index++;
  23. node = node.next;
  24. } while (node != null);
  25. }
  26. return -1;
  27. }

(4)在指定位序 index 上插入一个新的元素

在双向链表中添加元素也可分为三种情况:

  • 情况一:末位插入,新插入的节点 NodeX 成为链表的尾节点,新节点的前驱节点 prev 指向原链表的尾节点Node... ,原链表的尾节点 Node... 的后驱节点 next 指向新插入的节点 NodeX

cb11ca9c7cdb4dc7a1d4825be84421c0.png

  • 情况二:头部插入,新插入的节点 NodeX 变成了头节点,所以 NodeXNext 要指向原头节点 Node1NodeXprevnull

3c4379c731b848158ee9960818ba23f4.png

  • 情况三:中间插入,新插入的节点 NodeX 的前驱节点为原指定节点的前驱节点,原指定位置上的节点变成了新插入节点的后驱节点了

83cc1a7ab2ed410a954cd48e921fa291.png

  1. /**
  2. * 将指定元素添加到线性表的末位
  3. * @param element 元素
  4. */
  5. public void add(Element element) {
  6. add(size, element);
  7. }
  8. /**
  9. * 在指定位序上插入一个新的元素
  10. * @param index 位序
  11. * @param element 元素
  12. */
  13. public void add(int index, Element element) {
  14. // 校验插入位置是否合法:位置范围必须在 0 <= index <= size 这个区间内
  15. if (!(index >= 0 && index <= size)) {
  16. // 位置不合法
  17. throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
  18. }
  19. // 末端插入
  20. if (index == size) {
  21. // 原尾部节点
  22. Node<Element> last = tail;
  23. // 新插入节点
  24. Node<Element> newNode = new Node<>();
  25. // 设置节点数据
  26. newNode.data = element;
  27. // 尾部节点变为新插入的节点
  28. tail = newNode;
  29. if (last == null) {
  30. // 如果原尾部节点为空,则表示当前链表没有元素,新插入的节点既是头节点也是尾节点
  31. header = newNode;
  32. } else {
  33. // 设置原尾部节点的下一个节点为新插入的节点
  34. last.next = newNode;
  35. // 设置新插入节点的前驱节点为原尾部节点
  36. newNode.prev = last;
  37. }
  38. }
  39. // 头部或者中间插入
  40. else {
  41. // 获取原 index 位序的节点
  42. Node<Element> originNode = findNode(index);
  43. // 新插入节点
  44. Node<Element> newNode = new Node<>();
  45. // 设置节点数据
  46. newNode.data = element;
  47. // 设置新插入的节点的 next 为原节点
  48. newNode.next = originNode;
  49. // 新插入节点的 prev 为原节点的前驱节点
  50. newNode.prev = originNode.prev;
  51. // 判断原节点是否为头节点
  52. if (originNode.prev == null) {
  53. // 是,则表明在头部插入节点,新节点成为头节点
  54. header = newNode;
  55. } else {
  56. // 原节点的前驱节点的后继节点为新插入的节点
  57. originNode.prev.next = newNode;
  58. }
  59. // 原节点的前驱节点设置为新插入的节点
  60. originNode.prev = newNode;
  61. }
  62. // 线性表大小加一
  63. size++;
  64. }

(5)删除指定位序 index 的元素

删除链表头部节点也需要分为两种情况去看待:

  • 集合大小 = 1 时,此时删除第一个节点之后,链表就没有节点了,所以链表的头节点和尾节点都需要设置为 null
  • 集合大小 > 1 时,原头节点 Node1 的后驱节点 Next 指向 null,而它的下一个节点 Node2 变成头节点,Node2 的 前驱节点 Pre 指向 null

93cd514d5ccf43e7819954b531b3774d.png

  1. /**
  2. * 删除指定位序的元素
  3. * @param index 位序
  4. */
  5. public void remove(int index) {
  6. // 校验插入位置是否合法:位置范围必须在 0 <= index <= size 这个区间内
  7. if (!(index >= 0 && index < size)) {
  8. // 位置不合法
  9. throw new RuntimeException("Index: "+index+", Size: "+size);
  10. }
  11. // 获取 index 处的节点
  12. Node<Element> node = findNode(index);
  13. // 获取该节点的前驱节点
  14. Node<Element> prevNode = node.prev;
  15. // 获取该节点的后继节点
  16. Node<Element> nextNode = node.next;
  17. if (prevNode == null) {
  18. // 如果前驱节点为空,则说明删除的节点为头节点,则后继节点设置为头节点
  19. header = nextNode;
  20. nextNode.prev = null;
  21. } else if (nextNode == null) {
  22. // 如果后继节点为空,则说明删除的节点为尾节点,则前驱节点设置为为节点
  23. tail = node.prev;
  24. prevNode.next = null;
  25. } else {
  26. // 删除的节点不是头节点,则原前驱节点的下一个节点设置为原后继节点
  27. prevNode.next = nextNode;
  28. // 原后继节点的上一个节点设置为原前驱节点
  29. nextNode.prev = prevNode;
  30. }
  31. // 线性表大小减一
  32. size--;
  33. }
  34. /**
  35. * 从列表中删除指定元素的第一个出现(如果存在)
  36. * @param element 元素
  37. */
  38. public void remove(Element element) {
  39. int index = indexOf(element);
  40. if (index != -1) {
  41. remove(index);
  42. }
  43. }

(6)获取线性表的长度

  1. /**
  2. * 获取线性表大小
  3. * @return 线性表大小
  4. */
  5. public int size() {
  6. return size;
  7. }

完整代码示例:

  1. public class DoubleLinkList<Element> {
  2. // 头节点
  3. private Node<Element> header;
  4. // 尾节点
  5. private Node<Element> tail;
  6. // 线性表的长度
  7. private int size;
  8. /**
  9. * 节点
  10. * @param <Element> 数据元素类型
  11. */
  12. public static class Node<Element> {
  13. // 数据元素
  14. private Element data;
  15. // 前驱节点
  16. private Node<Element> prev;
  17. // 后继节点
  18. private Node<Element> next;
  19. }
  20. /**
  21. * 无参构造方法
  22. */
  23. public DoubleLinkList() {
  24. // 初始化线性表的长度
  25. size = 0;
  26. }
  27. /**
  28. * 根据位序 index,返回相应元素
  29. * @param index 为序
  30. * @return 元素
  31. */
  32. public Element get(int index) {
  33. if (index >= size) {
  34. // 位序大于线性表的大小
  35. throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
  36. }
  37. // 返回指定位序节点下的数据元素
  38. return findNode(index).data;
  39. }
  40. // 通过位序获取节点
  41. public Node<Element> findNode(int index) {
  42. // 二分法查找
  43. Node<Element> node;
  44. if (index < (size >> 1)) {
  45. node = header;
  46. for (int i = 0; i < index; i++) {
  47. node = node.next;
  48. }
  49. } else {
  50. node = tail;
  51. for (int i = size -1; i > index; i--) {
  52. node = node.prev;
  53. }
  54. }
  55. return node;
  56. }
  57. /**
  58. * 通过元素 Element,查找所在位置
  59. * @param element 元素
  60. * @return 所在位置
  61. */
  62. public int indexOf(Object element) {
  63. int index = 0;
  64. Node<Element> node = header;
  65. if (element == null) {
  66. // 遍历链表
  67. do {
  68. if (node.data == null)
  69. return index;
  70. index++;
  71. node = node.next;
  72. } while (node != null);
  73. } else {
  74. // 遍历链表
  75. do {
  76. if (element.equals(node.data))
  77. return index;
  78. index++;
  79. node = node.next;
  80. } while (node != null);
  81. }
  82. return -1;
  83. }
  84. /**
  85. * 将指定元素添加到线性表的末位
  86. * @param element 元素
  87. */
  88. public void add(Element element) {
  89. add(size, element);
  90. }
  91. /**
  92. * 在指定位序上插入一个新的元素
  93. * @param index 位序
  94. * @param element 元素
  95. */
  96. public void add(int index, Element element) {
  97. // 校验插入位置是否合法:位置范围必须在 0 <= index <= size 这个区间内
  98. if (!(index >= 0 && index <= size)) {
  99. // 位置不合法
  100. throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
  101. }
  102. // 末端插入
  103. if (index == size) {
  104. // 原尾部节点
  105. Node<Element> last = tail;
  106. // 新插入节点
  107. Node<Element> newNode = new Node<>();
  108. // 设置节点数据
  109. newNode.data = element;
  110. // 尾部节点变为新插入的节点
  111. tail = newNode;
  112. if (last == null) {
  113. // 如果原尾部节点为空,则表示当前链表没有元素,新插入的节点既是头节点也是尾节点
  114. header = newNode;
  115. } else {
  116. // 设置原尾部节点的下一个节点为新插入的节点
  117. last.next = newNode;
  118. // 设置新插入节点的前驱节点为原尾部节点
  119. newNode.prev = last;
  120. }
  121. }
  122. // 头部或者中间插入
  123. else {
  124. // 获取原 index 位序的节点
  125. Node<Element> originNode = findNode(index);
  126. // 新插入节点
  127. Node<Element> newNode = new Node<>();
  128. // 设置节点数据
  129. newNode.data = element;
  130. // 设置新插入的节点的 next 为原节点
  131. newNode.next = originNode;
  132. // 新插入节点的 prev 为原节点的前驱节点
  133. newNode.prev = originNode.prev;
  134. // 判断原节点是否为头节点
  135. if (originNode.prev == null) {
  136. // 是,则表明在头部插入节点,新节点成为头节点
  137. header = newNode;
  138. } else {
  139. // 原节点的前驱节点的后继节点为新插入的节点
  140. originNode.prev.next = newNode;
  141. }
  142. // 原节点的前驱节点设置为新插入的节点
  143. originNode.prev = newNode;
  144. }
  145. // 线性表大小加一
  146. size++;
  147. }
  148. /**
  149. * 删除指定位序的元素
  150. * @param index 位序
  151. */
  152. public void remove(int index) {
  153. // 校验插入位置是否合法:位置范围必须在 0 <= index <= size 这个区间内
  154. if (!(index >= 0 && index < size)) {
  155. // 位置不合法
  156. throw new RuntimeException("Index: "+index+", Size: "+size);
  157. }
  158. // 获取 index 处的节点
  159. Node<Element> node = findNode(index);
  160. // 获取该节点的前驱节点
  161. Node<Element> prevNode = node.prev;
  162. // 获取该节点的后继节点
  163. Node<Element> nextNode = node.next;
  164. if (prevNode == null) {
  165. // 如果前驱节点为空,则说明删除的节点为头节点,则后继节点设置为头节点
  166. header = nextNode;
  167. nextNode.prev = null;
  168. } else if (nextNode == null) {
  169. // 如果后继节点为空,则说明删除的节点为尾节点,则前驱节点设置为为节点
  170. tail = node.prev;
  171. prevNode.next = null;
  172. } else {
  173. // 删除的节点不是头节点,则原前驱节点的下一个节点设置为原后继节点
  174. prevNode.next = nextNode;
  175. // 原后继节点的上一个节点设置为原前驱节点
  176. nextNode.prev = prevNode;
  177. }
  178. // 线性表大小减一
  179. size--;
  180. }
  181. /**
  182. * 从列表中删除指定元素的第一个出现(如果存在)
  183. * @param element 元素
  184. */
  185. public void remove(Element element) {
  186. int index = indexOf(element);
  187. if (index != -1) {
  188. remove(index);
  189. }
  190. }
  191. /**
  192. * 用指定的元素替换此列表中指定位置的元素
  193. * @param index 指定位置
  194. * @param element 指定元素
  195. * @return 替换掉的元素
  196. */
  197. public Element set(int index, Element element) {
  198. // 校验坐标是否合法
  199. if (index >= size || index < 0) {
  200. // 位置不合法
  201. throw new RuntimeException("Index: "+index+", Size: "+size);
  202. }
  203. Node<Element> node = findNode(index);
  204. Element oldElement = node.data;
  205. // 重置数据
  206. node.data = element;
  207. return oldElement;
  208. }
  209. /**
  210. * 判断指定元素是否在线性表中
  211. * @return true 表示存在 false 表示不存在
  212. */
  213. public boolean contains(Element element) {
  214. return indexOf(element) != -1;
  215. }
  216. /**
  217. * 获取线性表大小
  218. * @return 线性表大小
  219. */
  220. public int size() {
  221. return size;
  222. }
  223. /**
  224. * 判断当前线性表是否为空
  225. * @return true 表示为空 false 表示不为空
  226. */
  227. public boolean isEmpty() {
  228. return size == 0;
  229. }
  230. /**
  231. * 返回当前线性表的字符串表示
  232. * @return 线性表字符串表示
  233. */
  234. public String toString() {
  235. StringBuilder str = new StringBuilder("[");
  236. if (size > 0) {
  237. // 遍历链表
  238. Node<Element> cursor = header;
  239. str.append(cursor.data);
  240. while (cursor.next != null) {
  241. cursor = cursor.next;
  242. str.append(",").append(cursor.data);
  243. }
  244. }
  245. str.append("]");
  246. return str.toString();
  247. }
  248. }

上篇:第一章、绪论

发表评论

表情:
评论列表 (有 0 条评论,146人围观)

还没有评论,来说两句吧...

相关阅读