浅谈链表
链表作为很常用的数据结构,很多同学在日常使用过程中或多或少都会遇到链表相关的问题,其中最常见的就是链表遍历和指针使用的问题。
通过亲自手写链表,可以帮助我们深入理解链表。数据结构的工程化使用,无外乎就是增删查改,当我们亲手实现一次链表的功能,链表这个数据结构就算真正入门了。
本文参考[代码随想录]
707. 设计链表
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:val
和 next
。val
是当前节点的值,next
是指向下一个节点的指针/引用。
如果是双向链表,则还需要属性 prev
以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。
实现 MyLinkedList
类:
MyLinkedList()
初始化MyLinkedList
对象。int get(int index)
获取链表中下标为index
的节点的值。如果下标无效,则返回-1
。void addAtHead(int val)
将一个值为val
的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。void addAtTail(int val)
将一个值为val
的节点追加到链表中作为链表的最后一个元素。void addAtIndex(int index, int val)
将一个值为val
的节点插入到链表中下标为index
的节点之前。如果index
等于链表的长度,那么该节点会被追加到链表的末尾。如果index
比长度更大,该节点将 不会插入 到链表中。void deleteAtIndex(int index)
如果下标有效,则删除链表中下标为index
的节点。
删除链表节点:
添加链表节点:
Tips
这里给大家介绍一个我对节点指针使用的理解:
例1 current_node.next = node # current_node的next指针指向了node
例2 current_node = current_node.next # 将current_node的下一个节点赋给current_node,实则是节点向后遍历。
node.next 出现在等号左边,表明node的next指针将指向等号右边那个节点,是“指向”的概念!
node.next 出现在等号右边,指代的是node的next指针指向的那个节点,是“节点”的概念!
构造单向链表
单向链表即对于每一个节点,只有一个指针指向并找到下一个节点,不能找到上一个节点。
对于单向链表,我们通常会构造一个虚拟头结点dummy_head,其好处在于处理链表头结点时比较方便。
注意:链表节点函数定义ListNode由我们自己手动构造!!!
# 单独定义链表节点
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
# 虚拟头结点和链表长度必不可少
self.dummy_head = ListNode()
self.size = 0
def get(self, index: int) -> int:
if index < 0 or index >= self.size:
return -1
cur = self.dummy_head.next
for _ in range(index):
cur = cur.next
return cur.val
def addAtHead(self, val: int) -> None:
self.dummy_head.next = ListNode(val, self.dummy_head.next)
self.size += 1
def addAtTail(self, val: int) -> None:
cur = self.dummy_head
while cur.next:
cur = cur.next
cur.next = ListNode(val)
self.size += 1
def addAtIndex(self, index: int, val: int) -> None:
if index < 0 or index > self.size:
return
cur = self.dummy_head
for _ in range(index):
cur = cur.next
new_node = ListNode(val, cur.next)
cur.next = new_node
self.size += 1
def deleteAtIndex(self, index: int) -> None:
if index < 0 or index >= self.size:
return
cur = self.dummy_head
for _ in range(index):
cur = cur.next
cur.next = cur.next.next
self.size -= 1
构造双向链表
双向链表,就是其上的每一个节点都自带两个指针prev和next,分别指向上一个节点和下一个节点,于是每一个节点都能向前或向后遍历。
此处我们构造双向链表,使用了类似虚拟头尾节点的方式,head和tail都是实际的链表头和尾,这是不同于上面dummy_head的地方。
最值得注意的地方在于,当链表为空时,head = tail = None, 当链表只有一个节点时,head就是tail。
# 双向链表节点定义,向前和向后的指针都要有
class ListNode:
def __init__(self, val=0, prev=None, next=None):
self.val = val
self.next = next
self.prev = prev
class MyLinkedList:
def __init__(self):
# head 和 tail都表示实际的链表头尾,故在后续操作中需时刻注意
self.head = None
self.tail = None
self.size = 0
def get(self, index: int) -> int:
if index < 0 or index >= self.size:
return -1
if index < self.size // 2:
cur = self.head
for _ in range(index):
cur = cur.next
else:
cur = self.tail
for _ in range(self.size - index - 1):
cur = cur.prev
return cur.val
def addAtHead(self, val: int) -> None:
# 直接新构造new_node,使其next指针指向当前头结点head
new_node = ListNode(val, None, self.head)
# 如果当前头结点不为None,则将其prev指向new_node
if self.head:
self.head.prev = new_node
# 若当前头结点为None,则意味着链表为空,新增头结点后整个链表只有一个节点,故需将头和尾节点都设为new_node
else:
self.tail = new_node
# 无论如何,新的头结点都将是new_node
self.head = new_node
self.size += 1
def addAtTail(self, val: int) -> None:
# 直接新构造new_node,使其prev指针指向当前头结点tail
new_node = ListNode(val, self.tail, None)
# 如果当前尾结点不为None,则将其next指向new_node
if self.tail:
self.tail.next = new_node
# 若当前尾结点为None,则意味着链表为空,新增尾结点后整个链表只有一个节点,故需将头和尾节点都设为new_node
else:
self.head = new_node
# 无论如何,新的尾结点都将是new_node
self.tail = new_node
self.size += 1
def addAtIndex(self, index: int, val: int) -> None:
if index < 0 or index > self.size:
return
if index == 0:
self.addAtHead(val)
elif index == self.size:
self.addAtTail(val)
else:
# 不论从前向后,或是从后向前遍历,都将cur指向待添加位置的前一位
if index < self.size // 2:
cur = self.head
for _ in range(index - 1):
cur = cur.next
else:
cur = self.tail
for _ in range(self.size - index):
cur = cur.prev
new_node = ListNode(val, cur, cur.next)
cur.next.prev = new_node
cur.next = new_node
self.size += 1
def deleteAtIndex(self, index: int) -> None:
# 若是空链表则直接返回
if index < 0 or index >= self.size:
return
if index == 0:
self.head = self.head.next
# 若此时head不为None,则将其prev指向None
if self.head:
self.head.prev = None
# 若此时head为None,则说明此时链表为空,需要将尾结点tail同步置为None
else:
self.tail = None
elif index == self.size - 1:
self.tail = self.tail.prev
# 若此时tail不为None,则将其next指向None
if self.tail:
self.tail.next = None
# 若此时tail为None,则说明此时链表为空,需要将尾结点head同步置为None
else:
self.head = None
else:
# 不论从前向后,或是从后向前遍历,都将cur指向待删除节点的前一位
if index < self.size // 2:
cur = self.head
for _ in range(index):
cur = cur.next
else:
cur = self.tail
for _ in range(self.size - index - 1):
cur = cur.prev
cur.prev.next = cur.next
cur.next.prev = cur.prev
self.size -= 1