Q: 为什么要引入链表的概念?它是解决什么问题的?

A: 数组作为数据存储结构有一定的缺陷,在无序数组中,搜索是低效的;而在有序数组中,插入效率又很低;不管在哪一个数组中删除效率都很低;况且一个数组创建后,它的大小是不可改变的。

A: 在本篇中,我们将学习一种新的数据结构 —— 链表,它可以解决上面的一些问题,链表可能是继数组之后第二种使用最广泛的通用存储结构了。

Q: 结点?

A: 在链表中,每个数据项都被包含在“结点”中,可以使用Node, 或者Entry等名词来表示结点,本篇使用Entry来表示。每个Entry对象中包含一个对下一个结点引用的字段(通常叫做next),单链表中每个结点的结构图如下: 
 
定义单链表结点的类定义如下:

class Entry<E> {
E mElement;
Entry<E> mNext; public Entry(E element, Entry<E> next) {
mElement = element;
mNext = next;
}
}

Q: 单链表?

A: 构成链表的结点只有一个指向后继结点的指针域。

Q: 单链表的Java实现?

A: 示例:SingleLinkedList.java

A: LinkedList类只包含一个数据项mHeader,叫做表头:即对链表中第一个节点的引用。它是唯一的链表需要维护的永久信息,用以定位所有其他的链接点。从mHeader出发,沿着链表通过每个结点的mNext字段,就可以找到其他的结点。

A: addFirst()方法 —— 作用是在表头插入一个新结点。

A: removeFirst()方法 —— 是addFirst()方法的逆操作,它通过把mHeader重新指向第二个结点,断开了和第一个结点的连接。 
 
在C++中,从链表取下一个结点后,需要考虑如何释放这个结点。它仍然在内存中的某个地方,但是现在没有任何指针指向它,将如何处理它呢?在Java中,垃圾回收(GC)将在未来的某个时刻销毁它,现在这不是程序员操心的工作。 
注意,removeFirst()方法假定链表不是空的,因此调用它之前,应该首先调用empty()方法核实这一点。

Q: 如何查找和删除指定的结点?

A: indexOf(Object)方法 —— 返回此列表中首次出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。

get(int)方法 —— 返回此列表中指定位置处的元素。

A: remove(Object) —— 从此列表中移除首次出现的指定元素(如果存在)。 
先搜索要删除的结点,如果找到了,必须把前一个结点和后一个结点连起来,知道前一个结点的唯一方法就是拥有一个对它的引用previous(每当current变量赋值为current.next之前,先把previous变量赋值为current)。 

A: 示例: SingleLinkedList.java

Q: 双端链表?

A: 双端链表(double-ended list )是在上边的单链表基础上加了一个表尾,即对最后一个结点的引用。如下图: 

A: 对最后一个结点的引用允许像表头一样,在表尾直接插入一个结点。当然,仍然可以在普通的单链表的表尾插入一个结点,方法是遍历整个链表直到到达表尾,但是这种方法效率很低。

Q: 双端链表的Java实现?

A: 示例: DoubleEndedList.java

A: DoubleEndedList有两个项,header和tailer,一个指向链表中的第一个结点,另一个指向最后一个结点。

A: 如果链表中只有一个结点,header和last都指向它。如果没有结点,两个都为null值。

A: 如果链表只有一个结点,删除时tailer必须被赋值为null。

A: addLast()方法 —— 在表尾插入一个新结点。

Q: 链表的效率?

A: 在表头插入和删除速度很快,仅需要改变一两个引用值,所以花费O(1)的时间。

A: 查找、删除和在指定结点的前面插入都需要搜索链表中一半的结点,需要O(N)次比较,在数组中执行这些操作也需要O(N)次比较。但是链表仍然要快一些,因为插入和删除结点时,链表不需要移动任何东西。

A: 链表比数组还有一个优点是,链表需要多少内存就可以用多少内存,不像数组在创建时大小就固定了。

A: 向量是一种可扩展的数组,它可以通过可变长度解决这个问题,但是它经常只允许以固定的增量扩展(比如快要溢出的时候,就增加1倍的数组容量)。这个解决方案在内存使用效率上来说还是要比链表低。

Q: 用链表实现的栈?

A: 示例:Stack.java

A: 栈的使用者不需要知道栈用的是链表还是数组实现。 因此Stack类的测试用例在这两个上是没有分别的。

Q: 用链表实现的队列?

A: 示例:Queue.java

A: 展示了一个用双端链表实现的队列。

