翻转链表-迭代和递归双版本
将一个链表翻转,如 1->2->3->4 变成 4->3->2->1 的链表。这是一个非常著名的面试题,看似非常的简单,但实际上非常的tricky.
实现方法可以有递归和迭代两种方法,这两个算法也都保证了in-place 和 one-pass. 所以效率还是很高的。这篇文章主要基于http://leetcode.com/2010/04/reversing-linked-list-iteratively-and.html. 他讲解的基本已经比较清楚,我就再从比较菜的角度去分析一下。
迭代方法:
首先来看迭代版本的基本思路,先上图:
上图就是迭代的第一步,基本就是prev, curr, next三个指针跟踪着当前点的情况,然后更新后向前移动,直到移到链表末尾
看代码后应该更清晰:
1 void reverse(Node*& head) { 2 if (!head) return; 3 Node* prev = NULL; 4 Node* curr = head; 5 while (curr) { 6 Node* next = curr->next; 7 curr->next = prev; 8 prev = curr; 9 curr = next; 10 } 11 head = prev; 12 }
代码中要注意 Node*&, 毕竟是改变了链表的结构,所以链表的头指针应该传一个引用。
翻转链表的迭代版本很清晰,且容易实现。而下面的递归方法虽然代码很短,但是比较难理解,且实现起来细节也很多。
递归版本:
这个整体过程比较复杂,所以还是先上图:
然后来看着代码分析:
void reverse(Node*& p) { if (!p) return; Node* rest = p->next; if (!rest) return; reverse(rest); p->next->next = p; p->next = NULL; p = rest; }
递归过程中,首先迅速由递归函数进行至第一幅图的情况(其实下面还有一步,此时rest 指向NULL,返回到第一幅的情况)
然后将当前节点的下一节点的子节点倒指向其父节点(即当前节点),然后将当前节点指向NULL,即当前我们翻转了从当前节点到末尾节点。
注意这之后的最后一个语句,将p 赋值为 rest,然后返回。 返回到上一层会发生什么呢? 这就要注意每层调用reverse函数时,传进去的指针式rest, 即修改上一层的rest指针为当前层的rest指针,换句话说,在以后递归的过程中,rest指针将一直指向最后一个节点,即新的链表根节点。