内容简介:本系列文章是在学习数据结构和算法时记录的学习笔记,概念讲解部分主要引用自《极客时间》的相关专栏,代码实践部分由本人采用 Python 实现。版权归极客时间所有。链表(Linked List)与数组一样,也是一种线性表数据结构,不过它不用一组连续的内存空间,而是通过指针的形式,将零散的内存块串联起来。
本系列文章是在学习数据结构和算法时记录的学习笔记,概念讲解部分主要引用自《极客时间》的相关专栏,代码实践部分由本人采用 Python 实现。
版权归极客时间所有。
概念
链表(Linked List)与数组一样,也是一种线性表数据结构,不过它不用一组连续的内存空间,而是通过指针的形式,将零散的内存块串联起来。
每一个内存块称为链表的 节点
。为了将所有的结点串起来,每个链表的结点除了存储数据之外,还需要记录链上的相邻结点的地址,记录下一个节点的指针叫作 后继指针 next
,记录上一个节点的指针叫作 前驱指针 previous
。
最常见的链表结构有:单链表、双向链表和循环链表。
单链表(Linked List)
单链表的特点:
- 节点存储内容:当前节点的数据
data
、后继指针next
- 头结点:记录链表
基地址
,有了它,我们就可以遍历得到整条链表 - 两个尾结点:后继指针指向
空地址NULL
,遍历时作为尾结点的判断条件
双向链表(Doubly Linked List)
双向链表的特点:
- 节点存储内容:当前节点的数据
data
、后继指针next
、前驱指针previous
,相比于单链表,双向链表会占用更多的内存空间 - 头结点:记录链表
基地址
,前驱指针指向空地址NULL
,有了它,我们就可以遍历得到整条链表 - 尾结点:后继指针指向
空地址NULL
,遍历时作为尾结点的判断条件
复杂度分析
相比于数组,链表主要有以下优势:
- 改善“插入”和“删除”的效率
- 不受内存连续性的限制,可以存储大量元素,例如 blockchain
对应的弊端就是,随机访问效率较低,需要根据指针一个结点一个结点地依次遍历,直到找到相应的结点。
单链表和双向链表在随机访问、查询操作时的时间复杂度相同:
- Access: O(n)
- Search: O(n)
在对链表进行“删除”操作时,存在两种情况:
(1)删除结点中“值等于某个给定值”的结点 q
(2)删除给定指针指向的结点 q
对于第一种情况,不管是单链表还是双向链表,为了查找到值等于给定值的结点,都需要从头结点开始一个一个依次遍历对比,直到找到值等于给定值的结点,然后再进行删除操作。查询时间复杂度 O(n),删除时间复杂度 O(1),因此总体时间复杂度为 O(1)。
对于第二种情况,已经确定了要删除的结点,但是删除某个结点 q 需要知道其前驱节点,因此在此情况下单链表和双向链表就存在较大差异:
- 单链表:需要从头节点依次遍历,找到 q 节点的前驱节点 p(p->next = q);查询时间复杂度 O(n),删除时间复杂度 O(1),因此总体时间复杂度为 O(n)。
- 双向链表:可直接获取 q 节点的前驱节点 p(q->previous);查询时间复杂度 O(1),删除时间复杂度 O(1),因此总体时间复杂度为 O(n)。
删除的伪代码:
// 单链表 while p->next { if (p->next = q) { break; } p = p->next; } p->next = q->next; q->next = null; // 双向链表 q->previous->next = q->next; q->next = null;
在对链表进行“插入”操作时,比如在链表的某个指定结点(q)前面插入一个结点(x),双向链表也有更大的优势:
- 单链表:需要从头节点依次遍历,找到 q 节点的前驱节点 p(p->next = q);查询时间复杂度 O(n),插入时间复杂度 O(1),因此总体时间复杂度为 O(n)。
- 双向链表:可直接获取 q 节点的前驱节点 p(q->previous);查询时间复杂度 O(1),插入时间复杂度 O(1),因此总体时间复杂度为 O(1)。
插入的伪代码:
// 单链表 while p->next { if (p->next = q) { break; } p = p->next; } p->next = x; x->next = q; // 双向链表 q->previous->next = x; x->next = q;
使用 Python 实现链表数据结构
在 Python 语言中没有链表的数据结构,需要模拟进行实现。
定义链表
定义单链表:
class Node(object): def __init__(self, data, next_node=None): self.data = data self.next_node = next_node
定义双向链表:
class BiNode(object): def __init__(self, data, previous_node=None, next_node=None): self.data = data self.previous_node = previous_node self.next_node = next_node
单链表常用操作
创建单链表(5->4->3->2->1):
head = None for data in range(1,6): head = Node(data, head)
遍历单链表:
# 输出 5、4、3、2、1 # head 当前对应节点 5 while head is not None: print(head.data) head = head.next_node # 遍历完成后,head is None,链表结构消失 # 若需要在遍历完成仍保留链表结构,则需使用一个临时变量 probe = head while probe is not None: print(probe.data) probe = probe.next_node # 遍历完成后,probe is None,head 当前对应节点 5
插入单链表,将 20 插入到 3 和 2 之间:
# head 当前对应节点 5 probe = head while probe is not None: if probe.data == 3: new_node = Node(20, None) new_node.next_node = probe.next_node probe.next_node = new_node break else: probe = probe.next_node
双向链表常用操作
实战
典型问题
1、实现单链表、循环链表、双向链表,支持增删操作
2、实现单链表反转
3、实现两个有序的链表合并为一个有序链表
4、实现求链表的中间结点
以上所述就是小编给大家介绍的《数据结构与算法: 链表》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 算法与数据结构之递归算法
- Python 数据结构与算法 —— 初识算法
- js实现数据结构及算法之排序算法
- 数据结构和算法面试题系列—递归算法总结
- 数据结构和算法面试题系列—随机算法总结
- 数据结构与算法——常用排序算法及其Java实现
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
图解Java多线程设计模式
[日] 结城浩 / 侯振龙、杨文轩 / 人民邮电出版社 / 2017-8 / 89.00元
本书通过具体的Java 程序,以浅显易懂的语言逐一说明了多线程和并发处理中常用的12 种设计模式。内容涉及线程的基础知识、线程的启动与终止、线程间的互斥处理与协作、线程的有效应用、线程的数量管理以及性能优化的注意事项等。此外,还介绍了一些多线程编程时容易出现的失误,以及多线程程序的阅读技巧等。在讲解过程中,不仅以图配文,理论结合实例,而且提供了运用模式解决具体问题的练习题和答案,帮助读者加深对多线......一起来看看 《图解Java多线程设计模式》 这本书的介绍吧!