记录关于链表学习中的一些困惑

Q1:指针和节点的区别?

看以下反转链表的案例:

class ListNode:
   def __init__(self, val=0, next=None):
       self.val = val
       self.next = next
class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        prev=None
        curr=head
        while curr:
            new_node=curr.next
            curr.next=prev
            prev=curr
            curr=new_node
        return prev
像这段代码prev、curr、head、new_node它们分别是指针还是节点

 在这段代码中,prevcurr 、head、 new_node都是指针,它们指向 ListNode 类的实例,也就是链表中的节点。

  • head 是链表的头指针,它指向链表的第一个节点。
  • curr 是当前指针,它在遍历链表的过程中指向当前正在处理的节点。
  • prev 是前一个指针,它在反转链表的过程中用来保存当前节点的前一个节点的引用。
  • new_node 是指向curr下一个节点的指针

在编程中,尤其是在讨论链表这样的数据结构时,术语“指针”和“节点”有时会被交替使用,这可能会导致混淆。让我来澄清一下这两个概念:

  1. 节点(Node)

    • 在链表的上下文中,节点是一个对象,它通常包含两个部分:数据(例如 val)和指向下一个节点的引用(例如 next)。
    • 节点是链表中存储数据的基本单元,每个节点都通过其 next 属性链接到下一个节点。
  2. 指针(Pointer)

    • 指针是一个变量,它存储了另一个变量(在这种情况下是节点)的内存地址。
    • 在链表的上下文中,当我们说“指针”时,我们通常是指一个变量,它指向链表中的一个节点。这个变量本身并不包含数据,而是指向包含数据的节点。

Q2:Q1中传入的参数head: Optional[ListNode]不是表示ListNode节点类型吗?为什么说head是指针?

        在Python中,`Optional[ListNode]` 是一个类型注解,它来自 `typing` 模块,用于表示一个值可以是 `ListNode` 类型,也可以是 `None`。这种类型注解在Python中用来提供更清晰的代码意图和帮助IDE或静态类型检查器理解代码。

        在这个上下文中,`head` 被注解为 `Optional[ListNode]`,意味着 `head` 可以是一个 `ListNode` 对象,也可以是 `None`。`None` 在这里表示一个空链表,即没有节点的链表。

        当我们说 `head` 是一个指针时,我们是在描述它在链表数据结构中的作用,而不是它的具体类型。在链表的上下文中,每个节点的 `next` 属性实际上是指向下一个节点的指针。所以,尽管 `head` 在类型上是 `Optional[ListNode]`,它在功能上扮演的是指向链表第一个节点的指针角色。同样,`curr` 和 `prev` 也是指针,它们在代码中用来遍历和修改链表。

        在Python中,当我们谈论指针时,我们实际上是在谈论变量,这些变量存储了对象(在这种情况下是链表节点)的内存地址。当我们把一个节点赋值给一个指针变量时,我们实际上是在存储那个节点的内存地址,而不是节点的值。这意味着指针变量并不直接携带节点的值,而是指向存储这些值的内存位置。

Q3:head=ListNode(1),curr=head,那curr.val和head.val的区别是什么?为什么curr.val可以实现,是什么原理?

当执行 head = ListNode(1)curr = head 这样的操作时,您创建了一个 ListNode 对象,并且有两个变量 headcurr 都引用了这个对象。在Python中,对象的属性可以通过点(.)操作符来访问。因此,head.valcurr.val 都是有效的属性访问,它们访问的是同一个 ListNode 对象的 val 属性。

这里是详细解释:

  1. 对象创建head = ListNode(1) 创建了一个 ListNode 类的实例,这个实例有一个 val 属性,初始值为1。

  2. 变量引用curr = head 使得 curr 变量引用了与 head 相同的 ListNode 对象。在Python中,变量实际上是对对象的引用。

  3. 属性访问head.valcurr.val 都是通过变量来访问对象的 val 属性。因为 headcurr 引用的是同一个对象,所以无论您通过哪个变量访问 val 属性,都会得到相同的值。

  4. 原理:在Python中,对象的属性访问是基于对象的引用(即内存地址)进行的。当您通过 .head.val.curr.val 访问属性时,Python解释器会查找这些变量所引用的对象,并返回或设置该对象的 val 属性。

  5. 修改属性:如果您修改了 curr.valhead.val,这个修改会反映在同一个对象上,因为它们引用的是同一个对象。例如:

    # 创建链表的头节点
    head = ListNode(1)
    
    # 创建一个新的变量curr,它引用head指向的对象
    curr = head
    
    # 现在curr.val和head.val都引用同一个对象的val属性
    print(curr.val)  # 输出: 1
    print(head.val)   # 输出: 1
    
    # 修改curr.val,head.val也会随之改变
    curr.val = 2
    print(curr.val)  # 输出: 2
    print(head.val)   # 输出: 2

    在这个例子中,curr.valhead.val 的值都会在修改后变为2,因为它们引用的是同一个 ListNode 对象。这就是为什么 curr.valhead.val 可以实现并且它们的行为是一致的。

