使用C中的递归技术反转链表

时间:2013-08-23 13:10:18

标签: c algorithm

我有以下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; 
} 

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

6 个答案:

答案 0 :(得分:1)

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

S *reverseRecursive(S **headref);

答案 1 :(得分:1)

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

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

答案 2 :(得分:1)

可能更接近。

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;
} 

答案 3 :(得分:1)

谢谢大家。我稍微修改了一下它现在有效。让我知道你的意见。 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; 

  }

答案 4 :(得分:1)

$ 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;
}

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

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

答案 5 :(得分:0)

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

首先,一些设置。让我们轻松加载链表 字符串和打印它们,所以我们可以确保这些东西工作:

// 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 )