JAVA数据结构之链表
链表应该是继数组之后应用最广的通用存储结构,链表的机制灵活,用途广泛,适用于许多通用的数据库,也可取代数组作为其他的存储结构的基础,如栈和队列,除非需要频繁地通过下标访问数据,否则在很多使用数组的地方都可以使用链表来代替。
接下来本篇博文会讲解到单链表,双端链表,有序链表,双向链表,和有迭代器的链表。(迭代器是用来随机访问链表元素的一种方法)
链表的实现一般需要用到两个类,链表对象(包含对第一个链表节点的引用),链表节点(所有保存的数据,和对下一个链表节点的引用)。这种 方式可以称作自引式,由于其中包含一个和自己相同类型的字段(这里的字段是包含对下一个节点的引用,相当于一个内存地址,而不是具体的对象。若是简单类型这里就是实实在在的值,而不是内存地址)
我们要使用链表实现栈,就必须要实现1.在链表头插入一个数据项,2.在链表头删除一个数据项,3.遍历链表显示他的内容。
双端链表:与传统的链表非常相似,但他有一个新的特性:即链表有对最后一个链节点的引用,就像对第一个链节点的引用一样,这样就可以高效地在链表尾部加入一个元素,从而不用遍历整个链表。(注意和后面讨论到的双向链表进行区分)
双端链表的插入和删除和普通链表类似,插入的时候需要注意链表为空的情况,双端链表为空的时候需要将first和last都指向新的链节点,如果链表只有一个链节点那么从表头删除也是一种特殊情况,last必须被赋值为null值。但是双端链表也不能很好地解决我们删除链表最后一个链节点,因为我们没有指向链表倒数第二个节点的引用,当然也可以遍历链表找到倒数第二个链节点,但效率不高,为解决这样的问题,接下来我们介绍“双向链表”。
双向链表:传统链表进行反向遍历是很困难的,双向链表在于每个链节点存在两个指向其他链节点的引用,这样就可以正向和反向遍历整个链表
双向链表的缺点是每次插入和删除需要处理四个链节点的引用而不是两个,每个链节点多了一个链节点的引用,链节点所占的空间变大。双向链表可以从任何一头插入或删除,可用来作为双端队列的基础
链表的效率:表头插入和删除的速度很快,只需改变两个引用值花费O(1)的时间,平均起来查找删除和在指定的链节点后插入都需要搜索链表中的一半的链节点。需要O(N)次比较,数组中执行这些操作也需要O(N)次比较,但链表速度仍然比数组快,因为在插入和删除的时候不需要移动任何东西,效率是显著的。这里我们使用链表相对于数组有一个显著的优点就是,链表的内存使用上是我们想用多少就用多少内存,不像数组在创建之初就固定了数组的大小不能改变。向量也是一种可扩展的数组,但是他扩展是指定长度的扩展,效率相对于链表还是低。
有序链表:即链表是按照关键值有序排列的,有序链表插入和删除一项最多需要O(N)次比较(平均N/2),可以在O(1)时间内找到或删除最小值,如果一个应用频繁地存取最小项,有序链表会是一个可靠的方案,例如优先级队列就可以使用有序链表来实现。有序链表可以用于一种高效的排序机制,将一个无序数组的元素插入到有序链表中,然后从有序链表中删除重新放入数组,这样数组就排好序了。
迭代器:迭代器提供给链表类似于数组下标可以快速找到对应的链节点,迭代器类包含了对数据结构中数据项的引用,并用来遍历这些结构。链表的迭代器如下图所示指向一个链节点
迭代器的特性:我们一般对于链表的操作会用到当前节点前一个链节点的引用,所以有必要存储一个previous字段来方便我们操作,迭代器有时候需要改变first的值,我们可以给迭代器传入一个当前链表的引用给迭代器,此时就可以利用此引用拿到first,下面是一个简单的迭代器类的实现
在进行不同的操作之后迭代器的指向:迭代器进行删除之后会将迭代器放在下一个节点(操作方便,可能会对附近数据进行操作,若不是双向链表不能指向前一个链节点),若删除的是最后一个链节点,就指向表头。插入了新的链节点会将迭代器指向新的链节点。
这里我们需要注意的是迭代器中atEnd()方法,通常有两种方式,当迭代器指向最后一个链节点的时候返回true,或者当迭代器指向最后一个链节点的下一个的时候返回true(此时不指向一个有效的链节点),第一种方式在遍历链表得时候显得很笨重,当到达最后一个链节点的时候还没来得及做循环体内的事情,循环就终止了。第二种方式一旦到达链表结尾,由于单链表不能倒退,所以此时需要对最后一个链节点进行操作就太晚了,比如删除。所以迭代器一般采用第一种方式,所以此时写循环遍历链表就要格外注意了
以上就是关于链表的一些知识点。