Q4: 链表中的对象引用是怎样的?

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


head = ListNode(0)
node1 = ListNode(1)
node2 = ListNode(2)
node3 = ListNode(3)

node4 = ListNode(4)

head.next = node1
node1.next = node2
node2.next = node3

curr1 = head
curr2 = head
curr3 = head
curr4 = curr3

# id() 函数返回一个对象的唯一标识符,如果 curr1 是指向链表中的一个节点的引用,那么 id(curr1) 将返回这个节点对象在内存中的唯一地址。
print(id(curr1)) #1988437524432
print(id(curr2)) #1988437524432
print(id(head))  #1988437524432
print(head == curr1) #True

# 当改变curr4.next = node4时,curr3.next和head.next此时也会指向node4
curr4.next = node4
print(curr3.next.val)  # 4
print(head.next.val)   # 4
print(curr3.val)  # 0
print(head.val)   # 0

# 当改变curr4.val = 5时,curr3.val==5和head.val==5也都成立
curr4.val = 5
print(curr3.val)  # 5
print(head.val)   # 5

首先代码创建了一个 ListNode 类的实例作为链表的头节点,并且有两个变量 curr1curr2 被赋值为 head。这意味着 curr1curr2 都引用了同一个 ListNode 对象。现在,让我们逐步分析代码和输出。

  1. head = ListNode(2):这行代码创建了一个新的 ListNode 对象,其 val 属性被初始化为2。head 是这个对象的引用。

  2. curr1 = headcurr2 = head:这两行代码使得 curr1curr2 都引用了 head 所指向的同一个 ListNode 对象。

  3. print(id(curr1)):这行代码打印出 curr1 变量所引用的对象的内存地址(在Python中,这个地址是一个唯一的标识符)。

  4. print(id(curr2)):这行代码打印出 curr2 变量所引用的对象的内存地址。由于 curr1curr2 引用的是同一个对象,所以它们的 id() 输出应该是相同的。

  5. print(id(head)):这行代码打印出 head 变量所引用的对象的内存地址。同样,由于 headcurr1curr2 引用的是同一个对象,它们的 id() 输出应该是相同的。

  6. print(head == curr1):这行代码比较 headcurr1 是否引用同一个对象。在Python中,如果两个变量引用同一个对象,== 运算符会返回 True

所以,当您运行这段代码时,您应该得到三个相同的 id() 输出,表示 curr1curr2head 引用的是同一个对象,以及一个 True,表示 headcurr1 是相等的(引用同一个对象)。

这是预期的输出:

1988437524432
1988437524432
1988437524432
True

 实际执行的截图见下图:

Q4中为什么当改变curr4.next = node4时,curr3.next和head.next此时也会指向node4?

        在Python中,变量是对象的引用。当你创建了一个引用并让它指向某个对象时,这个引用就持有那个对象在内存中的地址。当你将这个引用赋值给另一个变量时,你实际上是在让第二个变量也持有同一个对象的地址,因此两个变量现在都指向同一个对象。

        在你的代码中,curr1curr2curr3curr4最初都被赋值为head,这意味着它们都指向链表的头节点。它们是指向同一个对象的四个不同名称或引用。

        当你执行curr4.next = node4时,你正在改变curr4所指向的对象的next属性。由于curr4curr3head都指向同一个对象,你实际上是在改变这个对象的next属性,不论你是通过哪个引用去做的。因此,curr3.nexthead.next也会指向node4,因为它们和curr4一样,都指向了同一个对象,并对该对象的属性进行了修改。

        换句话说,你没有创建新的节点或新的链表,你只是修改了现有节点(在这种情况下是头节点)的next属性。由于所有引用都指向这个节点,因此任何通过这个节点进行的修改都会反映在所有引用上。

        这种行为是Python(以及许多其他面向对象编程语言)中引用和对象的基本工作原理。重要的是要理解变量(或引用)和它们所指向的对象之间的区别。变量可以更改以指向不同的对象,但对象的属性和状态可以通过任何指向该对象的引用来更改。

Q5:关于移除链表元素迭代法的一些疑惑

class Solution:
    def removeElements(self,head:Optional[ListNode],var:int)->Optional[ListNode]:
        dummy_node = ListNode(0)
        dummy_node.next=head
        curr = dummy_node

        while curr.next:
            if curr.next.val==val:
                curr.next=curr.next.next
            else:
                curr=curr.next
        return dummy_node.next

我不知道大家有没有跟我同样的疑问,为什么要返回 dummy_node.next而不是返回head或者curr.next,

首先dummy_node.next和head是指向同一个对象,起初curr和dummy_node指向同一个对象,curr.next是在修改对象(即链表中的节点)的属性,那么同时dummy_node.next也会改变,但会一旦执行了curr=curr.next后,curr和dummy_node就不再指向同一个对象了,那dummy_node就不再跟着curr改变对象的属性的同时,自己对象的属性也会跟着改变

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值