数据结构与算法: 链表

栏目: 编程工具 · 发布时间: 5年前

内容简介:本系列文章是在学习数据结构和算法时记录的学习笔记,概念讲解部分主要引用自《极客时间》的相关专栏,代码实践部分由本人采用 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、实现单链表、循环链表、双向链表,支持增删操作

TODO

2、实现单链表反转

TODO

3、实现两个有序的链表合并为一个有序链表

TODO

4、实现求链表的中间结点

TODO

以上所述就是小编给大家介绍的《数据结构与算法: 链表》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

图解Java多线程设计模式

图解Java多线程设计模式

[日] 结城浩 / 侯振龙、杨文轩 / 人民邮电出版社 / 2017-8 / 89.00元

本书通过具体的Java 程序,以浅显易懂的语言逐一说明了多线程和并发处理中常用的12 种设计模式。内容涉及线程的基础知识、线程的启动与终止、线程间的互斥处理与协作、线程的有效应用、线程的数量管理以及性能优化的注意事项等。此外,还介绍了一些多线程编程时容易出现的失误,以及多线程程序的阅读技巧等。在讲解过程中,不仅以图配文,理论结合实例,而且提供了运用模式解决具体问题的练习题和答案,帮助读者加深对多线......一起来看看 《图解Java多线程设计模式》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器