繁体   English   中英

链表指针令人困惑

[英]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(并将使用它来遍历列表,如我在我的版本中显示的那样)。

然后,您将创建两个变量并将它们指向同一事物: newListcurrent都指向这个新创建的 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

然后你可能会注意到我们并没有真正使用currentnewList作为节点,而只是真正使用了它的 .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 )。


最后一点,重要的是要了解是什么导致了这种情况发生:(可变对象的)突变与(重新)分配。 这篇文章已经太长了,但是由于currentnewList两个名称都指向同一个东西,变异一个,“变异另一个”(事实上,只有一个对象被变异了——只有一个对象存在,它只是有两种方式来谈论它)。

不适用于分配。

突变:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM