数组: 数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。
数组与链表的插入和删除操作:
例如在数组索引1(1, 2, 3, 4)处插入,为保持内存数据的连续,还需把3,4往后搬移一个位置。
数组与链表的访问操作:
数组的寻址公式:
a[i]_address = base_address + i * data_type_size
;其中data_type_size表示数组中每个元素的大小。
需要注意的是:尽管链表单纯删除操作时间复杂度是O(1),但遍历查找的时间是主要的耗时点,对应的时间复杂度为O(n)。根据时间复杂度分析中的加法法则,删除值等于给定值的结点对应的链表操作的总时间复杂度为O(n)。
根据链表结构特征进行抽象,需要设计两个类:
head、tail:记录起始、终止位置
指针概念:
有些语言有“指针”的概念,比如C语言;有些语言没有指针,取而代之的是“引用”,比如Java、Python。不管是“指针”还是“引用”,实际上,它们的意思
都是一样的,都是存储所指对象的内存地址。
将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这
个变量。
单向链表和双向链表。 在实际的软件开发中,从链表中删除一个数据无外乎这两种情况:
对于第一种情况,不管是单链表还是双向链表,为了查找到值等于给定值的结点,都需要从头结点开始一个一个依次遍历对比,直到找到值等于给定值的结点,然后再通过我前面讲的指针操作将其删除。
对于第二种情况,我们已经找到了要删除的结点,但是删除某个结点q需要知道其前驱结点,而单链表并不支持直接获取前驱结点,所以,为了找到前驱结点,我们还是要从头结点开始遍历链表。但是对于双向链表来说,这种情况就比较有优势了。因为双向链表中的结点已经保存了前驱结点的指针,不需要像单链表那样遍历。所以,针对第二种情况,单链表删除操作需要O(n)的时间复杂度,而双向链表只需要在O(1)的时间复杂度内就搞定了。
插入操作同理。
class LinkedItem:
def __init__(self, info, back_seek=None, front_seek=None):
self.info = info
self.back_seek = back_seek
self.front_seek = front_seek
class LinkedList:
def __init__(self):
# 所谓的链表无序,是指链表的一个个节点在内存中存在任何地方,不是在堆栈中有序保存的,通过指针,指向下一个节点在内存中的位置。
# 这里的用列表只是一个容器,节点总要保存吧,与链表无序是两回事,而且列表保存的是链表的引用,只是一个个ID。
self.__linked_list = [] # 不需要插入的列表来说,检索方便,但是插入、remove不合适
def append(self, node: LinkedItem):
if self.__linked_list:
self.__linked_list[-1].back_seek = id(node)
self.__linked_list.append(node)
def iter_nodes(self, reverse=False):
n = len(self.__linked_list)
while n > -1:
yield self.__linked_list[n]
n -= 1
class DoubleLinkedList(LinkedList):
"""使用列表索引,暴力解法
优点:检索方便
缺点:对于频繁插入、remove的运用场景,效率太低,不合适"""
def __init__(self):
super(DoubleLinkedList, self).__init__()
self.__linked_list = []
def append(self, node: LinkedItem):
if self.__linked_list:
self.__linked_list[-1].back_seek = id(node)
node.front_seek = id(self.__linked_list[-1])
self.__linked_list.append(node)
def pop(self, node_seek):
node_idx = None
for idx, cur_node in enumerate(self.__linked_list):
if id(cur_node) == node_seek:
node_idx = idx
break
# todo 考虑起点、终点、以及节点不存在情况
back_node = self.__linked_list[node_idx-1]
back = self.__linked_list[node_idx + 1]
back_node.back_seek = id(back)
back.front_seek = id(back_node)
return self.__linked_list.pop(node_idx)
def remove(self, node: LinkedItem):
node_id = id(LinkedItem)
for idx, cur_node in enumerate(self.__linked_list):
if id(cur_node) == node_id:
# todo 考虑起点、终点、以及节点不存在情况
self.__linked_list[idx-1].back_seek = id(self.__linked_list[idx+1])
self.__linked_list[idx+1].front_seek = id(self.__linked_list[idx - 1])
break
return self.__linked_list.remove(node)
def insert(self, node: LinkedItem, insert_seek):
node_id = id(LinkedItem)
insert_idx = None
for idx, cur_node in enumerate(self.__linked_list):
if id(cur_node) == insert_seek:
# todo 考虑起点、终点、以及节点不存在情况
insert_idx = idx+1
self.__linked_list[idx].back_seek = node_id
self.__linked_list[idx+1].front_seek = node_id
node.front_seek = id(self.__linked_list[idx])
node.back_seek = id(self.__linked_list[idx+1])
break
self.__linked_list.insert(insert_idx, node)
class Item:
def __init__(self, val, next_node=None, front_node=None):
self.val = val
self.next_node = next_node
self.front_node = front_node
def __repr__(self):
return str(self.val)
def __str__(self):
return str(self.val)
class LinkedListImprove:
"""
容器,记录节点,实现迭代、append、pop、insert、remove、getitem
append:增加节点
pop:从尾部弹出节点
insert:在指定索引处插入节点,超界头部或尾部插入
remove:移除指定索引处的节点,超界移除尾部
getitem:获取指定索引节点
"""
def __init__(self):
self.head = None
self.tail = None
def append(self, val):
"""尾部增加节点"""
node = Item(val)
# 更新head、tail
# 0个节点、1个节点、2个节点情况
if self.head is None:
self.head = node
else:
self.tail.next_node = node
node.front_node = self.tail
self.tail = node
def iter_items(self, reverse=False):
# 支持正反迭代
current = self.tail if reverse else self.head
while current:
yield current
current = current.front_node if reverse else current.next_node
def getitem(self, idx):
"""获取指定索引处的节点"""
current = None
for i, tmp_node in enumerate(self.iter_items()):
if i == idx:
current = tmp_node
break
if current is None:
raise IndexError('Index out of range')
return current
def pop(self):
"""从尾部弹出节点"""
if self.head is None: # 没有节点
raise Exception('Empty')
ret_node = self.tail
prev = ret_node.front_node
if prev is None: # 只有1个节点
self.head = None
else: # 两个或以上
prev.next_node = None
self.tail = prev
return ret_node
def insert(self, idx, val):
"""在指定索引处插入节点,超界尾部追加"""
node = Item(val)
current = None
for i, tmp_node in enumerate(self.iter_items()):
if i == idx:
current = tmp_node
break
# 超界尾部追加
if current is None:
self.append(val)
return
# 头部插入,处理head
prev = current.front_node
if prev is None:
node.next_node = current
current.front_node = node
self.head = node
else:
# 中间插入
prev.next_node = node
node.front_node = prev
node.next_node = current
current.front_node = node
def remove(self, idx):
"""移除指定索引处的节点,超界return"""
current = None
for i, tmp_node in enumerate(self.iter_items()):
if i == idx:
current = tmp_node
break
if current is None:
return
prev = current.front_node
next_node = current.next_node
# 只有一个节点
if prev is None and next_node is None:
self.head = None
self.tail = None
# 头部移除,两个以上节点
elif prev is None and next_node:
next_node.front_node = None
self.head = next_node
# 尾部移除,两个以上节点
elif prev and next_node is None:
prev.next_node = None
self.tail = prev
else:
prev.next_node = next_node
next_node.front_node = prev
del current
if __name__ == '__main__':
# 测试
lst = LinkedListImprove()
item = Item('start')
lst.append(item)
item = Item(1)
lst.append(item)
item = Item(2)
lst.append(item)
item = Item(3)
lst.append(item)
item = Item(4)
lst.append(item)
item = Item(5)
lst.append(item)
item = Item(6)
lst.append(item)
item = Item('mid')
lst.append(item)
item = Item(7)
lst.append(item)
item = Item(8)
lst.append(item)
item = Item('end')
lst.append(item)
for n in lst.iter_items():
print(n)
print('~~~~~~~~~~~~~~~~~~~~~')
lst.pop()
lst.pop()
lst.pop()
for n in lst.iter_items():
print(n)
print('~~~~~~~~~~~~~~~~~~~~~')
lst.insert(1, 0)
lst.insert(0, 'enter')
lst.insert(15, 'exit')
for n in lst.iter_items():
print(n)
print('~~~~~~~~~~~~~~~~~~~~~')
lst.remove(0)
lst.remove(8)
lst.remove(8)
lst.remove(8)
for n in lst.iter_items():
print(n)
print('~~~~~~~~~~~~~~~~~~~~~')
容器功能的魔术方法有:__len__、__getitem__、__iter__、__setitem__
。
功能简介:
__getitem__(self, item)
:该方法实现了通过lst[key]
方式访问容器元素的功能,对于字典,item就是key,对于序列对象item就是索引index,序列对象还切片功能也是通过该方法实现。__setitem__(self, key, value)
: 该方法实现了通过lst[key] = value
方法修改容器元素的功能,对于字典key、value就是键值对,序列对象就是在指定索引处插入的元素值.__iter__
: 必须返回一个可迭代对象。__len__
: 容器必须实现该方法。对于一个类的实例,如果位于逻辑表达式位置或bool(instance),如果类中未实现bool,则会调用__len__
。这也是为什么容器位于逻辑表达式位置时可以等效布尔。
代码实现
from tool.logger_define import get_log
logger = get_log(__name__)
class Item:
def __init__(self, val, back=None, prev=None):
self.val = val
self.back = back
self.prev = prev
def __repr__(self):
return str(self.val)
__str__ = __repr__
class LinkedList:
def __init__(self):
self.head = None
self.tail = None
self.len = 0
def __len__(self):
return self.len
def __iter__(self, reverse=False):
current = self.tail if reverse else self.head
while current:
yield current
current = current.prev if reverse else current.back
def __getitem__(self, idx):
"""获取索引为idx的节点;支持负向索引
通过lst[key]实现访问,字典item就是key,序列对象就是索引index ,序列对象还要能切片
"""
item = None
idx = idx if idx >= 0 else (abs(idx) if abs(idx) > self.len else (idx + self.len)) # 正、负索引超界都返回None
for i, cur_item in enumerate(self):
if i == idx:
item = cur_item
break
return item
def __setitem__(self, idx, val):
"""通过lst[key] = value实现修改,字典就是key、value对,序列对象就是在指定索引处插入元素"""
self.insert(idx, val)
def insert(self, idx, val):
"""在指定索引插入节点, 超界尾部插入"""
# 链表为空
if self.head is None:
self.append(val)
return
current_item = self[idx]
logger.info("current_item={}".format(current_item))
# 正向索引超界,尾部插入;负向索引超界,头部插入
if current_item is None:
if idx >= 0:
self.append(val)
return
current_item = self.head
item = Item(val)
prev = current_item.prev
current_item.prev = item
item.back = current_item
if prev is None:
# 头部插入,更新head
self.head = item
else:
item.prev = prev
prev.back = item
self.len += 1
def append(self, value):
"""尾部追加节点"""
item = Item(value)
# 节点数为0
if self.tail is None:
self.head = item
else:
self.tail.back = item
item.prev = self.tail
self.tail = item
self.len += 1
def pop(self):
"""从尾部移除节点"""
if self.tail is None:
return
ret_node = self.tail
prev = self.tail.prev
if prev is None:
# 只有一个节点
self.head = None
else:
prev.back = None
self.tail = prev
self.len -= 1
return ret_node
def remove(self, idx):
"""移除指定索引位置的节点"""
current_node = self[idx]
if current_node is None:
raise IndexError("Index out of range")
prev = current_node.prev
back = current_node.back
# 尾部移除
if back is None:
self.pop()
return
# 头部移除,更新头部
if prev is None:
self.head = back
else:
prev.back = back
back.prev = prev
self.len -= 1
def main():
lst = LinkedList()
# lst = GuardLinkedList()
for j in ['start', 1, 2, 3, 4, 'mid', 5, 6, 7, 8, 9, 10, 'end']:
lst.append(Item(j))
logger.info('~~~~~~~~~测试迭代~~~~~~~~~~~~~~~')
logger.info([cur_node for cur_node in lst])
logger.info('~~~~~~测试索引~~~~~~~')
logger.info("{}".format([lst[-len(lst)], lst[-1], lst[18], lst[-100]]))
logger.info('~~~~~~~~测试__setitem__~~~~~~~~~~~')
lst[len(lst)] = 'insert15'
lst[0] = 'insert0'
lst[3] = 'insert3'
logger.info([cur_node for cur_node in lst])
logger.info('~~~~测试remove操作~~~~~~~')
logger.info("before:{}".format([cur_node for cur_node in lst]))
logger.info(-len(lst))
lst.remove(-len(lst)), lst.remove(-1), lst.remove(2)
logger.info("after:{}".format([cur_node for cur_node in lst]))
if __name__ == '__main__':
main()
针对链表的插入、删除操作,需要对插入第一个结点和删除最后一个结点的情况进行特殊处理。这样代码实现起来就会很繁琐,不简洁,而且也容易因为考虑不全而出错。如何来解决这个问题呢?
哨兵,解决的是国家之间的边界问题。同理,这里说的哨兵也是解决“边界问题”的,不直接参与业务逻辑。利用哨兵简化实现难度
如果我们引入哨兵结点,在任何时候,不管链表是不是空,head指针都会一直指向这个哨兵结点。这种有哨兵结点的链表叫带头链表。相反,没有哨兵结点的链表就叫作不带头链表。
代码实现–继承自LinkedList
from tool.logger_define import get_log
logger = get_log(__name__)
class GuardLinkedList(LinkedList):
def __init__(self):
super(GuardLinkedList, self).__init__()
self.head = Item('Guard')
self.tail = self.head
self.len = 1
def insert(self, idx, val):
"""在指定索引插入节点, 超界尾部插入"""
current_item = self[idx]
logger.info("current_item={}".format(current_item))
# 正向索引超界,尾部插入;负向索引超界,头部插入
if current_item is None:
if idx >= 0:
self.append(val)
return
current_item = self.head.back # 负向索超界,取哨兵节点的下一个节点
if current_item is self.head:
# 解决索引为0时取到了哨兵节点
current_item = self.head.back
item = Item(val)
prev = current_item.prev
current_item.prev = item
item.back = current_item
item.prev = prev
prev.back = item
self.len += 1
def append(self, value):
"""尾部追加节点"""
item = Item(value)
self.tail.back = item
item.prev = self.tail
self.tail = item
self.len += 1
def pop(self):
"""从尾部移除节点"""
ret_node = None
if self.tail is self.head:
# 哨兵节点
return ret_node
ret_node = self.tail
prev = ret_node.prev
prev.back = None
self.tail = prev
self.len -= 1
return ret_node
def remove(self, idx):
"""移除指定索引位置的节点"""
current_node = self[idx]
if current_node is None:
raise IndexError("Index out of range")
if current_node is self.head:
current_node = self.head.back
prev = current_node.prev
back = current_node.back
# 尾部移除
if back is None:
self.pop()
return
prev.back = back
back.prev = prev
self.len -= 1