Java进阶之路

【数据结构与算法】链表数据结构

2019-04-23  本文已影响2人  叫我不矜持

什么是链表数据结构?

链表是线性表的一种,但是在存储上和线性表结构的数组有很大的差异,数组的存储在内存上是一块连续的空间,链表却可以理由分散空间进行存储,因此,当内存大小不够,或者内存碎片过多,而此时又需要为一个长度很大数组分配空间,可能就会出现内存不够,触发jvm提起回收内存的情况,甚至抛出OutOfMemoryError异常。但是链表就不用担心这一点,因为无需向数组一样,提前开辟好一块内存连续的空间。链表的长度时增加,占用的内存可以充分利用内存碎片来完成。

链表数据结构的核心就是下面这样的,包含一个保存实际数值的val,还有指向上一个、下一个链表节点的引用或者是指针(java为引用,c为指针),这里描述的是双链表的基础节点数据结构。

 public class ListNode {
     int val;
     ListNode next;
     ListNode pre;
     ListNode(int x) { val = x; }
 }

链表的种类

1.单向链表
对于普通单链表的随机访问时间复杂度为O(n),因为需要从链表头依次顺序查找,但是对于链表结构添加和删除节点的时间复杂度都是O(1)不需要像数组一样进行数据迁移,不过查找插入和删除位置还要单独计算时间复杂度。插入和删除节点其实就是先通过查询找到需要进行操作的位置,然后修改节点的next连接就可以了。被删除的节点如果是C或者是C++需要进行资源回收,如果是java,因为已经没有其他地方引用,GC会自动回收资源。

2.循环链表
循环链表是一种特殊的单链表,实际上循环链表也非常简单,他和单链表的唯一区别就是在尾节点,单链表的尾节点指向null,表示链表结束,循环链表的尾是指向链表头节点。

3.双向链表
我们常见的单向链表因为是一个方向的链接,这就导致无法直接查找前继节点,需要从头节点遍历才行,单向链表查找前继节点的时间复杂度是O(n)。如果我们在一些场景频繁需要查找前继节点那么就可以使用双向链表,双向链表和单向链表的区别就在于在节点结构里多了一个前继节点引用或指针,在维护链表的时候也要多维护一个前继指针的关系。所以同样的数据,双向链表需要的空间要多出O(n),但是双向链表查找前继节点的时间复杂度降到了O(1)。

特点介绍

1.内存中地址非连续,
2.长度可以实时变化
3.不支持随机查找 查找元素时间复杂度O(n)
4.适用于需要进行大量增添/删除元素操作,而对访问元素无要求的程序

Java中的链表数据结构

1.LinkedList
LinkedList是基于双向链表实现的,不论是增删改查方法还是队列和栈的实现,都可通过操作结点实现。
LinkedList根据index查询时采取的是二分法,即index小于总长度一半时从链表头开始往后查找,大于总长度一半时从链表尾往前查找。如果是根据元素查找,则需要从头开始遍历。

2.HashMap,ConcurrentHashMap
采用拉链法解决哈希冲突,拉链法是一种开放散列的解决哈希冲突的形式。所谓 “拉链法” 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。
tips:封闭散列解决哈希冲突有:开放定址法,再哈希法等。
3.LinkedHashMap
通过维护一个运行于所有条目的双向链表,LinkedHashMap保证了元素迭代的顺序。该迭代顺序可以是插入顺序或者是访问顺序。
LinkedHashMap可以认为是HashMap+LinkedList,即它既使用HashMap操作数据结构,又使用LinkedList维护插入元素的先后顺序。

总结

对于通过下标随机读取非常多、插入和删除数据是从末尾操作的场景适合用数组,对于随机读较少、插入和删除操作较多的场景适合用链表,对于需要频繁查找前继和后继节点的场景适合用双向链表,对于资源可以循环利用的可以使用循环链表。
通过单向链表和双向链表的介绍,还需要明白可以通过定义数据结构用空间换时间的设计思想。当内存空间充足的时候,如果追求代码执行效率,可以选择空间复杂度更高的数据结构或算法来提高执行效率。

拓展

java中的List集合最常见的有ArrayList,和LinkedList,ArraysList 实现了 RandomAccess 接口, 而 LinkedList 没有实现。为什么呢?

ArraysList 底层是数组,而 LinkedList 底层是链表。数组天然支持随机访问,时间复杂度为 O(1),所以称为快速随机访问。
链表需要遍历到特定位置才能访问特定位置的元素,时间复杂度为 O(n),所以不支持快速随机访问。

ArraysList 实现了 RandomAccess 接口,就表明了他具有快速随机访问功能。 RandomAccess 接口只是标识,并不是说 ArraysList 实现 RandomAccess 接口才具有快速随机访问功能的!

下面再总结一下 list 的遍历方式选择:
实现了RandomAccess接口的ArrayList,优先选择普通for循环 ,其次foreach,
未实现RandomAccess接口的LinkedList, 优先选择iterator遍历(foreach遍历底层也是通过iterator实现的)。
ps:大size的数据,千万不要使用普通for循环

上一篇下一篇

猜你喜欢

热点阅读