单向链表反转算法——递归版和迭代版

  最近在做笔试题时,遇到一道编程题:单向链表反转算法。一时紧张,没写出来就提前交卷了,然而交完卷就想出来了。。。

  最初想出来的是递归版,遗憾的是没能做到尾递归,后来又琢磨出了迭代版。后来用实际编译运行测试了一遍,能正常运行。

  递归版的灵感来源于《Haskell 趣学指南》中非常简洁的快速排序算法的实现,其思想是将单向链表分割头部和尾部。其中头部指是链表的第一个节点,尾部是指除去第一个节点后的子链表。通过递归的方法,将子链表继续分割成头部和尾部,直至尾部指剩下一个节点,无法继续分割,然后将头部和尾部的位置交换,返回尾部,然后将前一个头部接到返回的尾部后面,继续返回新的尾部,依此类推,直到整个递归完成。

  迭代版的思想是,用三个指针(first, now, next)来协助调整顺序。其中每一次迭代(即:每遍历一个节点),都将单链表动态分割成反转后的和反转前的两部分。每一次迭代后,反转后的链表会变长,反转前的链表会变短,直到反转前的链表长度为0,反转完成,返回反转后的链表。整个思想和冒泡排序有点像,把序列(链表)划分成有序区(反转后的部分)和无序区(反转前的部分),但是只需对整个单向链表进行一趟遍历。

  值得注意的是,第一次迭代,是从单向链表的第二个节点开始的。而第一个节点默认就是反转后的节点,而且是反转后的尾节点,为了避免环路导致的死循环,需要将该节点指向的下一个节点的指针设为 NULL 。

  这三个指针的作用如下:

  • first 指针始终指向反转后的链表的首节点;
  • now 指针指向当前遍历到的反转前的链表的首节点;
  • next 指针指向 now 节点的下一个节点,避免在 now->next 指向反转后的链表首节点时,丢失了反转前的链表的引用。

 

关键数据结构如下:

1 typedef struct _Node {
2     int data;
3     struct _Node * next;
4 } Node;

递归版的反转算法代码如下:

 1 Node * Node_reverse(Node *node) {
 2     if (node == NULL) return NULL;
 3     if (node->next == NULL) return node;
 4     Node * n = Node_reverse(node->next);
 5     if (n != NULL) {
 6         n->next = node;
 7         node->next = NULL;
 8     }
 9     return node;
10 }

其实,这个递归版反转算法有个小缺陷:那就是整个递归完成时,返回的节点就是传入的参数。也就是说,如果传入的是反转前的单向链表首节点,那么最终返回的也是这个节点,而这个节点却是反转后的单向链表的尾节点(因为反转了,首尾交换)。因此,使用时,需要做些微不足道的工作来弥补缺陷。即:在使用前需要先对单向链表进行一趟遍历,找到尾节点并用指针引用它。。。其实还能改善一下这段代码来修正这个小缺陷,把这些微不足道的工作交给这个函数来完成,也就是加个 if 分支的事,我懒得写了。迭代版的就没这个问题,而且效率略高。

 

迭代版的反转算法代码如下:

 1 Node * Node_reverse_v2(Node* node) {
 2     if (node == NULL) return NULL;
 3     if (node->next == NULL) return node;
 4 
 5     Node *first = node; //总是指向新链表的首部。
 6     Node *now = node->next;
 7     Node *next = now->next;
 8     first->next = NULL; //首节点变成尾节点,尾节点的下一个节点置空,防止环路。
 9     do {
10         next = now->next;
11         now->next = first;
12         first = now;
13         now = next;
14     } while (next != NULL);
15 
16     return first;
17 }

 

测试代码如下:

 1 void printNode(Node *node) {
 2     if (node != NULL) {
 3         printf("%d ", node->data);
 4     }
 5 }
 6 
 7 int main()
 8 {
 9     LinkedList *l = List_create();
10     for (int i=0; i<20; i++) {
11         List_append(l, i);
12     }
13     List_foreach(l, printNode);
14     printf("\n");
15     List_foreach(List_reverse(l), printNode);
16     printf("\n");
17     List_foreach(List_reverse_v2(l), printNode);
18     printf("\n");
19     return 0;
20 }

输出如下:

 

完整代码下载:

reverse.7z

 

posted @ 2017-04-17 07:38  自来喵的野  阅读(11660)  评论(1编辑  收藏  举报