Java双向链表时间复杂度_链表是什么?有多少种链表?时间复杂度是?
发布日期:2021-06-24 13:19:25 浏览次数:4 分类:技术文章

本文共 2214 字,大约阅读时间需要 7 分钟。

链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。

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

链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的存取往往要在不同的排列顺序中转换。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。

链表和数组的对比:

download_20191216221040_493.jpg

常见种链表结构:单向链表

双向链表

循环链表

块状链表

其它扩展

单向链表(单链表)-最简单,最常用

链表通过指针将一组零散的内存块串联在一起。把内存块称为链表的“结点”。

每个结点包含了两个域:一个是存储数据元素的数据域(信息域)

一一个是存储下一个结点地址的指针域

为了将所有的结点串起来,每个链表的结点除了存储数据之外,还需要记录链上的下一个结点的地址。如图所示,我们把这个记录下个结点地址的指针叫作后继指针 next。

danlianbiao_20191216222224_992.jpg

头结点,尾结点

头结点:链表的第一个节点,用来记录链表的基地址,方便遍历得到整条链表。

尾结点:链表的最后一个节点。指针不是指向下一个结点,而是指向一个空地址 NULL,表示这是链表上最后一个结点。

时间复杂度:

链表删除操作情况:删除结点中“值等于某个给定值”的结点;

删除给定指针指向的结点

插入和删除操作:如果已知相邻结点,这时候进行插入和删除操作,时间复杂度是O(1)。

lianbiaoshanchuhecharu_20191216224936_211.jpg

删除结点中“值等于某个给定值”的结点,单纯的删除操作时间复杂度是 O(1),但遍历查找的时间是主要的耗时点,对应的时间复杂度为 O(n)。根据时间复杂度分析中的加法法则,删除值等于给定值的结点对应的链表操作的总时间复杂度为 O(n)。

删除给定指针指向的结点,已经找到了要删除的结点,但是删除某个结点 q 需要知道其前驱结点,而单链表并不支持直接获取前驱结点,所以,为了找到前驱结点,我们还是要从头结点开始遍历链表,直到 p->next=q,说明 p 是 q 的前驱结点。因此时间复杂度还是为O(1)

在指定的节点前插入一个节点,单向链表的时间复杂度是O(n)

查找操作:因为链表需要一个接着一个往下找(如排队,你想知道排在N位置的人是谁,必须从第一个开始,然后一个一个往下数),所以时间复杂度是O(n)

循环链表——一种特殊的单链表

循环链表特点是表中最后一个结点的指针域指向头结点(首节点和末节点被连接在一起),整个链表形成一个环。

和单链表区别:单链表的尾结点指针指向空地址,表示这就是最后的结点了。

循环链表的尾结点指针是指向链表的头结点。

应用适用于存储有循环特点的数据,比如约瑟夫问题。

%E5%BE%AA%E7%8E%AF%E9%93%BE%E8%A1%A8_20191216230855_938.jpg

双向链表-和单链表相比,存相同的数据,需要更多的存储空间

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。

%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8_20191217163440_471.jpg

时间复杂度插入、删除时间复杂度都是O(1), 因为双向链表记录了后继结点和前驱结点的地址

查找时间复杂度为O(n)

双向循环链表-将双向表和循环表整合一起试用

%E5%8F%8C%E5%90%91%E5%BE%AA%E7%8E%AF%E9%93%BE%E8%A1%A8_20191217115950_103.jpg

链表VS数组性能

%E9%93%BE%E8%A1%A8%20VS%20%E6%95%B0%E7%BB%84%E6%80%A7%E8%83%BD_20191217163617_257.jpg

数组简单易用,在实现上使用的是连续的内存空间,可以借助 CPU 的缓存机制,预读数组中的数据,所以访问效率更高。而链表在内存中并不是连续存储,所以对 CPU 缓存不友好,没办法有效预读。CPU在从内存读取数据的时候,会先把读取到的数据加载到CPU的缓存中。而CPU每次从内存读取数据并不是只读取那个特定要访问的地址,而是读取一个数据块(这个大小我不太确定。。)并保存到CPU缓存中,然后下次访问内存数据的时候就会先从CPU缓存开始查找,如果找到就不需要再从内存中取。这样就实现了比内存访问速度更快的机制,也就是CPU缓存存在的意义:为了弥补内存访问速度过慢与CPU执行速度快之间的差异而引入。

对于数组来说,存储空间是连续的,所以在加载某个下标的时候可以把以后的几个下标元素也加载到CPU缓存这样执行速度会快于存储空间不连续的链表存储。

数组缺点:大小固定,若存储空间不足,需进行扩容,一旦扩容就要进行数据复制,而这时非常费时的。

数组优点:由于数组使用的连续的内存空间,可以借助CPU缓存机制,提高数组访问效率

链表缺点:内存空间消耗更大,因为链表每个结点都需要消耗额外的存储空间去存储指向下一个结点的指针。

对链表进行频繁的插入和删除操作,会导致频繁的内存申请和释放,容易造成内存碎片,如果是Java语言,还可能会造成频繁的GC(自动垃圾回收器)操作。

常见用途:

常用于组织检索较少,而删除、添加、遍历较多的数据。

参考文章:

https://zh.wikipedia.org/wiki/%E9%93%BE%E8%A1%A8

https://time.geekbang.org/column/article/41013

转载地址:https://blog.csdn.net/weixin_33093437/article/details/114729581 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:unity3d能和java系统整合吗_Android与Unity3d的整合
下一篇:linux shell mysql备份_linux shell 备份mysql 数据库

发表评论

最新留言

能坚持,总会有不一样的收获!
[***.219.124.196]2024年04月02日 04时01分07秒