【问题标题】:Reverse a linked list using recursion technique in C在 C 中使用递归技术反转链表
【发布时间】:2013-08-23 13:10:17
【问题描述】:

我有以下 C 代码,它返回链表的反向。

虽然它反转了链表,但我从来没有得到反转链表的头部,因为restofElements 节点被覆盖了。

S *reverseRecursive(S *headref) {
    S *firstElement   = NULL;
    S *restOfElements = NULL;

    if (headref==NULL) {
        return ;
    }

    firstElement = headref;
    restOfElements = headref->next;

    if (restOfElements == NULL)
        return headref;   

    reverseRecursive(restOfElements);    
    firstElement->next->next  = firstElement;
    firstElement->next  = NULL;          
    headref = restOfElements; 

    return headref; 
} 

如何将反向链表节点的头部返回给调用程序?

【问题讨论】:

  • 射击。你必须有一些东西从递归调用中接收返回。我确定这是您的代码的核心问题。
  • 顺便说一句:建议将return ;改为return NULL;

标签: c algorithm


【解决方案1】:

如果你想改变头指针,你必须通过引用(作为指针)传递它。应修改原型以接收头部为 S **。

S *reverseRecursive(S **headref);

【讨论】:

  • @Jim He 正在返回指针,对。但是eyalm的建议还是解决了这个问题。不需要返回语句 - 将 headref 作为引用传递就可以了。
  • @krrishna 问题是,您正在返回第一个元素的 restOfElements。这意味着您将始终收到最初的第二个元素作为回报。您可以将函数定义为“S* reverseRecursive(S*& headref)”,甚至可以定义为“void reverseRecursive(S*& headref)”。
  • @eyalm 我不反对你,但 krrishna 需要决定他想如何进行递归。如果他作为参考传递,那么他应该具有 sebi 指示的 void 函数。
【解决方案2】:

反向列表的头部等于以restOfElements 开头的反向列表的头部(因为原始headref 必须成为反向列表的最后一个元素)。因此,应该存储回避调用的结果(正如 Jim 在他的评论中已经建议的那样):

...
headref = reverseRecursive(restOfElements);  
firstElement->next->next  = firstElement;
firstElement->next  = NULL;          
/* headref = restOfElements; that's wrong */    
return headref; 

【讨论】:

  • 我同意这个片段是有问题的,因为 ->next->next 可能会跑偏。
  • @Jim,我不认为因为在我复制的部分代码之前,有一个检查 if( restOfElements == NULL )...restOfElements == headref->next == firstELement->next
  • @Jim firstElement->next 始终是非空指针,之前检查过,所以我认为不会有问题。
【解决方案3】:

谢谢大家。我对其进行了一些修改,现在可以使用了。让我知道你的cmets。 new_head 是一个全局变量。

S *reverseRecursive(S *headref)
 {
  S *firstElement   = NULL;
  S *restOfElements = NULL;

  if (headref==NULL)
    {
    return ;
    }
    firstElement = headref;


   if (headref->next == NULL)
      return headref;   
   else
    restOfElements = headref->next;


   reverseRecursive(restOfElements);

   firstElement->next->next  = firstElement;

   firstElement->next  = NULL;          

   if(new_head == NULL ) //just dont take it ervery time
      new_head = restOfElements;

   return new_head; 

  }

【讨论】:

  • 我真的很高兴你让它工作了。对你有益!递归可能很棘手。你可能会对 new_head 是一个全局变量感到不满。如果您想避免这种情况,您可以将其添加为第二个输入参数(init 为 NULL)并沿此方式携带它。但是很好,你有一些工作。恭喜!
  • 我很惊讶这个编译,你应该调高编译器警告级别。您的函数被定义为返回 S*,但您有一个不返回任何内容的裸 return; 语句和一个丢弃返回值的递归调用。
  • 如果他传入一个 NULL 指针,我认为它不会起作用。 (空白返回不是返回堆栈的顶部吗?)。其他问题似乎相互抵消了......
【解决方案4】:

可能更接近。

S *reverseRecursive(S *headref)
 {
  S *firstElement   = NULL;
  S *restOfElements = NULL;
  S *new_head = NULL;
  if (headref==NULL)
    {
    return ;
    }
  firstElement = headref;
  restOfElements = headref->next;
  if (restOfElements == NULL)
       return headref;   
  new_head = reverseRecursive(restOfElements); 
  restOfElements->next = new_head;           
  return restOfElements;
} 

【讨论】:

    【解决方案5】:

    $ gcc -std=c99 -Wall -Wextra reverse.c

    #include <stdlib.h>
    #include <stdio.h>
    
    typedef struct list {
      struct list* next;
      int data;
    } S;
    
    void print_list(S* list) {
      if (list == NULL) { printf("NULL\n"); return; }
      printf("%d ", list->data);
      print_list(list->next);
    }
    
    S* reverse_aux(S* list, S* tail) {
      // invalid arg
      if (list == NULL) { return NULL; }
    
      // base case
      if (list->next == NULL) {
        list->next = tail;
        return list;
      }
    
      // general case
      S* tmp = list->next;
      list->next = tail;
    
      return reverse_aux(tmp, list);
    }
    
    S* reverse(S* list) { return reverse_aux(list, NULL); }
    
    int main(int argc, char* argv[]) {
      // build a list with which to test
      S a[10];
      for (unsigned i = 0; i < sizeof(a)/sizeof(S); ++i) {
        a[i].data = i;
        a[i].next = &a[i+1];
      }
      a[sizeof(a)/sizeof(S) - 1].next = NULL;
      S* list = &a[0];
    
      print_list(list);
      list = reverse(list);
      print_list(list);
    
      return 0;
    }
    

    实际上,由于 reverse 是破坏性的(它改变了它的论点),一个更好的界面设计可能是

    void reverse(S** plist);
    reverse(&list);
    

    【讨论】:

      【解决方案6】:

      所以有两种方法可以递归地反转列表。

      首先,一些设置。让我们轻松加载链表 字符串并打印它们,这样我们就可以确保这些东西有效:

      // linked_list.c
      #include <stdio.h>
      #include <stdlib.h>
      
      // a linked lis of strings
      typedef struct S {
        struct S * next;
        char * val;
      } S;
      
      // print out the list
      void showS(char * const name, S * head) {
        printf("%s: (", name);
        while (head){
          printf(" ");
          printf("%s",head->val);
          head = head->next;
          printf( "%c", head ? ',' : ' ' );
        }
        printf(")\n");
      }
      
      // convert an array of strings into a linked list of strings
      S * mkS(int n, char ** args) {
        S * head = NULL;
      
        if (n > 0 && (head = calloc(n, sizeof(S)))){
          S * curr = head - 1;
      
          while (n-- > 0) {
            curr++;
            curr->val = *args++;
            curr->next = curr + 1;
          }
          curr->next = NULL;
        }
      
        return head;
      }
      

      反转列表的一种方法是将新的头部传回 一旦我们找到它就列出来。我们在本地不需要它(因为我们只是在移动 当前元素到新的结尾),但我们需要它以便调用者 完成后有一个指向列表头部的指针。

      // reverse a list one way
      S * revS1( S * const head ){
        if (head && head->next) {
          S * const new_head = revS1( head->next );
          head->next->next = head;
          head->next = NULL;
          return new_head;
        } else {
          return head;
        }
      }
      

      另一种方法是使用指向指针的指针。唯一的区别是 我们不需要返回任何东西,因为我们直接修改了一个变量 来电者有。我更喜欢这种调用方法,因为它更清晰 我们正在修改列表,而不是返回副本。也更难 让调用者以这种方式意外丢失指向新头部的指针。

      // reverse a list another way
      void revS2( S ** phead ){
        S * const head = *phead;
        if (head && head->next) {
          *phead = head->next;
          revS2( phead );
          head->next->next = head;
          head->next = NULL;
        }
      }
      

      但比这两种方法更好的是非递归地反转列表。 这些函数都不是尾递归的,所以编译器有 为列表中的每个元素分配新的堆栈帧。尝试反转长 足够的清单,你会炸毁你的堆栈。最好只是颠倒列表 使用 while 循环。

      // reverse a list non-recursively
      void revS3( S ** phead ){
        S * head = *phead;
        S * reversed = NULL;
      
        while (head) {
          S * curr = head;
          head = curr->next;
          curr->next = reversed;
          reversed = curr;
        }
        *phead = reversed;
      }
      

      现在我们可以通过在命令行中构建列表来测试我们的结果:

      // just use the command line arguments as our list
      int main(int argc, char** argv){
      
        S* list1 = mkS(argc - 1, argv + 1);
        S* list2 = mkS(argc - 1, argv + 1);
        S* list3 = mkS(argc - 1, argv + 1);
      
        showS( "given", list1 );
      
        showS( "revS1", revS1(list1) );
      
        revS2( &list2 );
        showS( "revS2", list2 );
      
        revS2( &list3 );
        showS( "revS3", list3 );
      
        return 0;
      }
      

      让我们编译:

      % gcc -Wall linked_list.c -o linked_list 
      

      并进行一些测试运行

      % ./linked_list 
      given: ()
      revS1: ()
      revS2: ()
      revS3: ()
      % ./linked_list first second third 
      given: ( first, second, third )
      revS1: ( third, second, first )
      revS2: ( third, second, first )
      revS3: ( third, second, first )
      % ./linked_list only
      given: ( only )
      revS1: ( only )
      revS2: ( only )
      revS3: ( only )
      

      【讨论】:

        猜你喜欢
        • 2017-08-16
        • 2012-12-14
        • 1970-01-01
        • 2020-07-22
        • 2013-07-27
        • 2018-11-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多