我尝试使用递归方法来反转链表,但出现了分段错误。发生了什么问题?

huangapple go评论79阅读模式
英文:

I tried reversing a linked list using an recursion approach but got an segmentation error? What went wrong?

问题

以下是代码的翻译部分:

#include <iostream>

class node {
public:
    int data;
    node* next;

    node(int val) : data(val) {
        this->next = nullptr;
    }
};

class linkedlist {
public:
    node* head;

    linkedlist() {
        this->head = nullptr;
    }

    void print() {
        while (head != nullptr) {
            std::cout << head->data << " ";
            head = head->next;
        }
    }

    void push(int val) {

        if (head == nullptr) {
            head = new node(val);
            return;
        }
        node* temp = new node(val);
        temp->next = head;
        head = temp;
    }

    // node* recursive_reverse(node* head) {
    //     if (head == nullptr || head->next == nullptr) return head;
    //     node* _rest = recursive_reverse(head->next);
    //     head->next->next = head;
    //     head->next = nullptr;
    //     head = _rest;
    //     return head;
    // }


    node* rr_bourne_again(node* head) {
        if (head->next == nullptr) return head;
        node* temp = new node(0);
        temp->next = head;
        head = temp;
        node* _rest = rr_bourne_again(head->next);
        head->next->next = nullptr;
        head = _rest;
        delete(temp);
        temp = nullptr;
        return head;
    }
};

int main()
{
    linkedlist ll;
    for (int i = 10; i > 0; i--) ll.push(i);

    // ll.head = ll.recursive_reverse(ll.head);
    
    ll.head = ll.rr_bourne_again(ll.head);
    ll.print();
}

对于你的问题,出现分段错误(segmentation fault)通常是由于内存访问错误导致的。在你的代码中,问题出现在rr_bourne_again函数中,尤其是在递归调用期间。具体地说,这段代码存在一些问题:

node* temp = new node(0);
temp->next = head;
head = temp;
node* _rest = rr_bourne_again(head->next);
head->next->next = nullptr;
head = _rest;
delete(temp);
temp = nullptr;

在这段代码中,你创建了一个新节点temp,将其插入到链表的头部,然后在递归调用rr_bourne_again时,对链表的head指针进行了修改。这可能导致链表的结构混乱和内存泄漏,最终导致分段错误。

要解决这个问题,你可以考虑重新设计rr_bourne_again函数,以确保链表结构的正确性和内存管理。一种可能的解决方法是使用迭代而不是递归来反转链表。这将更容易理解并避免潜在的问题。

英文:
#include &lt;iostream&gt;
class node {
public:
int data;
node* next;
node(int val) : data(val) {
this-&gt;next = nullptr;
}
};
class linkedlist {
public:
node* head;
linkedlist() {
this-&gt;head = nullptr;
}
void print() {
while(head != nullptr) {
std::cout &lt;&lt; head-&gt;data &lt;&lt; &quot; &quot;;
head = head-&gt;next;
}
}
void push(int val) {
if (head == nullptr) {
head = new node(val);
return;
}
node* temp = new node(val);
temp-&gt;next = head;
head = temp;
}
// node* recursive_reverse(node* head) {
//     if (head == nullptr || head-&gt;next == nullptr) return head;
//     node* _rest = recursive_reverse(head-&gt;next);
//     head-&gt;next-&gt;next = head;
//     head-&gt;next = nullptr;
//     head = _rest;
//     return head;
// }
node* rr_bourne_again(node* head) {
if (head-&gt;next == nullptr) return head;
node* temp = new node(0);
temp-&gt;next = head;
head = temp;
node* _rest = rr_bourne_again(head-&gt;next);
head-&gt;next-&gt;next = nullptr;
head = _rest;
delete(temp);
temp = nullptr;
return head;
}
};
int main()
{
linkedlist ll;
for (int i = 10; i &gt; 0; i--) ll.push(i);
// ll.head = ll.recursive_reverse(ll.head);
ll.head = ll.rr_bourne_again(ll.head);
ll.print();
}

I intend to push a temp node to the head of the list, making it the new head. Then I separate the list into two parts - the new head node and the rest, namely the original list. I would reverse the rest list then link it to a nullptr. Finally I would free up the memory allocated to temp node.

I was expecting something like this in the terminal:
10 9 8 7 6 5 4 3 2 1%

but something must have went south and i got a segmentation error instead.
zsh: segmentation fault

What went wrong?

答案1

得分: 2

在你的代码中,你已经注释掉了一个完美执行任务的函数,所以我猜你的问题只是想知道你的尝试出了什么问题:

你的代码首先创建了一个虚拟节点,然后将该节点作为前缀添加到当前的链表中,然后...将跟在虚拟节点后面的节点作为参数传递给递归调用。让我们通过一个具有值1和2的示例列表来可视化这些步骤:

                     head
↓
┌──────────────┐    ┌──────────────┐
│ data: 1      │    │ data: 2      │
│ next: ───────────►│ next: nullptr│
│              │    │              │
└──────────────┘    └──────────────┘

node* temp = new node(0); 创建一个新节点,所以我们得到这个:

 temp                head
↓                   ↓
┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│ data: 0      │    │ data: 1      │    │ data: 2      │
│ next: nullptr│    │ next: ───────────►│ next: nullptr│
│              │    │              │    │              │
└──────────────┘    └──────────────┘    └──────────────┘

temp-&gt;next = head; 创建了链接:

 temp                head
