为什么需要链表
数组的底层原理是顺序存储,是一块连续的内存空间,有了这块内存空间的首地址,就能直接通过索引计算出任意位置的元素地址。
链表不一样,一条链表并不需要一整块连续的内存空间存储元素。链表的元素可以分散在内存空间的天涯海角,通过每个节点上的next
, prev
指针,将零散的内存块串联起来形成一个链式结构。
这样做的好处很明显,首先就是可以提高内存的利用效率,链表的节点不需要挨在一起,给点内存 new 出来一个节点就能用,操作系统会觉得这娃好养活。
可以抽象为一条弯曲的链子,通过一个个环连接在一起。
另外一个好处,它的节点要用的时候就能接上,不用的时候拆掉就行了,从来不需要考虑扩缩容和数据搬移的问题,理论上讲,链表是没有容量限制的(除非把所有内存都占满,这不太可能)。
当然,不可能只有好处没有局限性。数组最大的优势是支持通过索引快速访问元素,而链表就不支持。
这个不难理解吧,因为元素并不是紧挨着的,所以如果你想要访问第 3 个链表元素,你就只能从头结点开始往顺着 next 指针往后找,直到找到第 3 个节点才行。
单链表
链表的创建
class Listnode:def __init__(self, x):self.val = xself.next = Nonedef create_linked_list(nums):head = Listnode(nums[0])cur = headfor i in range(1, len(nums)):cur.next = Listnode(nums[i])cur = cur.nextreturn headdef print_linked_list(current_node):while current_node:print(current_node.val, end='->')current_node = current_node.nextprint('None')if __name__ == '__main__':nums = [1, 2, 3, 4, 5]linked_list = create_linked_list(nums)print_linked_list(linked_list)
最终的输出结果:1->2->3->4->5->None
链表的实现
class ListNode:def __init__(self, val):self.val = valself.next = Noneclass MyLinkedList(object):def __init__(self):self.size = 0self.head = ListNode(0)def get(self, index):"""从链表中获取指定位置的元素值。:param index: 要获取元素的位置索引,从0开始计数。:type index: int:return: 如果索引有效,则返回对应位置的元素值;否则返回-1。:rtype: int"""# 检查索引是否超出链表范围if index < 0 or index >= self.size:return -1cur = self.head# 遍历链表至指定索引位置for _ in range(index + 1):cur = cur.nextreturn cur.val # 返回指定位置的元素值def addAtHead(self, val):""":type val: int:rtype: None"""self.addAtIndex(0, val)def addAtTail(self, val):""":type val: int:rtype: None"""self.addAtIndex(self.size, val)def addAtIndex(self, index, val):"""在指定索引处插入一个新节点。:param index: 要插入节点的索引位置。如果索引大于当前链表长度,则不做任何操作;如果索引小于0,则将节点插入到链表开头。:param val: 要插入的节点的值。:return: None"""if index > self.size: # 如果索引大于链表长度,则直接返回,不做任何操作returnif index < 0: # 如果索引小于0,将节点插入到链表开头index = 0self.size += 1 # 更新链表大小pre = self.head# 定位到要插入节点的前一个节点for _ in range(index):pre = pre.next # 逐个遍历,找到正确的位置to_add = ListNode(val) # 创建新节点to_add.next = pre.next # 新节点指向pre节点的next节点pre.next = to_add # pre的next指向新节点def deleteAtIndex(self, index):"""从链表中删除指定索引的元素。:param index: 要删除的元素的索引。如果索引无效(小于0或大于等于链表长度),则不进行任何操作。:type index: int:return: None"""if index < 0 or index >= self.size:returnself.size -= 1pre = self.head# 移动到要删除节点的前一个节点for _ in range(index):pre = pre.next# 要删除节点的前一个节点next指向删除节点的后一个节点pre.next = pre.next.next
双链表
链表的创建
class ListNode:def __init__(self, val):self.val = valself.next = Noneself.prev = Nonedef create_double_linked_list(arr):if not arr:return Nonehead = ListNode(arr[0])cur = headfor i in range(1, len(arr)):node = ListNode(arr[i])cur.next = nodenode.prev = curcur = cur.nextreturn headdef print_double_linked_list(head):while head:print(head.val, end='->')head = head.nextif __name__ == '__main__':head = create_double_linked_list([1, 2, 3, 4, 5])print_double_linked_list(head)
链表的实现
class ListNode:def __init__(self, x):self.val = xself.next = Noneself.prev = Noneclass MyLinkedList:def __init__(self):self.size = 0self.head, self.tail = ListNode(0), ListNode(0)self.head.next = self.tailself.tail.prev = self.headdef get(self, index: int) -> int:"""从链表中获取指定位置的元素值。"""# 检查索引是否超出链表范围if index < 0 or index >= self.size:return -1# 选择从头结点还是尾结点开始遍历取决于索引的位置,以优化遍历效率# 以索引值作为分割,左边短就从头结点开始遍历,右边短就从尾结点开始遍历if index + 1 < self.size - index:curr = self.head# 遍历链表到达指定索引位置for _ in range(index + 1):curr = curr.nextelse:curr = self.tail# 遍历链表到达指定索引位置for _ in range(self.size - index):curr = curr.prev# 遍历链表到达指定索引位置return curr.valdef addAtHead(self, val: int) -> None:self.addAtIndex(0, val)def addAtTail(self, val: int) -> None:self.addAtIndex(self.size, val)def addAtIndex(self, index: int, val: int) -> None:"""在双向链表的指定位置插入一个新节点,并保持双向链表的有序。"""# 处理index大于链表长度的情况,直接返回不进行操作if index > self.size:return# 将负索引调整为0if index < 0:index = 0# 确定插入位置的前驱节点和后继节点if index < self.size - index:# 当index在链表前半部分时,正常计算前驱节点和后继节点pred = self.headfor _ in range(index):pred = pred.nextsucc = pred.nextelse:# 当index在链表后半部分时,从尾部向前计算前驱节点和后继节点succ = self.tailfor _ in range(self.size - index):succ = succ.prevpred = succ.prev# 更新链表大小self.size += 1# 创建新节点并插入到链表中to_add = ListNode(val)to_add.next = succto_add.prev = predpred.next = to_addsucc.prev = to_adddef deleteAtIndex(self, index: int) -> None:if index < 0 or index >= self.size:returnif index < self.size - index:# 当index在链表前半部分时,正常计算前驱节点和后继节点pred = self.headfor _ in range(index):pred = pred.nextsucc = pred.next.nextelse:# 当index在链表后半部分时,从尾部向前计算前驱节点和后继节点succ = self.tailfor _ in range(self.size - index - 1):succ = succ.prevpred = succ.prev.prevself.size -= 1pred.next = succsucc.prev = pred