C++ 下面递归程序的最后一行是如何工作的?

C++ 下面递归程序的最后一行是如何工作的?,c++,recursion,linked-list,reverse,singly-linked-list,C++,Recursion,Linked List,Reverse,Singly Linked List,我在研究链表,然后我发现了一个递归反转链表的代码。这是C++代码。< /P> void recursiveReverse(node*& head) { node* first; node* rest; /* checking for an empty list */ if (head == NULL) return; first = head; rest = first->next; /* List has only one node *

我在研究链表,然后我发现了一个递归反转链表的代码。这是C++代码。< /P>
void recursiveReverse(node*& head)
{
  node* first;
  node* rest;

/* checking for an empty list */
  if (head == NULL)
   return;   

  first = head;  
  rest  = first->next;

/* List has only one node */
  if (rest == NULL)
   return;   

  recursiveReverse(rest);
  first->next->next  = first;  
  first->next  = NULL;          

/* fix the head pointer */
  head = rest;              
}
除了最后一行,我都懂了。因为据我所知,rest指针在递归展开期间也会像第一个指针一样更新,因此,在代码末尾,头不会指向最后一个节点

下面是我对以下代码的解释

让我们看一个链表1->2->3

最初,first将存储head节点和rest的地址 将包含节点2的地址。 现在,由于rest不是NULL,因此将调用RecursiveEversest。 现在,第一个将指向节点2,其余的将指向节点3。 同样,rest不是空的,因此将调用RecursiveEverserest。 现在,第一个将指向节点3,其余将包含NULL。 之后,递归将开始展开,首先返回节点2,其余将返回节点3。
现在,语句first->next->next=first;将导致节点3的下一部分指向节点2,链表将变为1->2 next->next=first;将导致节点2的下一部分指向节点1,链表将变为1可能还有其他误解,但我假设基本的误解与您的解释有关语句head=rest将导致head指向节点2而不是节点3,因为第一个->下一个的其余部分当前位于节点2

最后,head将指向初始列表的最后一个节点。让我们考虑一下代码的简化/缩短部分:

rest  = head->next;

if (rest == NULL)  // end of list reached; head points tho the last node 
   return;

recursiveReverse(rest);  // if the end is not reached, go forward with the next node (i.e. the value of head->next

head = rest;    // reset head to the (shared) value of rest. 
这是因为递归Everserest语句将被一次又一次地调用,直到head->next为NULL,即到达列表的末尾。RecursiveVerse的最后一次运行返回,因为head->next==NULL,在调用者中,变量rest指向最后一个节点。 现在请注意,rest在所有对RecursiveVerse的调用中共享,因为它是通过引用传递的。因此,到目前为止调用的每个RecursiveVerse实例都将调用语句head=rest,但是-由于rest在所有调用中共享,并且在递归调用后不会更改-语句head=rest将始终将head分配给相同的rest值,该值仍然指向最后一个节点

希望这是全面的


无论如何,使用引用传递的参数执行递归函数通常很难理解;通常,当递归函数管理其私有状态,但使用返回值来协调结果时,事情会变得更容易。如果对代码进行排列,使node*reverseRecursivenode*处于当前状态,则代码将更易于理解。

如步骤8所示。您忘记了head in void RecursiveVersenode*&head是一个引用。所以当你递归地调用递归珠穆朗玛峰时;然后通过引用传递rest。这意味着,在递归内部,head是调用函数中对rest变量的引用。因此,当它在递归中被更改为指向3时,调用函数中的rest也被更改

如果这听起来令人困惑,那么在堆栈上绘制局部变量可能会有所帮助:

1最初,head将是指向传递给函数的节点的指针的引用,首先将存储head节点的地址,其余将包含节点2的地址

堆栈看起来像这样,只显示局部变量,忽略调用函数时已经存在的任何变量:

====================================
head:     reference to original head
first:    node 1
rest:     node 2
现在,由于rest不是空的,所以将调用RecursiveEversest

3现在,head将在调用函数中引用rest,首先将指向节点2,rest将指向节点3。 堆栈将如下所示:

====================================
head:     reference to original head
first:    node 1
rest:     node 2      <---------------+
====================================  |
head:     reference to ---------------+
first:    node 2
rest:     node 3 
====================================
head:     reference to original head
first:    node 1
rest:     node 2      <---------------+
====================================  |
head:     reference to ---------------+
first:    node 2
rest:     node 3      <---------------+
====================================  |
head:     reference to ---------------+
first:    node 3
rest:     NULL 
6由于rest为空,我们只返回,堆栈返回到:

====================================
head:     reference to original head
first:    node 1
rest:     node 2      <---------------+
====================================  |
head:     reference to ---------------+
first:    node 2
rest:     node 3 

8之后,返回并首先再次指向节点1,语句first->next->next=first;将导致节点2的下一部分指向节点1,链接列表将变为1我们初学者应该互相帮助:

在我看来,对于像我们这样的初学者来说,要理解函数是如何工作的并不容易

因此,最好使用方案来计算它的工作

如果列表不包含节点或仅包含一个节点,则没有可反转的内容

此代码段对应于此结论

/* checking for an empty list */
  if (head == NULL)
   return;   

  first = head;  
  rest  = first->next;

/* List has only one node */
  if (rest == NULL)
   return; 
现在让我们假设列表正好包含两个节点。在这种情况下,它看起来像