Q: 什么时候应该使用链表而不是数组来实现栈和队列呢?

A: 这一点要取决于是否能精准地预测栈或队列需要容纳的数据量。如果这一点不是很清楚的话,链表就比数组表现出更好的适用性。两者都很快,所以速度可能不是考虑的重点。

Q: 什么是抽象数据类型(ADT)?

A: 简单来说,它是一种考虑数据结构的方式:着重于它做了什么,而忽略它是怎么做的。

A: 栈和队列都是ADT的例子,前面已经看到栈和队列既可以用数组实现,也可以使用链表实现,而对于使用它们的用户完全不知道具体的实现细节(用户不仅不知道方法是怎样运行,也不知道数据是如何存储的)。

A: ADT的概念在软件设计过程中很重要,如果需要存储数据,那么就要从它的实际操作上开始考虑,比如,是存取最后一个插入的数据项?还是第一个?是特定值的项?还是在特定位置上的项?回答这些问题会引出ADT的定义。

A: 只有在完整定义ADT后,才应该考虑细节问题。

A: 通过从ADT规范中剔除实现的细节,可以简化设计过程,在未来的某个时刻,易于修改实现。如果用户只接触ADT接口,应该可以在不“干扰”用户代码的情况下修改接口的实现。

A: 当然,一旦设计好ADT,必须仔细选择内部的数据结构,以使规定的操作的效率尽可能高。例如随机存取元素a,那么用链表表示就不够好,因为对链表来说,随机访问不是一个高效的操作,选择数据会得到更好的效果。

Q: 有序链表?

A: 在有序链表中,数据是按照关键值有序排列的,有序链表的删除常常是只限于删除在表头的最小(或最大)的节点。

A: 一般,在大多数需要使用有序数组的场合也可以使用有序链表。有序链表的优势在于插入的速度,因为元素不需要移动,而且链表可以随时扩展所需内存,数组只能局限于一个固定大小的内存。

A: 示例:SortedLinkedList.java

A: 当算法找到要插入的位置,用通常的方式插入数据项:把新节点的next字段指向下一个节点,然后把前一个结点的next字段指向新节点。然而,需要考虑一些特殊情况:节点有可能插在表头,或者表尾。

Q: 有序链表的效率?

A: 在有序链表插入或删除某一项最多需要O(N)次比较(平均N/2),因为必须沿着链表一步一步走才能找到正确的位置。然而,可以在O(1)的时间内找到或删除最小值,因为它总在表头。

A: 如果一个应用频繁地存取最小项,且不需要快速地插入,那么有序链表是一个有效的方案选择,例如,优先级队列可以用有序链表来实现。

Q: 链表插入排序(List Insertion Sort)?

A: 有序链表可以用于一种高效的排序机制。假设有一个无序数组,如果从这个数组中取出数据,然后一个一个地插入有序链表,它们自动地按照顺序排列。然后把它们从有序链表删除,重新放入数组,那么数组就排好序了。

A: 本质上与基于数组的插入排序是一样的,都是O(N2)的比较次数,只是说对于数组会有一半已存在的数据会涉及移动,相当于N2/4次移动,相比之下,链表只需2 * N次移动:一次是从数组到链表,一次是从链表到数组。

A: 不过链表插入有一个缺点:就是它要开辟差不多两倍的空间。

A: 示例: LinkedListSort.java

Q: 双向链表?

A: 双向链表提供了这样的能力,即允许向前遍历,也允许向后遍历整个链表,其中秘密在于它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。

A: 双向链表不必是双端链表(保持一个对链表最后一个元素的引用),但这种方式是很有用的。所以下面的示例将包含双端的性质。

Q: 基于双向链表的双端链表的Java实现?

A: 示例:DoublyLinkedList.java

A: addFirst(E)方法:将指定元素插入此列表的开头。 

A: addLast(E)方法:将指定元素添加到此列表的结尾。 

A: add(index, E)方法: 在此列表中指定的位置插入指定的元素。 

A: remove(Object o)方法: 从此列表中移除首次出现的指定元素(如果存在)。 

Q: 基于双向链表的双端队列?

A: 双向链表可以用来作为双端队列的基础。在双端队列中,可以从任何一头插入和删除,双向链表提供了这个能力。

Q: 为什么要引入迭代器的概念?

A: ArrayList底层维护的是一个数组;LinkedList是链表结构的;HashSet依赖的是哈希表,每种容器都有自己特有的数据结构。因为容器的内部结构不同,很多时候可能不知道该怎样去遍历一个容器中的元素。所以为了使对容器内元素的操作更为简单,Java引入了迭代器。

