了解 python 中的指针和合并列表

Understanding pointers and merging lists in python

所以我正在为 work/are 在 Python 中如何管理指针而苦恼。具体来说,我正在研究一个练习题,它接受两个排序的整数 linked 列表(从最小值到最大值),以及 returns 合并的、排序的 linked 列表。脑袋撞墙一个小时也想不通,于是在网上找了个解决方法,具体是这样的:

def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        cur = dummy = ListNode()
        while list1 and list2:               
            if list1.val < list2.val:
                cur.next = list1
                list1, cur = list1.next, list1
            else:
                cur.next = list2
                list2, cur = list2.next, list2
                
        if list1 or list2:
            cur.next = list1 if list1 else list2
            
        return dummy.next

我理解主要思想:相互检查当前节点的值,将较小的节点放入列表并将该输入列表向前移动一个节点,然后重复。但是,我正在努力解决 curdummy,以及它们在这个解决方案中的工作方式:

  1. 为什么dummy.nextlink变成cur?整个函数似乎只涉及 cur,并且没有明确地将 dummy 的指针移动到 cur 的头部,就像在 C 语言中必须做的那样。我假设跟它们同时初始化有关系,但是不太明白怎么弄的?

  2. 为什么我们要连续更新两次cur?首先我们将 cur.next 设置为列表中的一个,然后在下一行中将 cur 本身设置为相同的列表?我不明白为什么这是必要的,甚至不明白它在 linked 列表的构造中做了什么。

每个Python变量都是对一个值的引用;如果您已经了解指针在 C 中的工作方式,那么将引用可变对象的变量视为像 C 指针一样工作可能会很有用。它们实际上不是一回事,但是引用 ListNode 的 Python 变量比 C ListNode 更像 C ListNode*,所以指针隐喻如果这是您的母语,那么这是一个很好的概念起点。

  1. dummy 始终指向您在函数第一行中创建的节点,该节点与 cur 起始的节点相同。第一次更新 cur.next 时,您也在更新 dummy.nextcur 随后被重新分配给一个新节点,但您对 dummy 所做的修改仍然存在。

这个Python代码:

        cur = dummy = ListNode()
        while list1 and list2:               
            if list1.val < list2.val:
                cur.next = list1
                list1, cur = list1.next, list1

本质上等同于:

        ListNode* cur, dummy;
        cur = dummy = new ListNode();
        while (list1 && list2) {               
            if (list1->val < list2->val) {
                cur->next = list1;
                cur = list1;
                list1 = list1->next;
            }
        }
  1. 一个是修改cur指向的节点的next属性;另一个正在修改 cur 本身以指向一个新节点。
                cur.next = list2
                cur = cur.next

等同于:

                cur->next = list2;
                cur = cur->next;

有关变量在 Python 中如何工作的更多 in-depth 解释,请阅读 https://nedbatchelder.com/text/names.html

Why does dummy.next link to cur?

没有

最初,会创建一个额外的节点,以便源列表中的节点可以在它之后 linked。 dummycur 都是 该节点的 名称,在函数的开头。

随着函数的进行,cur成为不同节点的名称,但dummy仍然是最初创建的节点的名称。

函数returnsdummy.next,因为函数是在那个节点之后依次link个节点起作用的,所以dummy.next指的是(不是“指向”)第一个 linked 节点。

and there's no explicit moving of the pointer of dummy to the head of cur like one would have to do in a language like C.

Python 中没有指针。更进一步,cur是一个节点;它没有“头”。在此实现中,没有明确表示整个列表的对象 - 只有表示节点的对象,因此列表隐含地是某个节点以及可从中访问的所有其他节点。

dummy 开始是新创建节点的名称。无需重新分配 dummy 成为“列表头部”的名称,因为它是该节点的名称, 从未停止成为该节点的名称节点,并且该节点 被用作 列表的前身。 (由于该节点不是 实际上 预期结果的一部分,我们必须在返回时跳过它。)

Why do we update cur twice in consecutive lines?

首先,重要的是要了解 我们不会 这样做。详细:

First we set cur.next equal to one of the lists, but then we set cur itself equal to that same list in the very next line?

更改 cur.next 的值与设置 cur 本身不是一回事。例如,列表元素也会发生同样的事情:

a = [1, 2, 3]
b = a
# changing an element of a WILL change what we see in b:
a[0] = 4
print(b)
# REPLACING `a` will NOT change what we see in b:
a = 'hi mom' # we don't even have to replace it with another list
print(b)

写入 b = a 不会 复制列表。这意味着 b a 命名的同一事物 的另一个名称。如果我们更改 那个东西 ,那么显然我们会在检查 b 时看到更改。如果我们a成为其他东西的名称,那么这将不会影响b,因为b 仍然是以前的名称。

它与现实世界中人们的真实姓名 - 或其他形式的参考 - 相同。鲍勃是爱丽丝的小儿子。当 Bob 长大后,Alice 观察到“我最小的儿子”长大了,因为那指的是同一个人。如果 Alice 生了另一个男孩,并拥抱了“我最小的儿子”,Bob 感觉不到拥抱——因为“我最小的儿子”不再是同一个人。


I don't understand why that is necessary

现在我们可以考虑代码的用途了。

cur 是我们用于“我们正在构建的列表的当前最后一个节点”的名称。

由于我们将节点添加到列表中以构建它,因此随着时间的推移,这必须引用不同的节点。

通过执行 cur.next = ...,我们 link 在名为 cur 的节点之后的另一个节点。这有助于我们构建列表,但现在 cur 不再是最后一个节点的名称 - 因为它仍然是之前命名的节点的名称,并且该节点不再是最后一个节点,因为我们添加了一个节点。

所以,添加完节点后,我们re-assign cur给刚刚添加的节点命名。