数据结构与算法笔记(三)—— 链表(Linked List)

链表(Linked List)

1,链表结构

在数据结构中,通常会拿数组和链表来做比较,在数据结构笔记(一)数组篇中我们了解了数组。

1,相同点
都是线性表数据结构;
都支持数据的查找,插入和删除操作;

2,不同点
数组需要连续的内存空间,对内存要求严格;
链表则不需要连续的内存空间,链表通过“指针”将一组零散的内存块串联起来使用。

数据结构与算法笔记(三)—— 链表(Linked List)_第1张图片

1.1,单链表

数据结构与算法笔记(三)—— 链表(Linked List)_第2张图片
单链表为一头一尾结构,第一个节点为头结点,记录链表的基地址;最后一个节点为尾结点,指针指向一个空地址NULL,不是指向下一个结点,表示链表上最后一个结点。
由于链表的内存空间不连续,所以在链表上进行删除和插入操作十分快速。只需要考虑相邻结点的指针改变,因此时间复杂度为O(1)。
数据结构与算法笔记(三)—— 链表(Linked List)_第3张图片
但是同样的,链表的随机访问能力就没有数组那么强了,需要根据指针遍历结点来寻找相应的结点,因此时间复杂度为O(n)。

1.2,循环链表

循环链表是一种特殊的单链表
单链表的尾结点:指向空地址
循环链表的尾结点:指向头结点
循环链表相对于单链表的优点:链尾到链头比较方便,适合处理环形结构的数据。
数据结构与算法笔记(三)—— 链表(Linked List)_第4张图片

1.3,双向链表

1.3.1,单向链表和双向链表结构对比

单向链表:只有一个方向,只有一个后继指针指向后面的结点;
双向链表:支持两个方向,不止一个后继指针指向后面的结点,还有一个前驱指针prev指向前面的结点。
数据结构与算法笔记(三)—— 链表(Linked List)_第5张图片
由上图可知,双向链表需要额外的两个空间来存储后继结点和前驱结点的地址,因此比单向链表占用更多的内存空间,但操作更灵活。

1.3.2,单向链表和双向链表操作对比

对于在链表上删除操作,可以分为两种:
1,删除结点中 “值等于给定值” 的结点;
针对这种情况,单链表和双链表都要遍历循环找到值等于给定值的结点,然后通过指针操作将其删除。删除操作时间复杂度为O(1),遍历查找时间复杂度为O(n),由时间复杂度加法法则可知,删除结点中 “值等于给定值” 的结点操作的时间复杂度为O(n);

2,删除“给定指针指向”的结点。
针对这种情况,已经找到了要删除的结点,若需要删除该结点的前驱结点,对于不支持直接获取前驱结点的单链表而言,需要从单链表的头结点开始遍历,直到p->next=q,说明p是q的前驱结点,删除操作时间复杂度为O(n)。
而对于双向链表来说,已经保存了前驱结点的指针,不需要像单链表一样遍历,因此这种删除操作时间复杂度为O(1)。
同理可得,插入操作也是一样的。
对于一个有序链表,双向链表的按值查询的效率也要比单链表高一些,因为双向链表可以记录上次查找的位置p,可根据所查找值和p位置值的大小关系来决定向前还是向后查找,可以缩短查找时间。

因此,综上所述,双向链表比单向链表更加高效,虽然比较费内存,但还是比单链表应用更广泛。

1.3.3,空间换时间

1,对于执行较慢的程序,可以通过消耗更多的内存(空间换时间)来进行优化;(内存空间充足,追求代码的执行速度,选择空间复杂度相对较高、但时间复杂度相对很低的算法或者数据结构。)
2,而消耗过多内存的程序,可以通过消耗更多的时间(时间换空间)来降低内存的消耗(内存空间紧缺,比如代码跑在手机或者单片机上,则采用时间换空间的设计思路)。

1.4,双向循环链表

数据结构与算法笔记(三)—— 链表(Linked List)_第6张图片

1.5,总结

数组和链表的最大不同:
1,数组大小固定,一经声明就要占用整块连续内存空间
数组声明过大:系统没有足够的连续内存分配,导致“out of memory”;
数组声明过小:若数组不够用需要申请更大的内存空间将原数组拷贝进去,十分耗时。
2,链表本身没有大小限制,天然支持动态扩容。
但是链表每个结点需要额外的储存空间去储存指向下一个结点的指针,因此内存的消耗会翻倍。
如果对链表频繁进行插入,删除操作,会导致内存的频繁申请和释放,产生内存碎片。

(注明:以上笔记整理自王争《数据结构与算法之美》)

你可能感兴趣的:(数据结构与算法,数据结构,链表,算法)