A: 把访问逻辑从不同类型的集合类中抽取出来,从而避免向外部暴露集合的内部结构。
对于数组我们使用的是下标来进行处理的:

for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}

对于链表,我们从表头开始遍历:

    public void displayForward() {
System.out.print("List (first-->last): [");
Entry<E> current = mHeader;
while(current != null) {
E e = current.mElement;
System.out.print(e);
if (current.mNext != null) {
System.out.print(" ");
}
current = current.mNext;
}
System.out.print("]\n");
}

A: 不同的集合会对应不同的遍历方法,客户端代码无法复用。在实际应用中如何将上面两个集合整合是相当麻烦的。所以才有Iterator,它总是用同一种逻辑来遍历集合。使得客户端自身不需要来维护集合的内部结构,所有的内部状态都由Iterator来维护。客户端不用直接和集合进行打交道,而是控制Iterator向它发送向前向后的指令,就可以遍历集合。

A: 迭代器模式就是提供一种方法对一个容器对象中的各个元素进行访问,而又不暴露该对象容器的内部细节。

Q: 迭代器定义的接口?

A: 迭代器包含对数据结构中数据项的引用,并且用来遍历这些结构的对象。下面是迭代器的接口定义:

public interface Iterator<E> {
boolean hasNext(); E next(); void remove();
}
public interface ListIterator<E> extends Iterator<E> {
boolean hasPrevious(); E previous(); int nextIndex(); int previousIndex(); void set(E e);
}

A: 每个容器的iterator()方法返回一个标准的Iterator实现。一般而言,Java中迭代器和链表之前的连接是通过把迭代器设为链表的内部类来实现,而C++是"友元"来实现。

A: 如下图显示了指向链表的某个结点的两个迭代器:

Q: JDK1.6的LinkedList的迭代器?

A: 迭代器类ListItr实现ListIterator接口,定义如下:

private class ListItr implements ListIterator<E> {

}

A: 示例:ListIteratorTestCase.java

Q: 迭代器指向哪里?

A: 迭代器类的一个设计问题是决定在不同的操作后,迭代器应该指向哪里。而JDK1.6中LinkedList.ListItr中的add()实现,next指针一直指向表头,这里假设调用的是iterator(),不指定下标。

Q: 本篇小结

  • 链表包含一个LinkedList对象和许多Entry对象。
  • next字段为null意味着链表的结尾。
  • 在表头插入结点需要把新结点的next字段指向原来的第一个结点,然后把header指向新结点。
  • 在表头删除结点要把header指向header.next。
  • 为了遍历链表,从header开始,然后从一个结点到下一个结点,方法是用每个结点的next字段找到下一个结点。
  • 通过遍历链表可以找到拥有特定值的结点,一旦找到,可以显示、删除或用其他方式操纵该结点。
  • 新结点可以插在某个特定值的结点的前面或后面,首先要遍历找到这个结点。
  • 双端链表在链表中维护一个指向最后一个结点的引用,它通常和header一样,叫做tailer。
  • 双端链表允许在表尾插入数据项。
  • 抽象数据类型是一种数据存储类,不涉及它的实现。
  • 栈和队列是ADT,它们既可以用数组实现,也可以用链表实现。
  • 有序链表中,结点按照关键字升序或降序排列。
  • 在有序链表中插入需要O(N) 的时间,因为必须要找到正确的插入点,最小值结点的删除需要O(1)时间。
  • 双向链表中,每个结点包含对前一个结点的引用,同时有对后一个结点的引用。
  • 双向链表允许反向遍历,并可以从表尾删除。
  • 迭代器是一个引用,它被封装在类对象中,这个引用指向相关联的链表中的结点。
  • 迭代器方法允许使用者沿链表移动迭代器,并访问当前所指的结点。
  • 能用迭代器遍历链表,在选定的结点上执行某些操作。

