最近在做笔试题时,遇到一道编程题:单向链表反转算法。一时紧张,没写出来就提前交卷了,然而交完卷就想出来了。。。
最初想出来的是递归版,遗憾的是没能做到尾递归,后来又琢磨出了迭代版。后来用实际编译运行测试了一遍,能正常运行。
递归版的灵感来源于《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(" "); 15 List_foreach(List_reverse(l), printNode); 16 printf(" "); 17 List_foreach(List_reverse_v2(l), printNode); 18 printf(" "); 19 return 0; 20 }
输出如下:
完整代码下载: