![](/img/trans.png)
[英]When we assign a LinkedList (list1) to another (l1), changes made to list1 are not reflected in l1. Why? - C#
[英]Why does merging two sorted linked Lists work - since when we do l4=l4.next, l4 points to a different object on heap, than l3. - C#
很抱歉这个问题有点复杂 - 但我找不到更好的方法来问这个问题。 非常感谢所有的帮助!
以下是我为合并两个排序链表而编写的方法 - ( MergeTwoLists(ListNode list1, ListNode list2)
)。 以下是整个程序:
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LinkedList
{
public class ListNode
{
public int val;
public ListNode next;
public ListNode(int val = 0, ListNode next = null)
{
this.val = val;
this.next = next;
}
}
internal class Program
{
static void Main(string[] args)
{
ListNode list1= new ListNode(1, new ListNode(2, new ListNode(4, null)));
ListNode list2 = new ListNode(1, new ListNode(3, new ListNode(4, null)));
ListNode list3 = MergeTwoLists(list1,list2);
}
public static ListNode MergeTwoLists(ListNode list1, ListNode list2)
{
if (list1 == null)
return list2;
else if (list2 == null)
return list1;
ListNode **l3 = new ListNode(0,null)**;
ListNode **l4 = l3**;
while (list1 != null && list2 != null)//list1 = 1->2->4->null
{ //list2 = 1->3->4->null
if (list1.val <= list2.val)
{
l4.next = list1;
list1 = list1.next;//after this statement, list1 now = 2->4->null.
}
else
{
l4.next = list2;
list2 = list2.next;
}
**l4 = l4.next**;//this should cause a problem in merging two lists, but doesn't.
}
l4.next=list1==null?list2:list1;
return l3.next;
}
}
}
现在,这是我的问题 -
上面的方法MergeTwoLists
中有三个粗体行(stackoverflow 不允许将代码片段加粗,因此加粗的代码片段被双星号包围)。
方法中的三个粗体行 - MergeTwoLists
- 是:
在上面的第 1 行中, l3
引用变量在堆栈上,并指向堆上的ListNode
object 0->null
。 在上面的第 2 行中, l4
引用变量在堆栈上,并被分配了l3
引用变量。 因此, l4
和l3
都指向堆上的同一个ListNode
object - 0->null
。 上面的第 3 行应该会导致合并两个列表时出现问题。 因为,当我们执行l4 = l4.next
- l4
时,分配了一个对堆上新 memory 位置的引用。 并且l3
和l4
之间的链接被破坏(意思是,它们现在引用堆上不同的 memory 位置 - 而不是引用堆上相同的 memory 位置)。 那么 l3 是如何完美地合并两个链表(list1 和 list2)的呢?
MergeTwoLists
中进入while
循环时会发生什么。MergeTwoLists
方法时, list1
引用变量在栈上,并指向堆上的如下ListNode
object - 1->2->4->null
。 2.同样,原来,当我们进入MergeTwoLists
方法时, list2
引用变量在栈上,指向堆上的如下ListNode
object - 1->3->4->null
。 现在,当我们第一次进入while
循环时(在MergeTwoLists
方法中), list1
指向1->2->4->null
并且list2
指向1->3->4->null
。 并且由于list1.val == list2.val
为true
,我们将 go 放入while
循环的if
语句中。
在if
语句中,第一行是l4.next = list1
。 执行l4.next=list1
后, l4
引用变量指向堆上的0->1->2->4->null
。 由于l3
引用变量=
l4
引用变量,堆栈上的l3
也指向堆上的相同位置,如l4
。 也就是说l3
也指向堆上的0->1->2->4->null
。
此后的下一条语句,在if
块中是: list1 = list1.next
,并且list1
引用变量现在指向堆上的以下ListNode
object - 2->4->null
。
所以l4
和l3
的状态,在if
块结束后,第一次是: l4
引用变量指向堆上的ListNode
object 0->1->2->4->null
。 并且, l3
引用变量也指向堆上相同的ListNode
object 0->1->2->4->null
。
接下来,我们跳过 else 块(因为我们已经进入了if
块)。 并执行 else 块正下方的语句 - 即: l4 = l4.next 。
上面的语句 - l4 = l4.next - 正是让我感到困惑的地方。 在上面的语句中 - l4 = l4.next
- 引用变量l4
被分配了对堆上新 object 的引用(即,它被分配l4.next
)。
由于现在l4
被分配了对新 object 的引用,因此 l3 和 l4 之间的链接断开了。 也就是说, l4
现在指向堆上的新ListNode
object - 1->2->4->null
。 但是, l3
指向原始ListNode
object,它在堆上 - 0->1->2->4->null
。
当我们第二次进入while循环时: list1
指向堆上的ListNode
object - 2->4->null
。 并且, list2
指向堆上的ListNode
object - 1->3->4->null
。
当我们第二次进入while循环时, list1!=null
= true
&&
list2!=null
= true
,所以我们再次进入while循环。 这一次,list1.val = 2 和 list2.val = 1 - 因此,我们不会将 go 放入if
块,而是将 go 放入else
块。
else
块中的第一条语句是l4.next = list2
。 该语句将导致以下结果: l4
引用变量现在将指向堆上的以下ListNode
object - 1->1->3->4->null
。
else
块中的下一行是: list2 = list2.next
。 当我们执行list2 = list2.next
时, list2
引用变量将指向堆上的以下ListNode
object - 3->4->null
。
同样,就在 else 块之后,我们遇到了语句 - l4 = l4.next
。 l4
引用变量现在将指向堆上的以下ListNode
object - 1->3->4->null
。
我的问题是: l3
如何进一步受到l4
更改的影响? - 因为一旦我们为l4
分配了一个新的 object 引用, l3
和l4
之间的链接就断开了(也就是说,一旦我们做了l4 = l4.next
,第一次在while
循环结束时)?
注意:通过链接断开,我的意思是l3
和l4
现在不指向堆上的同一个 object。 但是现在指向堆上的不同对象。
但是,当我在 Visual Studio 中运行上述代码时, l3
遵循l4
- 即l3
完美地创建了一个合并的排序链表。
实际正确结果:程序在visual studio中运行时,返回l3
引用变量,该变量指向堆上的如下ListNode
object - 0->1->1->2->3->4->4->null
. 预期结果,按照我的逻辑:当我们返回l3
时,它应该返回0->1->2->4->null
。
重申我的逻辑:下面的代码片段搞砸了两个链表按排序顺序的合并 - l4 = l4.next
。 当我们第一次(以及以后)进入while
循环时,我们第一次遇到这个代码片段。 在我们第一次进入while
循环之前,一切都很好 - l3
指向堆上的0->null
。 由于l4 = l3
, l4
也指向堆上的相同位置(即0->null
)。 当我们第一次在while
循环中使用 go 时,一切都很好 - 我们在while
循环中进入if
块,然后我们点击l4.next = list1
。 这使得l4
指向0->1->2->4->null
(在堆上)。 而且由于l4 = l3
- l3
指向与l4
- 0->1->2->4->null
相同的位置(在堆上)。 但是,一旦我们第一次到达代码行l4 = l4.next
- l4
现在指向堆上的新 object ( 1->2->4->null
)。 并且l3
指向堆上的原始 object ( 0->1->2->4->null
)。 从这里开始,对l4
所做的任何更改都不会反映在l3
中(因为l3
指向堆上的不同 memory 位置 - 并且l4
一直指向堆上不同的 memory 位置,从这里开始)。
那么,最后l3
怎么会以完全排序的顺序显示合并列表呢? - l3
在方法结束时只能是0->1->2->4->null
。 那么l3
如何给出正确的排序结果呢? 任何帮助将不胜感激!
首先,正如详尽指出的那样, 堆栈是一个实现细节,它与大多数关于引用和值类型的讨论完全无关。
它实际上都是关于引用语义的,你可以通过使用class
来获得它,这与struct
的值语义相反。 换句话说:引用类型总是通过引用传递,这样的 object 变量指向object 而不是object。
回到你的问题:
本质上, l3
所指的根/头 object 始终指向列表的头。 l4
代表当前节点。 更好的变量名称会使这更容易理解。
l3
始终指向头部object ,这个 object 仍然可以使用它的next
变量访问其他链接对象,这也是一个指针(因为引用语义)。
l4
首先分配给这个节点。 当您修改l4.next
时,您也在修改与 l3.next 相同的l3.next
。 然后当你分配l4 = l4.next;
您还可以通过l3.next.next
访问该节点。
没有断开的链接。 语句“即 l3 也指向堆上的 0->1->2->4->null。” 应该是“即 l3指向的 object也指向 0->1->2->4->null。” 并且 object 仍然存在,并通过 l4 进行了修改。
是对象本身进行指向,因此您有双重间接: l3
变量指向一个节点,该节点本身指向l4
指向另一个节点,依此类推。
所以当它再次循环回来时, l4
现在指向链表中的第二个节点。 l4
被修改,使其然后根据合并标准指向下一个节点。 然后我们再次赋值l4 = l4.next;
这样我们就可以将第四个节点附加到第三个节点上。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.