【问题标题】:reverse a linked list using recursion error使用递归错误反转链表
【发布时间】:2015-06-10 01:03:36
【问题描述】:

我了解递归,所以我尝试编写反向链表程序。我写了下面的函数,但它说分段错误(核心转储)。

void reverse(){
    if (head -> next == NULL){
        return;
    }
    reverse (head -> next);
    struct node *q = (struct node*) malloc (sizeof(struct node));
    q = head -> next;
    q -> next = head;
    head -> next = NULL;
}

请有人指导我。谢谢。

【问题讨论】:

    标签: c recursion linked-list


    【解决方案1】:

    不应该反向接受一个论点吗?请注意,您不能更改函数中的指针并使其成为持久的更改。也就是说,在 C 函数中,唯一持久的变化是那些使用 *var = something 的变化。

    【讨论】:

    • 这让事情变得更容易。但是,您仍然将参数传递给 reverse,但它的签名表明它不需要一个
    • 我没有将参数传递给反向函数?我没有得到你的最后一点。谢谢。
    • @deep:是的,你是。请参阅reverse(head->next) 行。
    【解决方案2】:

    递归是一种通过实践获得的思维方式。所以祝贺你的尝试。这是不正确的,但不要气馁。

    这里有两种解决问题的方法。

    您的目标是将其细分为一个较小的版本,再加上一个(希望计算简单且快速)增量步骤,该步骤将较小版本的解决方案变为完整的解决方案。这就是递归思维的精髓。

    第一次尝试:将列表视为头部元素加上“列表的其余部分”。即,

    L = empty or
      = h . R
    

    其中h 是头元素R 是列表的其余部分,点. 将新元素加入列表。反转此列表包括反转R,然后在末尾附加h

    rev(L) = empty if L is empty
           = rev(R) . h otherwise
    

    这是一个递归解决方案,因为我们可以递归调用 reverse 函数来解决反转 R 的稍微小一点的问题,然后添加一点工作来追加 h,这样我们就得到了完整的解决方案。

    这个公式的问题是附加h 比你想要的要贵。因为我们有一个只有一个头指针的单链表,所以很耗时:遍历整个链表。但它会正常工作。在 C 语言中是:

    NODE *rev(NODE *head) {
      return head ? append(head, rev(head->next)) : NULL;
    }
    
    NODE *append(NODE *node, NODE *lst) {
      node->next = NULL;
      if (lst) {
        NODE *p;
        for (p = lst; p->next; p = p->next) /* skip */ ;
        p->next = node;
        return lst;
      }
      return node;
    }
    

    那么如何摆脱糟糕的表现呢?通常情况下,问题的不同递归公式具有不同的效率。所以经常会涉及到一些试验和错误。

    下一次尝试:考虑将列表划分为两个子列表的计算:L = H T,因此 rev(L) = rev(T) + rev(H)。这里加上+ 是列表连接。关键是如果我知道rev(H) 并且想在其头部添加一个新元素,那么要添加的元素是T 中的first 元素。如果这看起来很模糊,令 H = [a, b, c] 和 T = [d, e]。那么如果我已经知道 rev(H) = [c, b, a] 并且想在头部添加下一个元素,我想要 d,它是 T 的第一个元素。在我们的小符号中,你可以写下这个观察就这样:

    rev(H + (d . T)) = rev(T) + ( d . rev(H) )
    

    所以这看起来非常好。在这两种情况下(获取 T 的头部并将其移动到 rev(H) 的头部),我只对列表的头部感兴趣,这是非常有效的访问。

    当然,如果 T 为空,则 rev(H) = rev(L)。这就是答案!

    把它写成递归过程。

    NODE *rev(NODE *t, NODE *rev_h) {
      if (t) {                // if t has some elements
        NODE *tail = t->next;   // save the tail of T
        t->next = rev_h;        // prepend the head to rev(H)
        return rev(tail, t);    // recur to solve the rest of the problem
      }
      return rev_h; // otherwise T is empty, so the answer is rev(H)
    }
    

    一开始,我们对 rev(H) 一无所知,所以 T 是整个列表:

    NODE *reversed_list = rev(list, NULL);
    

    接下来要注意的是这个函数是尾递归的:递归调用在函数返回之前执行。这很好!这意味着我们可以轻松地将其重写为循环:

    NODE *rev(NODE *t, NODE *rev_h) {
    recur:
      if (t) {                // if t has some elements
        NODE *tail = t->next;   // save the tail of T
        t->next = rev_h;        // prepend the head to rev(H)
        rev_h = t;              // "simulate" the recursive call
        t = tail;               //   by setting both args
        goto recur;             //   and going back to the start
      }
      return rev_h; // otherwise T is empty, so the answer is rev(H)
    }
    

    您始终可以使用尾递归调用来进行这种转换。您应该认真思考为什么会这样。

    现在goto 很容易被重写为while 循环,我们可以将rev_h 设为初始化为NULL 的局部变量,因为这就是初始调用所做的全部:

    NODE *rev(NODE *t) {
      NODE *rev_h = NULL;
      while (t) {             // while t has some elements
        NODE *tail = t->next;   // save the tail of T
        t->next = rev_h;        // prepend the head to rev(H)
        rev_h = t;              // "simulate" the recursive call
        t = tail;               //   by setting both args
      }
      return rev_h; // otherwise T is empty, so the answer is rev(H)
    }
    

    一个只需要少量固定空间的就地链表逆向器!

    然后看!我们从来不用画有趣的方框图和箭头图,也不必考虑指针。它“刚刚发生”是通过仔细推理如何将问题细分为更小的自身实例,即递归的本质。这也是一种很好的方式,可以看出循环只是一种特殊的递归。很酷,不是吗?

    【讨论】:

      【解决方案3】:

      我假设您在 .c 文件中预定义了以下内容

      typedef struct node node_t;
      struct node {
          int some_data;
          node_t *next;
      };
      
      /* Your linked list here */
      typedef struct {
          node_t *head;
          node_t *foot; /* to keep track of the last element */
      } list_t;
      

      在你的函数中,你犯了一些错误

      • 不提供任何输入参数
      • 访问 head->next 当程序不知道在哪里找到 head 时

      因此,导致了 C 中最令人沮丧的错误——分段错误!

      相反,您应该尝试以下方法:

      void reverse(list_t *mylinkedlist){
          if (mylinkedlist->head->next == NULL) {
              return;
          }
          /* do something */
      }
      

      【讨论】:

        猜你喜欢
        • 2020-07-22
        • 2018-11-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-12-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多