--------    ----------    ----------------   
| head | -> |   A  |B| -> |   B  |nullptr|
--------    ----------    ----------------
此列表按以下方式分为以下部分

--------    ----------
| head | -> |   A  |B|
--------    ----------


--------    ----------
| first| -> |   A  |B|
--------    ----------

--------    ----------------   
| rest | -> |   B  |nullptr|
--------    ----------------
此代码段对应于此结论

first = head;  
rest  = first->next;
head = rest;
现在函数递归地调用自己

recursiveReverse(rest);
事实上,这个新的列表需要它

--------    ----------------   
| rest | -> |   B  |nullptr|
--------    ----------------
由于这个新列表只包含一个节点,因此函数只返回

由于原始列表必须反转,因此头部必须包含rest值,即头部必须poi nt到节点B

此代码段对应于此结论

first = head;  
rest  = first->next;
head = rest;
然而,在这种情况下,我们将得到

--------    ----------------   
| head | -> |   B  |nullptr|
--------    ----------------
--------     ----------------  | -----------   
| first | -> |   A  |nullptr|  | |   B  | A|-----------
--------     ----------------  | -----------          |
                                     ^                V
--------                             |             -----------------   
| rest | ----------------------------              |   A  | nullptr|
--------                                           -----------------
--------     ----------------  | -----------   
| first | -> |   A  |nullptr|  | |   B  | A|-----------
--------     ----------------  | -----------          |
                                     ^                V
--------                             |             -----------------   
| head | ----------------------------              |   A  | nullptr|
--------                                           -----------------
但是这个列表只包含一个节点B

head = rest;
first->next  = NULL;
head = rest;
我们需要在列表中添加节点A

正如我们所看到的,指针首先指向节点A

然后我们可以做下面的事情

first->next->next  = first; 
这导致

--------     ----------    -----------   
| first | -> |   A  |B| -> |   B  | A|
--------     ----------    -----------
这是另一方面,我们有

--------    ----------    -----------   
| rest | -> |   B  |A| -> |   A  | B|
--------    ----------    -----------
然后在这个声明之后

head = rest;
first->next  = NULL;
head = rest;
我们会得到

--------    ----------------   
| head | -> |   B  |nullptr|
--------    ----------------
--------     ----------------  | -----------   
| first | -> |   A  |nullptr|  | |   B  | A|-----------
--------     ----------------  | -----------          |
                                     ^                V
--------                             |             -----------------   
| rest | ----------------------------              |   A  | nullptr|
--------                                           -----------------
--------     ----------------  | -----------   
| first | -> |   A  |nullptr|  | |   B  | A|-----------
--------     ----------------  | -----------          |
                                     ^                V
--------                             |             -----------------   
| head | ----------------------------              |   A  | nullptr|
--------                                           -----------------
现在确实是时候发表声明了

head = rest;
first->next  = NULL;
head = rest;
我们会得到

--------    ----------------   
| head | -> |   B  |nullptr|
--------    ----------------
--------     ----------------  | -----------   
| first | -> |   A  |nullptr|  | |   B  | A|-----------
--------     ----------------  | -----------          |
                                     ^                V
--------                             |             -----------------   
| rest | ----------------------------              |   A  | nullptr|
--------                                           -----------------
--------     ----------------  | -----------   
| first | -> |   A  |nullptr|  | |   B  | A|-----------
--------     ----------------  | -----------          |
                                     ^                V
--------                             |             -----------------   
| head | ----------------------------              |   A  | nullptr|
--------                                           -----------------
也就是说,列表是相反的

如果列表包含两个以上的节点,则在拆分原始列表后,指针将首先指向原始列表的第一个节点,该节点必须是反向列表中的最后一个节点。反过来,第一个节点将指向反向列表中的下一个节点,该节点将是最后一个节点

使用此代码段

first->next->next  = first;  
first->next  = NULL;  
我们可以将其放置在反转列表中的最后一个节点之后。我们所需要做的就是将head设置为指针rest中存储的值,因为指针rest是反向列表的头,指针指向的节点首先被附加到反向列表中

head = rest;
仅此而已

这是一个演示程序

#include <iostream>

struct node
{
    int value;
    node *next;
};

void push( node * &head, int value )
{
    head = new node { value, head };
}

std::ostream & out( node * const &head, std::ostream &os = std::cout )
{
    for ( node *current = head; current != nullptr; current = current->next )
    {
        os << current->value << ' ';
    }

    return os;
}

void recursiveReverse( node * &head )
{
    if ( head != nullptr && head->next != nullptr )
    {
        node *first = head;
        node *rest  = head->next;


        recursiveReverse( rest );


        first->next->next  = first;  
        first->next  = nullptr;          

        head = rest;
    }       
}

int main() 
{
    node *head = nullptr;

    const int N = 10;

    for ( int value = 0; value < N; value++ )
    {
        push( head, value );
    }

    out( head ) << std::endl;

    recursiveReverse( head );

    out( head ) << std::endl;

    return 0;
}

你可以用调试器一行一行地调试,找出真正发生的事情。@OliverCharlesworth,我一行一行地调试,我知道rest没有改变,但它是固定的。但我无法找出它发生的原因,这就是为什么,我在这里提出了这个问题。试着在函数调用之前在纸上写下列表的状态,每次列表的状态都会改变,同时逐行进行。