↓                   ↓
┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│ data: 0      │    │ data: 1      │    │ data: 2      │
│ next: ───────────►│ next: ───────────►│ next: nullptr│
│              │    │              │    │              │
└──────────────┘    └──────────────┘    └──────────────┘

...然后通过 head = temp; 将其变为头节点

 temp head
↓    ↓
┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│ data: 0      │    │ data: 1      │    │ data: 2      │
│ next: ───────────►│ next: ───────────►│ next: nullptr│
│              │    │              │    │              │
└──────────────┘    └──────────────┘    └──────────────┘

然后通过传递 head-&gt;next 作为参数进行递归调用,但 head-&gt;next 是数据为1的节点,所以递归执行将看到这样的内容:

                     head
↓
┌──────────────┐    ┌──────────────┐
│ data: 1      │    │ data: 2      │
│ next: ───────────►│ next: nullptr│
│              │    │              │
└──────────────┘    └──────────────┘

新的局部变量 head 指向与我们开始的节点完全相同的节点!所以反转的“问题”没有得到简化...当进行递归调用时,我们没有取得任何进展。注意:我没有描绘之前创建的虚拟节点,因为这个递归执行上下文无法访问它,所以它不相关。

现在,这个过程从顶部继续,唯一能结束这一连串递归调用的事情是内存不足。最有可能的是堆栈内存会首先用尽,但虚拟节点的创建也会消耗堆内存。请注意,实际上不需要为列表反转创建虚拟节点。

你已经有的正确代码不会将相同的节点指针传递给递归调用,而是将指向较短列表的指针传递,这保证了递归将在某一点停止。

此外,正确的代码可以处理列表,因为它会测试 head == nullptr

英文:

In the code you have commented out a function that would do the job perfectly, so I guess your question is only to know what went wrong with your attempt at it:

Your code first creates a dummy node, then prefixes that node to the current linked list, and then ... passes the node that follows that dummy node as argument to the recursive call. Let's visualise those steps with an example list with values 1 and 2:

                     head
↓
┌──────────────┐    ┌──────────────┐
│ data: 1      │    │ data: 2      │
│ next: ───────────►│ next: nullptr│
│              │    │              │
└──────────────┘    └──────────────┘

node* temp = new node(0); creates a new node, so we get this:

 temp                head
↓                   ↓
┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│ data: 0      │    │ data: 1      │    │ data: 2      │
│ next: nullptr│    │ next: ───────────►│ next: nullptr│
│              │    │              │    │              │
└──────────────┘    └──────────────┘    └──────────────┘

temp-&gt;next = head; creates the link:

 temp                head
↓                   ↓
┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│ data: 0      │    │ data: 1      │    │ data: 2      │
│ next: ───────────►│ next: ───────────►│ next: nullptr│
│              │    │              │    │              │
└──────────────┘    └──────────────┘    └──────────────┘

...and then it is made the head by head = temp;

 temp head
↓    ↓
┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│ data: 0      │    │ data: 1      │    │ data: 2      │
│ next: ───────────►│ next: ───────────►│ next: nullptr│
│              │    │              │    │              │
└──────────────┘    └──────────────┘    └──────────────┘

Then the recursive call is made by passing head-&gt;next as argument, but head-&gt;next is the node with data 1, so this is what the recursive execution will see:

                     head
↓
┌──────────────┐    ┌──────────────┐
│ data: 1      │    │ data: 2      │
│ next: ───────────►│ next: nullptr│
│              │    │              │
└──────────────┘    └──────────────┘

The new local variable head points to the exact same node as the node we started with! So the "problem" of reversal has not been simplified... we didn't make any progress when making the recursive call. NB: I did not depict the dummy node that was created earlier, as this recursive execution context has no access to it, so it isn't relevant.

Now the process continues as from the top, and the only thing that can end this chain of recursive calls is a memory shortage. Most likely stack memory will run out first, but also heap memory is consumed by the dummy nodes that are repeatedly created. Note that there really is no need to create dummy nodes for a list reversal.

The correct code you already have does not pass the same node pointer to the recursive call, but a pointer to a shorter list, which guarantees that the recursion will stop at a certain point.

Also, the correct code can cope with an empty list, as it tests for head == nullptr.

答案2

得分: 1

你会发现在递归之前交换下一个指针更容易。但这意味着你需要在每个递归步骤中同时传递 head->next->next 和 head->next。

node* recursive_reverse_impl(node* left, node* right) {
    if (right == nullptr) {
        return left;
    }

    node* nextRight = right->next;
    right->next = left;
    return recursive_reverse_impl(right, nextRight);
}

node* recursive_reverse(node* head) {
    node* newHead = recursive_reverse_impl(head, head ? head->next : nullptr);
    if (head) {
        head->next = nullptr;
    }
    return newHead;
}
英文:

You'll find it easier to swap the next pointer before recursing. But that means you need to pass head->next->next as well as head->next to each recursive step.

node* recursive_reverse_impl(node* left, node* right) {
if (right == nullptr) {
return left;
}
node* nextRight = right-&gt;next;
right-&gt;next = left;
return recursive_reverse_impl(right, nextRight);
}
node* recursive_reverse(node* head) {
node* newHead= recursive_reverse_impl(head, head ? head-&gt;next : nullptr);
if (head) {
head-&gt;next = nullptr;
}
return newHead;
}

huangapple
  • 本文由 发表于 2023年2月27日 13:35:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/75577047.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定