在几乎绝大多数的算法课中,链表都是放在第一节课的:链表非常基础而重要,对于后续理解、运用其他数据结构,或者学习算法来说,它都是很好的敲门砖。本篇文章,讲解如何在python中构造一个单链表,以及单链表的增加与删除操作。
(本文参考图片部分来自“Hello 算法”,这是一本Github上的开源书,也是非常优秀的学习材料,本文的行文风格和部分知识点也是来源于此;另外部分图片来自“编程导航算法村”,本文大部分知识点和思想过程来源于此)
Python中如何构造一个链表
遍历链表(用于获取长度、查找目标值)
Python中增加链表元素
在链表首部增加元素
在链表中间增加元素
在链表尾部增加元素
总结
Python中删除链表元素
在链表首部删除元素
在链表中间删除元素
在链表尾部删除元素
总结
完整代码
(一个链表的结构如图所示)
链表(LinkedList)由节点(Node)顺序连接组成,每个节点包含一个值(Value)和指向下一个节点的指针(Pointer)或者叫引用(Reference)。
链表存在头节点(head),而且通常情况下,“头节点head”和“链表head”都是可以被接受的称呼。
链表的尾节点指向的是“空”,在Python中记为None。
链表定义方式:
class ListNode:
"""链表节点类"""
def __init__(self,val:int):
self.val = val #节点值
self.next = None # 指向下一节点的指针(引用)
def get_data(self):
return self.val
def set_data(self,val):
self.val = val
def get_next(self):
return self.next
def set_next(self,next):
self.next = next
在这样最正式的定义方式下,我们定义了链表节点类,并且使用四个set、get函数,来定义或获取每个节点的值以及节点之间的指向关系。
然而,在力扣(LeetCode)上的算法题中,还经常以另一种方式来创建链表:
class ListNode:
def __init__(self,val=0,next=None):
self.val = val
self.next = next
# 创建实例
listnoode = ListNode(1)
这种定义方式下能直接使用listnode.val和listnode.next进行操作,虽然违背了面向对象的设计要求,但无疑更加精简和方便。
在定义完链表之后,我们进行初始化链表操作。
例如,问我们初始化链表1 -> 3 -> 2 -> 5 -> 4。
# 初始化各个节点
n0 = ListNode(1)
n1 = ListNode(3)
n2 = ListNode(2)
n3 = ListNode(5)
n4 = ListNode(4)
# 构建引用指向
n0.next = n1
n1.next = n2
n2.next = n3
n3.next = n4
以上,我们成功实现了对链表的定义(创建)和初始化这两个操作。
核心代码就是"head = head.next",可以理解成一个指针在不断向后移动。
第一种,遍历获取链表长度(就是节点的总个数)
def length(self):
"""链表长度"""
# cur初始时指向头节点
cur = self._head
count = 0
# 尾节点指向None,当未到达尾部时
while cur != None:
count += 1
# 将cur后移一个节点
cur = cur.next
return count
(顺便一提,cur,pre,pos,target,count,temp,fast/slow(或fa/sl、f/s),res/ans,等都是常见变量名(因为大家都很偷懒(bushi,大意分别为:现在指针、前一指针、目标数、位置、计数、临时变量、快指针/慢指针、结果/答案,前两个在本文中就分别表示:现在的节点、前一个节点)
第二种,遍历链表,查找目标值:
def find(head: ListNode, target: int) -> int:
"""在链表中查找值为 target 的首个节点"""
index = 0
while head:
if head.val == target:
return index
head = head.next
index += 1
return -1
当我们需要在链表的 第N个元素后面 插入一个新的节点,我们该如何做呢?
当我们需要在表头直接插入一个元素的时候,我们可以直接创建一个新节点(newNode),让新节点指向原有的头节点head即可。
为了方便后续操作,我们一般还将“修改后”的头节点head变为我们插入的这个新节点newNode。
在中间增加元素较为复杂,因为我们需要先遍历找到正确的位置,随后进行修改。
而修改的过程同样有坑。
如下图,如果我们直接让(15)指向新节点(new),会导致原有的链表直接断开,因为新节点(new)的后面是一个None,而不是原来存在的(7)->(40)。
而且,由于此时原本的链表head已经被你修改过,Python会自动“帮你”回收掉“不需要”的原先后半截链表,甚至无法再改回最初的链表了。
因此,我们应该先创建一个指向关系,将新节点new和后续的(7)连接,再将(15)指向(new),如此我们总算让回收机制正确地帮助了我们(
与首部相似,我们只需要让尾节点(40)指向新节点(new)即可(因为在定义的时候,我们让任何新节点new的nex都t默认为None)
代码如下:
# 头部添加
def add(self, item):
"""头部添加元素"""
node = Node(item)
node.next = self._head
self._head = node
# 尾部添加
def append(self, item):
"""尾部添加元素"""
node = Node(item)
if self.is_empty():
self._head = node
else:
cur = self._head
while cur.next != None:
cur = cur.next
cur.next = node
# 指定位置添加(头、尾、中间)
def insert(self, pos, item):
"""指定位置添加元素"""
if pos < 0:
self.add(item)
elif pos >= self.length():
self.append(item)
else:
node = Node(item)
pre = self._head
count = 0
while count < (pos-1):
count += 1
pre = pre.next
node.next = pre.next
node = pre.next
当我们知道怎样增加链表元素,那么删除也是类似的。
与增加相反:当插入元素时,我们让新节点newNode.next=head,创造了一个指向关系;当删除时,我们舍去一层指向关系,让head.next等于head,即让(15)变成这个链表新的头节点。
在中间删除也是同样的较为复杂,因为我们仍然需要先遍历、再操作。
为了同样避免链条断开的风险。我们在删除操作中需要先遍历到要删除元素之前,这个先前节点的指针指向next的next,即直接跳过要被删除的元素。
核心代码:cur.next=cur.next.next
同样需要遍历,不过这次的遍历是遍历到最后一个元素之前,即倒数第二个元素,让倒数第二个元素的next变为None即可。
代码如下:
def remove(self, item):
"""删除节点"""
cur = self._head
pre = None
while cur != None:
if cur.item == item:
if pre is None:
self._head = cur
else:
pre.next = cur.n
else:
pre = cur
cur = cur.next
class Node(object):
"""单链表节点"""
def __init__(self, item):
# _item存放数据元素
self.item = item
# _next是下一个节点的标识
self.next = None
def is_empty(self):
"""判断链表是否为空"""
return self._head == None
def length(self):
"""链表长度"""
cur = self._head
count = 0
while cur != None:
count += 1
cur = cur.next
return count
# 头部添加
def add(self, item):
"""头部添加元素"""
node = Node(item)
node.next = self._head
self._head = node
# 尾部添加
def append(self, item):
"""尾部添加元素"""
node = Node(item)
if self.is_empty():
self._head = node
else:
cur = self._head
while cur.next != None:
cur = cur.next
cur.next = node
# 指定位置添加
def insert(self, pos, item):
"""指定位置添加元素"""
if pos < 0:
self.add(item)
elif pos >= self.length():
self.append(item)
else:
node = Node(item)
pre = self._head
count = 0
while count < (pos-1):
count += 1
pre = pre.next
node.next = pre.next
node = pre.next
# 删除结点
def remove(self, item):
"""删除节点"""
cur = self._head
pre = None
while cur != None:
if cur.item == item:
if pre is None:
self._head = cur.next
else:
pre.next = cur.next
else:
pre = cur
cur = cur.next