定义一个单链表节点类
class Node(object):
def __init__(self, data, next=None):
self.data = data
self.next = next
遍历
使用一个临时的指针变量。这个变量先初始化为链表结构的head指针,然后控制一个循环。如下所示:
probe = head
while probe != None:
<use or modify probe.data>
probe = probe.next
搜索
一个链表结构的顺序搜索和遍历是类似的,因为也必须从第1节点开始并且沿着链接直到遇到哨兵。然而,在这个例子中,会出现两个哨兵:
1. 空链接,表明不再有要检查的数据。
2. 等于目标项的一个数据项,表明一次成功的搜索。
如下是搜索一个给定项的形式:
probe = head
while probe != None and targetItem != probe.data:
probe = probe.next
if probe != None:
<targetItem has been found>
else:
<targetItem is not in the linked structure>
平均情况下,对于单链表结构的顺序搜索是线性的。
访问一个链表结构中的第 i 项,也是一次顺序搜索操作。这是因为,必须从第1个节点开始统计链接的数目,直到遇到第 i 个节点。可以假设 0 <= i < n ,其中 n 是链表结构中的节点数目。如下是访问第 i 项的代码:
# Assumes 0 <= index < n
probe = head
while index > 0:
probe = probe.next
index -= 1
return probe.data
和数组不同,链表结构并不支持随机访问。
替换
单链表结构中的替换操作也利用了遍历模式。在链表结构中搜索一个给定项或一个给定位置,并且用新的项替换该项。
第一个操作,即替换一个给定的项,并不需要假设目标项在链表结构中。如果目标项不存在,那就不会发生替换,并且该操作返回False。如果目标项存在,新的项会替换它,并且该操作返回True。如下是该操作的形式:
probe = head
while probe != None and targetItem != probe.data:
probe = probe.next
if probe != None:
probe.data = newItem
return True
else:
return False
还有一个替换第 i 项的操作,假设 0 <= i < n ,其形式如下:
# Assumes 0 <= index < n
probe = head
while index > 0:
probe = probe.next
index -= 1
probe.data = newItem
两种替换操作在平均情况下都是线性的。
在开始处插入
在结构的开始处插入一项。其形式如下:
head = Node(newItem, head)
在第一种情况下,head指针是None,因此,向结构中插入了第1项。在第2中情况下,将第2项插入到了同一结构的开头处。
注意在第2种情况中,不需要复制数据并向下移动数据,并且并不需要额外的内存。这意味着,在一个链表结构的开始处插入数据,时间和内存都是常数,这与数组的相应操作有所不同。
在末尾插入
对于单链表结构来说,在末尾插入一项必须考虑如下两种情况:
1. head指针为None,此时,将head指针设置为新的节点。
2. head指针不为None,此时,代码将搜索最后一个节点,并将其next指针指向新的节点。
第2种情况又回到了遍历模式。其形式如下:
newNode = Node(newItem)
if head is None:
head = newNode
else:
probe = head
while probe.next != None:
probe = probe.next
probe.next = newNode
从开始处删除
在这种类型的操作中,通常可以假设结构中至少有一个节点。这个操作返回删除项。其形式如下:
removedItem = head.data
head = head.next
return removedItem
该操作使用的时间和内存都是常数的。
从末尾删除
对于单链表结构来说,从末尾删除的操作假设结构中至少有一个节点。需要考虑如下的两种情况:
1. 只有一个节点。head指针设置为None。
2. 在最后一个节点之前有一个节点。代码搜索倒数第2个节点并将其next指针设置为None。
在这两种情况下,代码都返回了所删除的节点所包含的数据项。如下:
removedItem = head.data
if head.next is None:
head = None
else:
probe = head
while probe.next.next != None:
probe = probe.next
removedItem = probe.next.data
probe.next = None
return removedItem
该操作在时间上和内存上都是线性的。
在任何位置插入
要在 i 位置插入,必须首先找到位置为 i-1 (如果 i < n )或 n-1 (如果 i >= n )的节点。然后,考虑如下两种情况:
1. 该节点的next指针为None。这意味着, i >= n,因此,应该将新的项放在链表结构的末尾。
2. 该节点的next指针不为None。这意味着, 0 < i < n ,因此,必须将新的项放在位于 i - 1和 i 的节点之间。
和搜索第 i 项相同,插入操作必须计数节点,直到到达了想要的位置。然而,由于目标索引可能会大于或等于节点的数目,因此在搜索中,必须小心避免超出链表结构的末尾。为了解决这个问题,循环有一个额外的条件,就是测试当前节点的next指针,看看这个节点是否是最后一个节点。形式如下:
if head is None or index <= 0:
head = Node(newItem, head)
else:
probe = head
while index > 1 and probe.next != None:
probe = probe.next
index -= 1
probe.next = Node(newItem, probe.next)
和使用了遍历模式的单链表结构操作一样,这个操作的性能也是线性的。然而,它使用的内存是常数的。
从任意位置删除
从一个链表结构中删除第 i 个项,具有一下3种情况:
1. i <= 0——使用删除第1项的代码。
2. 0 < i < n——搜索位于 i - 1位置的节点,删除其后面的节点。
3. i >= n——删除最后一个节点。
假设链表结构至少有1项。这个模式类似于插入节点所使用的模式,必须保证不会超过链表结构的末尾。换言之,必须允许probe指针不会超过链表结构的倒数第2个节点。形式如下:
if index <= 0 or head.next is None:
removedItem = head.data
return removedItem
else:
probe = head
while index > 1 and probe.next.next != None:
probe = probe.next
index -= 1
removedItem = probe.next.data
probe.next = probe.next.next
return removedItem
复杂度权衡:时间、空间和单链表结构
操作 | 运行时间 |
---|---|
在第 i 个位置访问 | O(n),平均情况 |
在第 i 个位置替换 | O(n),平均情况 |
在开始处插入 | O(1),最好情况和最差情况 |
从开始处删除 | O(1),最好情况和最差情况 |
在第 i 个位置插入 | O(n),平均情况 |
从第 i 个位置删除 | O(n),平均情况 |