学习资料来源:
【算法通关手册】:https://algo.itcharge.cn/
【datawhale组队学习】https://github.com/datawhalechina/team-learning
大多是对这次组队学习的学习资料的学习笔记,非原创~
【未完待续……】
链表(Linked List)基础知识
链表简介
链表定义
链表是一种线性表数据结构。它使用一组任意的存储单元(可以是连续的,也可以是不连续的),来存储一组具有相同类型的数据。是实现线性表的链式存储结构的基础。
如上图所示,链表:
- 存储单元任意。
- 物理地址随机:逻辑上相邻的数据元素在物理地址上的相邻是随机的。
- 后继指针
next
:指出某个数据元素在逻辑关系上的直接后继元素所在链节点的地址。 - 逻辑关系:数据元素之间的逻辑关系通过指针间接反映。
- 链节点:每个数据元素占用若干存储单元的组合。
- 链节点存放:一个数据元素的值 + 后继指针。
链表结构优缺点:
- 优点:
- 节省空间:不用实现分配存储空间,需要的时候临时申请
- 操作效率高:插入、移动、删除
- 缺点:
- 指针需要占用存储
- 比数组的空间开销大
- 总的来说链表虽然空间开销大,但是不浪费!
各种链表
单列表:就是上面那种最简单的形式
双向链表(Doubly Linked List):每个链节点中有两个指针,分别指向直接后继和直接前驱。
- 目的在于方便访问任意节点的前驱节点和后继节点。
循环列表(Circular linked list):最后链节点指向头节点,形成一个环。
- 目的在于任意节点出发都能找到任何节点。
链表基本操作
- 增、删、改、查。——同时也都是数据结构常见操作
链表的结构定义以及常见变量命名
-
next
:链节点通过next
链接构成链表 -
ListNode
:链节点类 -
val
:ListNode
类中用来表示数据元素的值 -
next
:指针变量——用来表示后继指针 -
LinkedList
:链表类 -
head
:LinkedList
类中的唯一链节点变量,用来表示链表的头节点 -
cur
:指针变量,生成链表的时候用
创建空列表时将链表头节点变量设置为空链接(python
中为None
)
"""链节点类"""
class ListNode:
def __init__(self, val = 0, next = None):
self.val = val
self.next = next # 写下一个ListNode,也可以在外面写循环写self.next,,具体看1.2.2
"""链表类"""
class LinkedList:
def __init__(self):
self.head = None
建立一个线性链表
根据线性表的数据元素动态生成链节点,并依次将其连接到链表中,做法如下:
- 从所给线性表的第1个数据元素开始依次获取表中的数据元素。
- 每获取一个数据元素,就为该数据元素生成一个新节点,将新节点插入到链表的尾部。
- 插入完毕之后返回第1个链节点的地址。
可见时间复杂度为 O ( n ) O(n) O(n),生成一个线性列表的代码如下:
"""将data初始化为一个新列表"""
def create(self, data):
self.head = ListNode(0) # 头节点的值为0
cur = self.head # 指针指向头节点?
for i in range(len(data)):
node = ListNode(data[i]) # 对列表的每一个数都实例化为ListNode类
cur.next = node # 再把新生成的node连到上一个节点的next中去
cur = cur.next
求线性链表的长度
- 线性链表的长度:链节点的个数
cur
:指针变量count
:计数器
代码如下:
"""获取链表的长度"""
def length(self):
count = 0 # 初始化计数器
cur = self.head # 指针变量指向头节点
while cur:
# 当指针变量指向非空时
count += 1
cur = cur.next
return count
查找元素
查找值为val
的位置,链表只能从头节点head
开始逐个节点查找
- 查找成功返回节点地址,否则返回
None
代码如下:
"""在链表中查找元素"""
"""输出节点就是输出地址?"""
def find(self, val):
cur = self.head
while cur:
"""同样是通过指针变量cur来判断是否进入循环"""
if val == cul.val:
return cur
cur = cur.next
return None
插入元素
链表中的三种插入元素操作:头部插入、尾部插入和中间插入
- 中间插入:在第
i
个链节点之前插入值为val
的链结点
头部插入
Node
:先创建一个值为val
的链节点Node
代码如下:
"""在链表头部插入值为val的元素"""
def insertFront(self, val):
node = ListNode(val) # 首先新建一个链节点
node.next = self.head
self.head = node
尾部插入
- 将指针变量
cur
从链表的头部移动到尾部的操作次数为n,故时间复杂度 O ( n ) O(n) O(n)
代码如下:
"""在链表尾部插入元素"""
def insertRear(self, val):
node = ListNode(val)
cur = self.head
while cur.next:
'''为了移动到最后一个节点,当cur.next为None的时候就说明已经移到的尾部'''
cur = cur.next
cur.next = node
中间插入元素
将元素连接到第i
个链节点之前,也就是第i-1
个链节点之后
代码如下:
"""在链表的第i个链节点之前插入值为val的元素"""
def InsertInside(self, index, val):
count = 0
cur = self.head
while cur and count < index - 1:
'''当指针变量没有指空,并且计数器没有到达目标索引的时候进入循环'''
count += 1
cur = cur.next
if not cur:
'''如果指针指空了就报错'''
return 'Error'
node = ListNode(val)
node.next = cur.next
cur.next = node
改变元素
将第i
个元素的值改为val
(有些类似1.2.5.3 中间插入元素)
"""将第i个元素的值改为val"""
def change(self, index, val):
count = 0
cur = self.head
while cur and count < index - 1:
count += 1
cur = cur.next
if not cur:
return 'Error'
cur.val = val
删除元素
三种情况:头部删除、尾部删除以及中间删除
头部删除
def removeFront(self):
if self.head:
'还得先判断一下头节点是否存在'
self.head = self.head.next
尾部删除
移动到链表尾部倒数第二个元素——算法复杂度 O ( n ) O(n) O(n)
代码如下:
def removeRear(self):
if not self.head.next:
'如果节点少于2个则直接报错'
return 'Error'
cur = self.head
while cur.next.next:
'下下个节点还存在就说明还没有移动到倒数第二个节点'
cur = cur.next
cur.next = None
中间删除
删除列表中第i
个元素
代码如下:
def removeInside(self, index):
count = 0
cur = self.head
while cur.next and count < index - 1:
count += 1
cur = cur.next
if not cur:
return 'Error'
del_node = cur.next
cur.next = del_node.next