指向链表的指针什么时候会改变实际的链表?

mcc*_*mcc 9 python pointers reference linked-list

我有一个单链表 L,并创建一个指向该列表 P 的指针。似乎有时修改 P 会更改实际列表,而其他时候修改 P 对实际列表 L 没有任何作用,只会更改 P 所指向的内容到。

假设我创建一个指向 L 的指针,P = L(在 python 中)。执行类似 P = P.next 的操作会使 L 保持不变,但 P.next = P.next.next 会更改 L。同样,通过修改 P.data 来更改存储在列表中的实际数据实际上会更改 L.data。

为什么会出现这种情况?我觉得我错过了一些关于指针/引用的基本知识。

class Node:
    def __init__(self, val):
        self.val = val
        self.next = None

    def addNode(self, val):
        root = self
        while root.next is not None:
            root = root.next
        root.next = Node(val)

    def iterateLL(self):
        root = self
        print
        while root is not None:
            print(str(root.val) + " ", end="")
            root = root.next
        print()

if __name__ =="__main__":
    L = Node(1)
    L.addNode(2)
    L.addNode(3)
    L.addNode(4)

    # iterate through list and print:
    L.iterateLL()

    # changing value of pointer does not affect L
    P = L
    P = P.next
    L.iterateLL() # L is unchanged

    # changing "next" value of pointer does affect L
    P = L 
    P.next = P.next.next
    L.iterateLL() # now we've skipped node 2

    # changing data of pointer does affect L
    P = L
    P.val = 10
    L.iterateLL()
Run Code Online (Sandbox Code Playgroud)

上面的代码执行时输出如下(第一行显示原始链表,第二行显示指针 P 更改后链表未更改,而第三行和第四行显示链表已更改)

1 2 3 4

1 2 3 4

1 3 4

10 3 4

这里发生了什么?为什么改变 P 不会影响 L,但改变 P.next 和 P.val 会影响?如果所有这些操作的行为方式相同,则不会更改指针,或者总是更改链表(因此 P = P.next 应该通过删除第一个节点来修改 L),或者从不更改链表(因此 P.next = P.next.next 应该保持 L 不变)?

我有一种感觉,这与 L.next 是一个指针有关,就像 P.next 一样。因此修改 P.next 最终会修改 L.next 指向的内容(?)。但我觉得规则对我来说不太清楚。

Jam*_*ugh 17

在 Python 中的大多数情况下,当您对变量执行赋值时,P在这种情况下,变量的值会发生P变化,但它最初引用的对象不会发生变化。这是因为 Python 变量只是对象的引用/指针。这是一个例子:

var1 = "test1"
var2 = "test2"
var3 = var1 # var3 = "test1"
var1 = var2 # var1 = "test2"

print(var1) # "test2"
print(var2) # "test2"
print(var3) # "test1"
Run Code Online (Sandbox Code Playgroud)

那么这里发生了什么?我们只是改变这些变量指向的内容,而不是改变底层对象。

现在,在您的情况下,您执行以下操作:

# changing value of pointer does not affect L
P = L
P = P.next
L.iterateLL() # L is unchanged
Run Code Online (Sandbox Code Playgroud)

当您执行P = L和时P = P.next,您只是更改变量P所指向的内容。您没有对P指向的基础对象进行更改。让我们想象一下。

原始配置: 原始配置

P = L
Run Code Online (Sandbox Code Playgroud)

P = L

P = L.next
Run Code Online (Sandbox Code Playgroud)

P = L.下一个

然而,当你这样做时

P = L
P.next = P.next.next
L.iterateLL() # we've now skipped node two
Run Code Online (Sandbox Code Playgroud)

P您正在更改所指向的对象的属性。您正在将属性设置P.next为指向P.next.nextP.next您实际上并没有对最初指向的底层对象进行更改。通过这样做,最初指向的对象P.next就会超出范围并被垃圾收集器清理。

P.next = P.next.next
Run Code Online (Sandbox Code Playgroud)

P.下一个 = P.下一个.下一个

根据您的代码判断,我假设您在这种情况下的预期行为是L从 LinkedList 中删除并最终得到一个像“2 3 4”这样的列表。要实现这一点,应该足够了L = L.next。这将导致第一个节点超出范围,垃圾收集器应该清理它。

作为一个快速警告,我提到在大多数情况下,赋值不会更改变量指向的对象。然而,属性有点不同。它们重写了__set__魔术方法,该方法允许您使用赋值运算符编辑底层对象。这里的情况并非如此。