赞
踩
在几乎绝大多数的算法课中,链表都是放在第一节课的:链表非常基础而重要,对于后续理解、运用其他数据结构,或者学习算法来说,它都是很好的敲门砖。本篇文章,讲解如何在python中构造一个单链表,以及单链表的增加与删除操作。
(本文参考图片部分来自“Hello 算法”,这是一本Github上的开源书,也是非常优秀的学习材料,本文的行文风格和部分知识点也是来源于此;另外部分图片来自“编程导航算法村”,本文大部分知识点和思想过程来源于此)
(一个链表的结构如图所示)
链表(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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。