繁体   English   中英

为什么合并两个已排序的链表有效 - 因为当我们执行 l4=l4.next 时,l4 指向堆上的不同 object,而不是 l3。 - 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 = new ListNode(0,null)
  2. l4 = l3
  3. l4 = l4.next

在上面的第 1 行中, l3引用变量在堆栈上,并指向堆上的ListNode object 0->null 在上面的第 2 行中, l4引用变量在堆栈上,并被分配了l3引用变量。 因此, l4l3都指向堆上的同一个ListNode object - 0->null 上面的第 3 行应该会导致合并两个列表时出现问题。 因为,当我们执行l4 = l4.next - l4时,分配了一个对堆上新 memory 位置的引用。 并且l3l4之间的链接被破坏(意思是,它们现在引用堆上不同的 memory 位置 - 而不是引用堆上相同的 memory 位置)。 那么 l3 是如何完美地合并两个链表(list1 和 list2)的呢?

  • 那么,让我们看看,当我们最初在方法MergeTwoLists中进入while循环时会发生什么。
  1. 原来,当我们进入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.valtrue ,我们将 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

所以l4l3的状态,在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

  • 现在,让我们看看会发生什么,当我们 go 进入 while 循环时,第二次 -

当我们第二次进入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 引用, l3l4之间的链接就断开了(也就是说,一旦我们做了l4 = l4.next ,第一次在while循环结束时)?

注意:通过链接断开,我的意思是l3l4现在不指向堆上的同一个 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 = l3l4也指向堆上的相同位置(即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.

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