当前位置: 首页 > 知识库问答 >
问题:

使用递归从尾部开始反转链表

谢建业
2023-03-14

我仍然在努力用递归技术来解决这个问题。我知道下面有更好的方法来解决我的问题,即反转链表。我见过的大多数方法都是通过从头到尾开始反转指针,或者使用迭代或递归。

我试图通过递归查找列表中的最后一个节点,然后每次函数返回时都更改指针来反转列表。

下面我到底做错了什么?或者这种方法甚至可以工作,而不需要向递归函数传递更多参数?提前感谢您的帮助。

struct Node
{
    int data;
    struct Node *next;
};

Node* Reverse(Node *head)
{
    static Node* firstNode = head;
    // if no list return head
    if (head == NULL)
    {
        return head;
    }

    Node* prev = NULL;
    Node* cur = head;

    // reached last node in the list, return head
    if (cur->next == NULL)
    {
        head = cur;
        return head;
    }

    prev = cur;
    cur = cur->next;
    Reverse(cur)->next = prev;

    if (cur == firstNode)
    {
        cur->next = NULL;
        return head;
    }
    return cur;
}

编辑:另一次尝试

Node* ReverseFromTail(Node* prev, Node* cur, Node** head);

Node* ReverseInit(Node** head)
{
    Node* newHead = ReverseFromTail(*head, *head, head);
    return newHead;
}



Node* ReverseFromTail(Node* prev, Node* cur, Node** head)
{
    static int counter = 0;
    counter++;

    // If not a valid list, return head
    if (head == NULL)
    {
        return *head;
    }

    // Reached end of list, start reversing pointers
    if (cur->next == NULL)
    {
        *head = cur;
        return cur;
    }


    Node* retNode = ReverseFromTail(cur, cur->next, head);
    retNode->next = cur;


    // Just to force termination of recursion when it should. Not a permanent solution 
    if (counter == 3)
    {
        cur->next = NULL;
        return *head;
    }

    return retNode;
}

最终解决了:

Node* NewestReverseInit(Node* head)
{
    // Invalid List, return
    if (!head)
    {
        return head;
    }

    Node* headNode = NewestReverse(head, head, &head);

    return headNode;
}

Node* NewestReverse(Node *cur, Node* prev, Node** head)
{
    // reached last node in the list, set new head and return
    if (cur->next == NULL)
    {
        *head = cur;
        return cur;
    }

    NewestReverse(cur->next, cur, head)->next = cur;

    // Returned to the first node where cur = prev from initial call
    if (cur == prev)
    {
        cur->next = NULL;
        return *head;
    }

    return cur;
}

共有3个答案

吕宸
2023-03-14

下面是我在5分钟内编写的完整实现:

#include <stdio.h>

struct Node
{
     int data;
     struct Node *next;
};

struct Node* Reverse(struct Node *n)
{
    static struct Node *first = NULL;

    if(first == NULL)
      first = n;

    // reached last node in the list
    if (n->next == NULL)
      return n;

    Reverse(n->next)->next = n;

    if(n == first)
    {
      n->next = NULL;
      first = NULL;     
    }

    return n;
}

void linked_list_walk(struct Node* n)
{
  printf("%d", n->data);
  if(n->next)
    linked_list_walk(n->next);
  else
    printf("\n");
}

int main()
{
  struct Node n[10];
  int i;

  for(i=0;i<10;i++)
  {
    n[i].data = i;
    n[i].next = n + i + 1;
  }
  n[9].next = NULL;

  linked_list_walk(n);
  Reverse(n);
  linked_list_walk(n+9);
}

输出:

0123456789                                                                                                                                                                          
9876543210 
双弘益
2023-03-14

这是可以做到的。理解递归的关键是起点是什么?

通常我会创建一个“启动”函数来准备第一次调用。有时它是一个单独的函数(如底部的非OO实现)。有时这只是一个特殊的第一次呼叫(如下面的示例)。

此外,关键是在变量更改之前记住它们,以及新的头是什么。

新的头是列表的最后一个元素。所以你必须从列表的底部开始。

下一个元素始终是您的父元素。

然后诀窍就是按正确的顺序做每件事。

    Node* Reverse( Node* parent) // Member function of Node.
    {
        Node* new_head = next ? next->Reverse( this ) 
                              : this;

        next = parent;

        return new_head;
    }

您可以使用以下方式调用函数:var. Reverse(nullptr);

例子:

