一、要求
给定一个链表,要求把链表反转,假如原链表存储的值依次为 1->2->3->4->null;,
则要求输出结构为4 -> 3 -> 2 -> 1 -> null
二、分析
该链表的实现主要是要改变链表的指向。因此我们可以把问题分解成小模块,先不要想着如何把整个链表反转,而是先实现把两个结点反转,然后再依次循环实现整个链表的反转。这种把大问题化为小问题的思想正是递归思想,一般可以用递归实现的也就可以用迭代实现。接下来看一下如何用递归和迭代实现链表的反转?
三、代码实现
3.1递归实现
3.1.1思路
- 递归就是方法不断调用自身,直到条件结束。
- 这里结束条件是最后一个结点的链域等于NULL;
3.1.2代码
public LinkNode reversal(LinkNode head) {
if (head == null || head.getNext() == null) {
return head;
}
LinkNode next = head.getNext();
LinkNode node = reversal(next); //不断的调用方法本身
next.setNext(head);
head.setNext(null);
return node;
}
想要把递归这块的内部过程了解,我们需要借助堆栈的入栈和出栈来跟踪代码;
调用方法本身是在入栈,方法的返回是在出栈。
假设给定一个链表:
做入栈操作:入栈时用到的变量有head, next两个变量,我们来看看在入栈过程中两个变量的值变化
刚开始传递的时整个链表head,只要链表还有结点,就让next指向下一个结点再把该结点作为参数传进去,直到为最后一个结点时,返回,这是就到了出栈的过程:
做出栈操作:出栈时用到的变量有head, next,node三个变量,我们来看看在出栈过程中三个变量的值变化
刚开始栈顶弹出的是链表的最后一个结点,每次出栈都让后一个结点指向前一个结点,再把前一个结点的链域设置为NULL,当全部出栈,把保存了最后结果的node值返回。
总结:使用递归虽然代码简洁,但是不好理解,而且递归调用方法,浪费空间,而且如果链表太长还容易造成堆栈的溢出。
3.2迭代实现
3.2.1思路
- 利用变量的原值推算出变量的一个新值,迭代就是A不停的调用B。
- 这里我们需要定义三个变量:
- 一个表示当前节点,该节点可以用来当作循环条件;
- 一个表示直接前驱节点,初始化为null,就可以巧妙的把反转后的链表的最后一个结点的链域设置成null;因为是从链表的第一个结点开始遍历链表,所以第一个结点也就是反转后的最后一个结点,如果刚开始不给它的链域设置null,后面就没有机会了;
- 一个表示直接后继结点,当前面的改变指向后,这个变量可以用来保存剩余结点;
- 最后用遍历来不断改变这三个变量的值;就可实现反转;
3.2.2代码
public LinkNode reversal(LinkNode node) {
LinkNode pro = null; //直接前驱节点
LinkNode curr = node; //当前结点
LinkNode next = null; //直接后继节点
while(curr != null) {
next = curr.getNext();
curr.setNext(pro);
pro = curr;
curr = next;
}
return pro;
}
先把代码思路过一遍:
- 刚开始传进来一个node链表,先让curr指向该链表;
- 然后开始遍历,让next指向下一个结点,curr指向pro,因为pro初始化为null,所以这时候就把第一个结点的链域设置成null了;
- 然后让pro指向当前结点curr,curr指向next;
- next再后移一个结点;
总结:这里一定要注意顺序,顺序不对会导致值被覆盖,不能达到目的。
优点:使用迭代效率高,运行时间只因循环次数增加而增加;而且没什么额外开销,空间上也没有什么增加
缺点: 不容易理解;代码不如递归简洁; 编写复杂问题时困难。