欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

数据结构学习笔记(2)——链表

程序员文章站 2022-07-04 08:10:22
...

机械工业出版社《数据结构与算法——Python语言实现》学习笔记。
绘图工具:ProcessOn

1 链表

主要介绍链表的数据结构及方法,省略详细实现。

1.1 单向链表

1.1.1 结构

数据结构学习笔记(2)——链表
要维护的数据结构:

  • Node类:包含元素成员和指针域成员
  • head指针:保存指向头节点的引用
  • tail指针:保存指向尾节点的引用
  • size:保存节点长度

1.1.2 单向链表头部插入元素

数据结构学习笔记(2)——链表

Algorithm add_first(L, e):
    newest = Node(e) #创建新节点
    newest.next = L.head #连接
    L.head = newest #更新head指针
    L.size = L.size + 1 #更新链表长度

1.1.3 单向链表尾部插入元素

数据结构学习笔记(2)——链表

Algorithm add_last(L, e):
    newest = Node(e) #创建新节点
    newest.next = None #新节点的next要设为None
    L.tail.next = newest #连接
    L.tail = newest #更新tail指针
    L.size = L.size + 1 #更新链表长度

1.1.4 从单向链表中删除元素

单向链表中想删除元素必须从头开始遍历,找到要删除节点的前一个节点,因此 删除尾部元素需要遍历整个链表
删除头部元素:

数据结构学习笔记(2)——链表

Algorithm add_first(L, e):
    if L.head is None then
        Indicate an error:the list is empty.
    L.head = L.next #更新head指针
    L.size = L.size - 1 #更新链表长度

1.2 循环链表

1.2.1 结构

数据结构学习笔记(2)——链表
数据结构学习笔记(2)——链表
一个循环链表可能并没有开始或者结束节点,但是必须为一个特定的节点维护一个引用(如上图的current),这样才能使用该链表

1.3 双向链表

1.3.1 结构

在单向链表中提到,由于单向链表的不对称性,删除尾部元素需要遍历整个链表,为了弥补这个缺陷,引入双向链表的概念:

数据结构学习笔记(2)——链表
要维护的数据结构:

  • Node类:包含元素成员,pre前指针和next后指针
  • header头节点,也称为头哨兵
  • tailer尾节点,也称为尾哨兵
  • size:保存节点长度

使用哨兵的好处:使除了哨兵之外的所有节点都处于两个节点之间,因此可以使用同样的方法处理所有节点的插入/删除操作。

1.3.2 添加节点操作

数据结构学习笔记(2)——链表

1.3.3 删除节点操作

数据结构学习笔记(2)——链表

1.4 位置链表

到目前为止介绍的链表都有一个共同的缺点:每个节点都没有索引,因此很难直接定位到序列中的某一个元素,然而,数组中标记位置的整型索引放到链表中并不适用,因此,我们定义了一个 含位置信息的列表ADT 和一个更简单的 位置 抽象数据类型,来描述列表中的某个位置。 改变列表的其他位置不会影响位置p。

位置实例是一个简单的对象,只支持以下方法:

  • p.element(): 返回存储在位置p的元素。

位置列表ADT中,需要增加列表L所支持的访问器方法:

  • L.first(): 返回L中第一个元素的位置。如果L为空,则返回None。
  • L.last(): 返回L中最后一个元素的位置。如果L为空,则返回None。
  • L.before§: 返回L中p紧邻的前面元素的位置。如果p为第一个位置,则返回None。
  • L.before§: 返回L中p紧邻的后面元素的位置。如果p为最后一个位置,则返回None。
  • L.is_empty(): 如果L列表不包含任何元素,返回True。
  • len(L): 返回列表元素的个数。
  • iter(L): 返回列表元素的前向迭代器。

位置列表ADT中,需要增加列表L所支持的更新方法:

  • L.add_first(e): 在L的前面插入新元素e,返回新元素的位置。
  • L.add_last(e): 在L的后面插入新元素e,返回新元素的位置。
  • L.add_before(p,e): 在L中位置p之前插入一个新元素e,返回新元素的位置。
  • L.add_after(p,e): 在L中位置p之后插入一个新元素e,返回新元素的位置。
  • L.replace(p,e): 用元素e取代位置p处的元素,返回之前p位置处的元素。
  • L.delete§: 删除并返回L中位置p处的元素,取消该位置。

2 基于链表的序列与基于数组的序列的对比

项目 基于数组的序列 基于链表的序列
访问元素 基于整数索引的时间复杂度为O(1)的访问方法 定位第k个元素要从起始位置或结束位置开始遍历链表
基本操作复杂度 较为简单(新索引计算、整数增量、在数组中为元素存储引用) 较为复杂(节点实例化、节点链接、整数增量)
存储比例 较少(对象引用) 较多(对象引用、链接各个节点的引用)
时间界限 动态数组扩张和收缩时存在“摊销” 为操作提供最坏情况的时间界限
插入/删除操作效率 在序列中间进行插入/删除操作代价较大(要循环替换所有后续元素) 在任意位置插入/删除操作的时间复杂度都为O(1)