本系列为《Python数据结构与算法分析》第二版学习笔记,作者:布拉德利.米勒;戴维.拉努姆。
1、列表
列表是元素集合,其中每个元素都有一个相对于其他元素的位置,更具体的说,这种列表称为无序列表。
无序列表支持的操作:
- List()创建一个空列表。它不需要参数,且会返回一个空列表
- add(item)假设元素item之前不在列表中,并向其中添加item。
- remove(item)假设元素item已经在列表中,并从其中移除item。
- search(item)在列表中搜索item,返回布尔值。
- isEmpty()检查列表是否为空,返回布尔值。
- append(item)假设元素item之前不在列表中,并在列表的最后位置添加item。
- index(item)假设元素已经在列表中,返回该元素在列表中的位置,它接收一个元素作为参数,返回该元素的下标。
- insert(pos, item)假设元素Item之前不在列表中,同时假设pos是合理的值,并在位置pos处添加元素item。
- pop()假设列表不为空,移除列表中的最后一个元素。
- pop(pos)假设在指定位置pos处存在元素,移除该位置上的元素,它接收位置参数且会返回一个元素。
2、链表
无序列表需要维持元素之间的相对位置,但是不需要在连续的内存空间中维护这些位置信息。为了实现无序列表,我们构建链表。
节点是构造链表的基本数据结构,每一个节点对象必须持有至少两份信息。首先,节点必须包含列表元素,我们称之为数据变量。其次,节点必须保存指向下一个节点的引用。
构造Node类
class Node:
# 初始化节点,将next初始值设为None,这也被称为将节点接地
def __init__(self, initdata):
self.data = initdata
self.next = None
def getData(self):
return self.data
def getNext(self):
return self.next
def setData(self, newdata):
self.data = newdata
def setNext(self, newnext):
self.next = newnext
3、无序列表
无序列表是基于节点集合来构造的,每一个节点都通过显式的引用指向下一个节点。只要知道第一个节点的位置,其后的每一个元素都能通过下一个引用找到。因此无序列表类必须包含指向第一个节点的引用。
构造无序列表类:
class UnorderedList:
# None表明列表的头部没有指向任何节点,列表本身并不包含任何节点对象,而只有指向整个链表结构中第一个节点的引用
def __init__(self):
self.head = None
# isEmpty方法检查列表的头部是否指向None的引用
def isEmpty(self):
return self.head is None
# add方法为列表添加元素,列表中的每一个元素都必须被存放在一个节点对象中
def add(self, item):
# 第一步,为元素创建一个新节点
temp = Node(item)
# 第二步,将新节点的next引用指向当前列表的第一个节点
temp.setNext(self.head)
# 第三步,修改列表的头节点,使其指向新创建的节点
self.head = temp
# 为了实现length方法,需要遍历链表并且记录访问过多少个节点
def length(self):
# current为外部引用,它被初始化为列表的头节点
current = self.head
count = 0
# 遍历列表,只要current没有指向列表的结尾,就将其指向下一个节点
while current is not None:
count += 1
current = current.getNext()
return count
# search方法的实现与length方法相似
def search(self, item):
# 从头节点开始
current = self.head
found = False
# 遍历列表,如果当前元素值与搜索值不同而且未到末尾,就将current指向下一个节点
while current is not None and not found:
if current.getData() is item:
found = True
else:
current = current.getNext()
return found
def remove(self, item):
# 为了方便移除目标元素,这里引入双指针,快指针current和慢指针previous,这里假设目标值在列表中
# 在遍历过程中,current与之前一样,标记在链表中的当前位置,慢指针previous总是指向current上次访问的节点
current = self.head
previous = None
found = False
# 遍历列表
while not found:
# 如果发现目标值,退出遍历
if current.getData() == item:
found = True
# 如果未发现目标值,则将快慢指针向前移动一次
# 必须先将previous移动到current的位置,然后再移动current,这个顺序不能颠倒
else:
previous = current
current = current.getNext()
# 如果被移除的元素正好是链表的第一个元素,那么current会指向链表中的第一个节点,previous的值则是None
# 这种情况下,需要修改链表的头节点,而不是Previous指向的节点
if previous is None:
self.head = current.getNext()
else:
previous.setNext(current.getNext())
4、链表分析
在分析链表操作的时间复杂度时,考虑其是否需要遍历列表。以有n个节点的链表为例,isEmpty方法的时间复杂度是O(1),这是因为它只需要执行一步操作,即检查head引用是否为None。length方法时间复杂度为O(n),向无序列表中添加元素是O(1),这是因为无序链表新添元素都放在第一个位置。但对于有序列表的search、remove以及add都需要进行遍历操作,所有它们的时间复杂度都为O(n)。