[英]Linked list pointer is confusing
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class SinglyLinkedList:
def __init__(self):
self.head = None
self.tail = None
def __len__(self):
size, temp = 0, self.head
while temp:
temp = temp.next
size += 1
return size
def middle(self) -> Optional[ListNode]:
temp, newList = self.head, ListNode('whatever lmao')
current = newList
size = len(self)
for i in range(size):
if i >= math.floor(size/2):
current.next = temp
current = current.next
temp = temp.next
return newList.next
最近刚学数据结构,想做一个链表,有一个中间函数,返回链表的中间值。
我以某种方式设法通过查看一些参考来做到这一点,但是代码有点令人困惑,我不明白它为什么会起作用。
在middle
函数中,我声明了一个 var current
并将一个listnode
类对象newList
分配给它。 所以从逻辑上讲,因为它是一个 OOP,它应该只将类复制到current
的 var 中,对吗?
还是它实际上变成了一个指针?
(但我不认为这是一个指针,因为我可以返回current.next
)。 对我来说最令人困惑的部分是newList
应该与current
完全没有连接,但它形成了一个链表,如果它实际上与current
有连接,为什么我在 for 循环上对current
所做的更改不会影响newList
?
当我将listnode
分配给匿名变量时会发生什么?
我首先要指出的是,我可能已经使用以下方法实现了这一点:
def middle(self):
steps = len(self) // 2
current = self.head
for _ in range(steps):
current = current.next
return current
在这里,您使用截断/取整除以将列表的长度除以 2,您称之为steps
。 这将是您从列表头开始的跳数。
然后你创建一个名为current
的变量,它就是你当前的位置。 有时这是一个“walker”或“context”或“pointer”,但想法都是一样的。 最初是头部,for 循环导致它沿着列表steps
跳。
在 for 循环之后,您只需返回节点。
这是一个野生的。
首先,我们可以将你的middle
重写为:
def middle2(self):
temp = self.head
newList = ListNode('whatever lmao')
current = newList
for i in range(len(self)):
if i >= (len(self) // 2):
current.next = temp
current = current.next
temp = temp.next
return newList.next
这里没有任何重要的变化。 size
被“内联”到它使用的两个位置, math.floor
被截断/下除法替换,并且您在第一行中的元组样式初始化在此版本中被拆分为前两行。
如果您仔细观察,您可能会注意到temp
的操作方式与我在我的版本中使用current
的方式类似:
def middle2(self):
temp = self.head
#newList = ListNode('whatever lmao')
#current = newList
for i in range(len(self)):
#if i >= (len(self) // 2):
# current.next = temp
# current = current.next
temp = temp.next
#return newList.next
(希望语法突出显示使评论在这里消失,揭示我正在谈论的相似性)。
所以temp
“遍历”了列表。 那讲得通。 但是在这个版本中,整个列表都被遍历了:
for i in range(len(self)): # Your version
for _ in range(len(self) // 2): # My version
所以我们需要理解这一点。
此外,您拥有使用 lmao 创建的这个newList
变量,然后似乎并没有真正使用,除了返回它,并且不知何故仍然有效? 这很令人困惑——我们也需要理解这一点。
最后, if i >= (len(self) // 2):
块看起来有点像我的len(self) // 2
版本中的for
循环,但我的版本只迭代“第一个”范围的一半,而您的版本在范围的后半部分进入 if 分支(一旦我们位于/经过中间节点)。 所以让我们也明白这一点。
这是正在发生的事情。
首先,您正在创建temp
并将其初始化为 head(并将使用它来遍历列表,如我在我的版本中显示的那样)。
然后,您将创建两个变量并将它们指向同一事物: newList
和current
都指向这个新创建的 lmao ListNode。
然后,您将使用temp
遍历(整个)列表。
最终,在此遍历过程中,您将满足您的if
条件(实际上,它会在您处于中间节点时)。
此时,您首先:
current.next = temp
-- 到目前为止, current
没有改变,所以它仍然指向你的 LMAO 节点。 因此,此行将您的 LMAO 节点的.next
属性设置为temp
(记住,此迭代是中间节点)。current = current.next
- 在这里,您重新分配current
。 所以current
现在指向current.next
(只是temp
,你的助行器) 在随后的迭代中,您像往常一样推进temp
,并将进入您的 if 块,但重要的事情发生了变化。
current.next = temp
-- 因为在上一次迭代中您重新分配了current
,它不再指向您的 LMAO 节点。 事实上,在您从 LMAO 节点返回之前,您将永远不会再接触它。 如此有效,它的作用并不重要。 (事实上,它实际上什么也没做。它与temp
一起“行走” current
)。所以真的,唯一重要的迭代是第一次,所以这可以重写为:
def middle3(self):
temp = self.head
current = newList = ListNode('whatever lmao')
for i in range(len(self)):
if i == (len(self) // 2): # Changed >= to ==
current.next = temp
current = current.next
temp = temp.next
return newList.next
然后你可能会注意到我们真的不需要 if 语句中的第二行:
def middle3(self):
temp = self.head
current = newList = ListNode('whatever lmao')
for i in range(len(self)):
if i == (len(self) // 2):
current.next = temp
# (Removed line here)
temp = temp.next
return newList.next
然后你可能会注意到我们并没有真正使用current
或newList
作为节点,而只是真正使用了它的 .next,所以我们可以结合这两个名称并消除.next
(见最后注释):
def middle3(self):
temp = self.head
current = ListNode('whatever lmao')
for i in range(len(self)):
if i == (len(self) // 2):
current = temp
temp = temp.next
return current
我们现在不需要那个 LMAO 节点,我们可以使用None
(或者根本不初始化它):
def middle3(self):
temp = self.head
current = None # removed LMAO node
for i in range(len(self)):
if i == (len(self) // 2):
current = temp
temp = temp.next
return current
最后,让我们将 current 重命名为更准确的middle_node
:
def middle3(self):
temp = self.head
middle_node = None # now `middle_node`, was `current`
for i in range(len(self)):
if i == (len(self) // 2):
middle_node = temp
temp = temp.next
return middle_node
现在我们在一个最熟悉的地方。
temp
遍历列表,当我们到达len(self) // 2
(即中间节点)时,我们保留对它的引用。 我的版本在这里停止迭代并返回它,您的版本将中间节点保存为middle_node
但继续遍历列表(重要的是,不修改middle_node
)。
最后一点,重要的是要了解是什么导致了这种情况发生:(可变对象的)突变与(重新)分配。 这篇文章已经太长了,但是由于current
和newList
两个名称都指向同一个东西,变异一个,“变异另一个”(事实上,只有一个对象被变异了——只有一个对象存在,它只是有两种方式来谈论它)。
这不适用于分配。
突变:
a = b = [1,2]
a.append(3) # Mutation
print(len(a)) # 3
print(len(b)) # 3
任务:
a = b = 5
a += 2 # Reassignment that might be mistaken for mutation
# (ints are immutable)
print(a) # 7
print(b) # 5
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.