int main()
{
    Node d{ 4, nullptr };
    Node c{ 3, &d };
    Node b{ 2, &c };
    Node a{ 1, &b };

    Node* reversed = a.Reverse( nullptr );
}

那么这里发生了什么?

首先,我们创建一个链表:

 a->b->c->d->nullptr

然后该函数html" target="_blank">调用:

  1. a.调用反向(nullptr)
  2. 这将调用下一个节点上的Reverseb。使用父a反向。
  3. 这将调用下一个节点上的Reversec。使用父b反向。
  4. 这将调用下一个节点上的Reversed。使用父c反向。
  5. d没有下一个节点,所以它说新头是它自己。
  6. d下一个现在是它的父c
  7. d返回自身为new_head
  8. 返回cnew_headd返回的是d
  9. c下一个现在是它的父b
  10. c返回它从d
  11. 接收到的 new_head
  12. 返回bnew_headc返回的是d
  13. b下一个现在是它的父a
  14. b返回它从c
  15. 接收到的 new_head
  16. 返回anew_headb返回的是d
  17. a下一个现在是它的父nullptr
  18. a返回它从b
  19. 接收到的 new_head
  20. d返回

非面向对象实现;

Node* reverse_impl(Node* parent)
{
    Node* curr = parent->next;
    Node* next = curr->next;

    Node* new_head = next ? reverse_impl( curr ) 
                          : curr;

    curr->next = parent;

    return new_head;
}

Node* reverse(Node* start)
{
    if ( !start )
        return nullptr;

    Node* new_head = reverse_impl( start );
    start->next    = nullptr;

    return new_head;
}
唐照
2023-03-14

我不会给你密码,我会给你想法。您可以在代码中实现这个想法。

所有递归问题的关键是弄清楚两种情况:重复步骤和结束情况。一旦你这样做了,它几乎就像魔法一样工作。

将此原则应用于颠倒链表:

  • 结束案例:一个元素的列表已经反转(这很简单),并返回元素本身
 类似资料:
  • 我做了一个使用递归方法反转单链表的函数。然而,我在执行下面的代码时遇到了一些困难: 我应该如何在ReverseCursive函数/方法中传递第二个参数,以便执行它? 作为第二个参数,我想简单地传递链表的头节点。但是我不知道如何从类的init方法中获取头节点linked_list 我试了几件事,但都解决不了。也许我不太擅长OOP概念。有人能帮我解决这个问题吗?

  • 我有一个关于如何将“递归”转换为“尾部递归”的问题。 这不是家庭作业,只是在我试图润色算法书籍中的递归定理时出现的一个问题。 我熟悉使用递归的两个典型示例(阶乘和斐波那契序列),也知道如何以递归方式和尾部递归方式实现它们。 我的代码如下(我使用Perl只是为了使其简单,但可以轻松地转换为C/Java/C)。 运行代码时,输出如下: 递归函数在返回之前使用不同的参数调用自己两次。我尝试了几种方法将其

  • 我试图了解如何将各种递归函数转换为尾递归。我已经查看了许多将斐波那契和阶乘转换为尾递归的示例,并理解了这些示例,但很难跳到具有某种不同结构的问题。一个例子是: 如何将其转换为尾部递归实现? 我已经看过类似的问题,例如:将正常递归转换为尾部递归,但这些似乎并没有转化为这个问题。

  • 还缺少的是将最后一个节点的next赋值为NULL。 在任何世界里,像这样的东西会起作用吗?它给出了一个运行时/分段错误。

  • 我想知道是否有一些通用方法可以用foo(…)转换“正常”递归foo(…) 作为尾部递归的最后一个调用。 例如(scala): 函数语言将递归函数转换为等价尾部调用的一般解决方案: 一种简单的方法是将非尾部递归函数包装在蹦床单子中。 所以pascal函数不再是递归函数。然而,蹦床单子是需要完成的计算的嵌套结构。最后,是一个尾递归函数,它遍历树状结构,解释它,最后在基本情况下返回值。 Rúnar Bj

  • 我只是想知道这样的函数是否可以递归地完成。我觉得很难,因为它自己叫了两次。 这是我在javascript中的非尾部递归实现。(是的,我知道大多数javascript引擎不支持TCO,但这只是理论上的。)目标是找到给定数组(arr)中具有特定长度(大小)的所有子列表。例如:getSublistsWithFixedSize([1,2,3],2)返回[[1,2]、[1,3]、[2,3]]