反转链表递归

reversing a Linked List Recursion

本文关键字:递归 链表      更新时间:2023-10-16
struct Node
      {
         int data;
         struct Node *next;
      }

我应该反转链表。与黑客兰克一样,此代码工作正常。

代码 A

void rev(Node* head){
    if(head==NULL) return;
    rev(head->next);
    cout<<head->data<<endl;
}

输入: 2 1 4 5输出: 5 4 1 2

但是如果我进行以下小更改,它就会中断,即我得到分段错误

代码 B

void rev(Node* head){
    if(head==NULL) return;
    head=head->next;
    rev(head);
    cout<<head->data<<endl;
}

输入: 2 1 4 5输出:分段错误

但是,如果我进行以下更改,它会以某种方式工作,但不显示第一个数字。

代码 C

void rev(Node* head){
        if(head->next==NULL) return;
        head=head->next;
        rev(head);
        cout<<head->data<<endl;
    }

输入: 2 1 4 5输出: 5 4 1

问题:代码 A 和代码 B 大多相似。什么是破坏代码 B?

在你的代码中在案例 B 中:

void rev(Node* head){
if(head==NULL) return;
head=head->next;
rev(head);
cout<<head->data<<endl;}

让我们看一下递归的堆栈段。

让我们NODE1 -> NODE2->NULL 其中NODE1 and NODE2是结构对象。

你的函数做什么是:

  1. 致电rev(NODE1)
  2. 检查它是否为空
  3. 指向next NODE i.e. NODE2

  4. 致电rev(NODE2)

  5. 检查它是否为空
  6. 指向next NODE i.e. NULL

  7. 致电rev(NULL)

  8. 检查它是否为空
  9. 指针将返回 head = NULL

和这是错误,即您正在访问 NULL 值的head->data

希望这会对您有所帮助。

好吧,当然,在情况 B 中,您无法访问内存。指针指向未分配的地址 (NULL)。顺便说一下,我建议制作一个双链表。更方便。

C 的情况下,你给函数void rev (Node* head)一个指向列表的指针,并跳过第二行head=head->next;这是你的第一个元素。因此,它不会显示在 std::cout 的输出中。

用例 A,你没事。

链表迭代器只能继续到链表的末尾。

所有链表迭代器代码都必须包含一个循环不变量 - 测试确保如果它接下来要取消引用,下一个是有效的。 如果不是,它将停止。 这通常采用以下形式:

if(head==NULL) return;

while(head!=NULL)

if(head->next != NULL) recurse(head->next)

如果头部NULL,那么做head->next是不安全的(或head->datahead->anything

更改的效果是绕过循环不变量,并查看head->next内部而不检查它是否NULL

具体来说,您先访问head->data,然后再检查head是否为空,然后 BOOM!

看起来你在/'我应该剪蓝线还是黄线'/领域。 玩得愉快。


虽然您没有使用循环,但递归也是一种迭代形式。

让我为您重写代码 A

void rev(Node* head){
    if(head!=NULL)
    {
        rev(head->next); //recursive call with pointer to next!
        cout<<head->data<<endl; //write after recursive call (reversed)
    }
}

代码 B 可以用相同的方式重写:

void rev(Node* head){
    if(head!=NULL)
    {
        head=head->next; 
        //head becomes next, but what if next is null, 
        //you try to read next, which is a null pointer here == segmentation fault
        rev(head);
        cout<<head->data<<endl;
    }
}

所以基本上代码 A 不读取空指针,但在代码 B 中,您将空指针分配给head因此您必须读取空指针才能执行此分配。

代码 C 中,行 if (head->next==NULL) return; 阻止您在下一个元素为 null 的情况下使用该行下面的代码,因此不会打印当前标头。

您的链表结构为 2->1->4->5->NULL

代码 B:

在递归时,当 head 有 data=5,head=head->next 使 head=NULL ,所以下一次调用 rev() 立即返回 head=NULL,现在你尝试访问与 NULL->data 相同的 head->data 结果是分割错误。

不是对你的问题的直接回答,但无论你有一个单向还是双向链表,如果你想反转该列表,那么最好同时维护一个 Head 指针和一个 Tail 指针:

要反转单向链表,请调用Reverse()

void Swap(Node* pCurr,Node* pNext)
{
    if (pNext != NULL)
    {
        Swap(pNext,pNext->pNext);
        pNext->pNext = pCurr;
    }
}
void Reverse()
{
    if (pHead != NULL)
    {
        Swap(pHead,pHead->pNext);
        pHead->pNext = NULL;
    }
    Node* pTemp = pHead;
    pHead = pTail;
    pTail = pTemp;
}

要反转双向链表,请调用Reverse()

void Swap(Node* pCurr)
{
    if (pCurr->pNext != NULL)
    {
        Swap(pCurr->pNext);
        pCurr->pNext->pNext = pCurr;
    }
    pCurr->pPrev = pCurr->pNext;
}
void Reverse()
{
    if (pHead != NULL)
    {
        Swap(pHead);
        pHead->pNext = NULL;
    }
    Node* pTemp = pHead;
    pHead = pTail;
    pTail = pTemp;
}

代码 A 和代码 B 并不相似。在代码 A 中,将 head->next 作为 rev 的参数传递而不更改指针head,但在代码 B 中,将指针head更改为 。因此,它们在调用cout<<head->data时会产生不同的结果

您的链表样本可以可视化为:2->1->4->5->NULL
您的评估可能被评估为(相同范围的相同缩进):
在代码 A 中:

head: 2,    call rev(head) //your initial call  
    head: 2,    check head==NULL false
    head: 2,    call rev(head->next)
        head: 1,    check head==NULL false
        head: 1,    call rev(head->next)
            head: 4,    check head==NULL false
            head: 4,    call rev(head->next)
                head: 5,    check head==NULL false
                head: 5,    call rev(head->next) 
                    head==NULL, check head==NULL true
                        return
                head: 5,    call cout<<head->data<<endl;  
            head: 4,    call cout<<head->data<<endl;
        head: 1,    call cout<<head->data<<endl;
    head: 2,    call cout<<head->data<<endl;
    return

但在代码 B 中:

head: 2,    call rev(head) //your initial call  
    head: 2,    check head==NULL false
    head: 2,    head = head->next
    head: 1,    call rev(head)
        head: 1,    check head==NULL false
        head: 1,    head = head->next
        head: 4,    call rev(head)
            head: 4,    check head==NULL false
            head: 5,    head = head->next
            head: 5,    call rev(head)
                head: 5,    check head==NULL false
                head: 5,    head = head->next
                head==NULL, call rev(head) 
                    head==NULL, check head==NULL true
                        return
                head: NULL, call cout<<head->data<<endl;  //segmentation fault here

否则,在代码 C 中:

head: 2,    call rev(head) //your initial call  
    head: 2,    check head->next==NULL false
    head: 2,    head = head->next
    head: 1,    call rev(head)
        head: 1,    check head->next==NULL false
        head: 1,    head = head->next
        head: 4,    call rev(head)
            head: 4,    check head->next==NULL false
            head: 5,    head = head->next
            head: 5,    call rev(head)
                head: 5,    check head->next==NULL true
                    return
            head: 5,    call cout<<head->data<<endl;
        head: 4,    call cout<<head->data<<endl;
    head: 1,    call cout<<head->data<<endl;
    return

看到有三个调用cout,所以只打印了三个元素。