Java数据结构和算法 - 链表的更多相关文章

  1. java 数据结构与算法---链表

    原理来自百度百科  一.链表的定义 链表是一种物理存储单元上非连续.非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的.链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运 ...

  2. Java数据结构和算法(一)线性结构之单链表

    Java数据结构和算法(一)线性结构之单链表 prev current next -------------- -------------- -------------- | value | next ...

  3. Java数据结构和算法(四)--链表

    日常开发中,数组和集合使用的很多,而数组的无序插入和删除效率都是偏低的,这点在学习ArrayList源码的时候就知道了,因为需要把要 插入索引后面的所以元素全部后移一位. 而本文会详细讲解链表,可以解 ...

  4. 【Java数据结构学习笔记之二】Java数据结构与算法之栈(Stack)实现

      本篇是java数据结构与算法的第2篇,从本篇开始我们将来了解栈的设计与实现,以下是本篇的相关知识点: 栈的抽象数据类型 顺序栈的设计与实现 链式栈的设计与实现 栈的应用 栈的抽象数据类型   栈是 ...

  5. java数据结构与算法之栈(Stack)设计与实现

    本篇是java数据结构与算法的第4篇,从本篇开始我们将来了解栈的设计与实现,以下是本篇的相关知识点: 栈的抽象数据类型 顺序栈的设计与实现 链式栈的设计与实现 栈的应用 栈的抽象数据类型 栈是一种用于 ...

  6. Java数据结构和算法 - 二叉树

    前言 数据结构可划分为线性结构.树型结构和图型结构三大类.前面几篇讨论了数组.栈和队列.链表都是线性结构.树型结构中每个结点只允许有一个直接前驱结点,但允许有一个以上直接后驱结点.树型结构有树和二叉树 ...

  7. Java数据结构和算法 - 高级排序

    希尔排序 Q: 什么是希尔排序? A: 希尔排序因计算机科学家Donald L.Shell而得名,他在1959年发现了希尔排序算法. A: 希尔排序基于插入排序,但是增加了一个新的特性,大大地提高了插 ...

  8. Java数据结构和算法 - 栈和队列

    Q: 栈.队列与数组的区别? A: 本篇主要涉及三种数据存储类型:栈.队列和优先级队列,它与数组主要有如下三个区别: A: (一)程序员工具 数组和其他的结构(栈.队列.链表.树等等)都适用于数据库应 ...

  9. Java数据结构和算法 - OverView

    Q: 为什么要学习数据结构与算法? A: 如果说Java语言是自动档轿车,C语言就是手动档吉普.数据结构呢?是变速箱的工作原理.你完全可以不知道变速箱怎样工作,就把自动档的车子从1档开到4档,而且未必 ...

随机推荐

  1. Android FoldingLayout 折叠布局 原理及实现(二)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/44283093,本文出自:[张鸿洋的博客] 1.概述 在上一篇Android Fo ...

  2. Android 带你玩转实现游戏2048 其实2048只是个普通的控件

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40020137,本文出自:[张鸿洋的博客] 1.概述 博主本想踏入游戏开放行业,无 ...

  3. redis list命令操作

    1.将值追加到列表 RPUSH key value [value ...]summary: Append one or multiple values to a listsince: 1.0.0 12 ...

  4. 某校高中生利用Python,巧妙获取考试成绩,看到成绩后无言以对!

    Python是非常有吸引力的编程语言,学习Python的不是帅哥就是美女.为什么这么说呢?因为我和我的女朋友都是学习Python认识的,小编肯定是帅哥,不用去怀疑,而且我眼光特高. 给大伙讲一个故事, ...

  5. java泛型基础、子类泛型不能转换成父类泛型

    参考http://how2j.cn/k/generic/generic-generic/373.html 1.使用泛型的好处:泛型的用法是在容器后面添加<Type>Type可以是类,抽象类 ...

  6. acl.go

    package acl   import ( "github.com/armon/go-radix" )   var ( // allowAll is a singleton po ...

  7. 【爆料】-《堪培拉大学毕业证书》Canberra一模一样原件

    ☞堪培拉大学毕业证书[微/Q:2544033233◆WeChat:CC6669834]UC毕业证书/联系人Alice[查看点击百度快照查看][留信网学历认证&博士&硕士&海归& ...

  8. String去重方法

    思路:利用集合的contains方法将某个字符串中的集合中没有的单个字符添加到集合中,然后再将集合中每个元素做拼接 @Test public void aa5(){ String aa="a ...

  9. 一起来学Spring Cloud | 第二章:服务注册和发现组件 (Eureka)

    本篇文章,很浅显的一步步讲解如何搭建一个能运行的springcloud项目(带所有操作截图).相信!看完本篇之后,你会觉得springcloud搭建如此简单~~~~ 一. Eureka简介: 1.1  ...

  10. flask下载excel

    flask 应用的基本结构: htmlweb.py -- static -- templates 将 bootstrap.min.css 放到 static 文件夹下,在 templates 文件夹下 ...