链表反转(图解)
前言
链表反转是面试中的一个高频题目,可以通过多种方法实现,例如循环,递归;本篇文章将使用递归对链表实现反转。
一、链表反转
例如,给定一个单链表,原链表中的数据为: 1–>2–>3–>,反转后的链表为:4–>3–>2–>1。
二、递归思想
递归,有”递“还要有”归“,所谓递归,就是会在函数内部代码中,调用这个函数本身,所以,我们必须要找出递归的结束条件,否则会一直调用自己,造成死循环,从而栈内存溢出。也就是说,我们需要找出当方法的参数符合某一条件时,递归结束,之后把结果返回。使用递归反转其实就是从链表第一个存数据的节点开始,一次递归调用反转每一个节点,直到把最后一个节点反转完毕,整个链表就反转完毕。
三、具体实现
1.反转方法API
public void reverse():对整个链表反转
public void reverse(Node curr):反转链表中的某个节点,并把反转后的节点返回
2.具体实现代码
(链表的实现代码见另一篇文章:链式存储结构–单向链表的实现戳这儿
public void reverse(){
if (isEmpty()){
return;
}
reverse(head.next);
}
public Node reverse(Node curr){
if (curr.next == null){
//如果curr是最后一个节点,让头节点指向它
head.next = curr;
return curr;
}
//递归反转当前节点curr的下一个节点,返回值是链表反转后当前节点的上一个节点
Node pre = reverse(curr.next);
//让返回的下一个节点成为当前节点curr
pre.next = curr;
//使节点原来的指向为空
curr.next = null;
return curr;
}
3.方法测试
测试结果:
四、方法调用过程
main方法进栈:
在main方法中调用reverse()方法,reverse()方法进栈。
reverse() 方法调用带重载的reverse(1) 方法。(此时reverse的参数curr=1,此处简写为reverse(1))reverse(1) 方法进栈,当方法执行到Node pre = reverse(2)时,调用带重载的reverse(2) 方法。
reverse(2) 方法进栈,当方法执行到Node pre = reverse(3)时,调用带重载的reverse(3) 方法。
reverse(3) 方法进栈,当方法执行到Node pre = reverse(4)时,调用带重载的reverse(4) 方法。
reverse(4) 方法进栈, 当执行reverse(4) 方法时, 进行判断curr.next == null ,此时满足条件,使头结点指向 4 这个结点 ,然后,该方法将该节点返回给reverse(3).(reverse(4) 执行完毕出栈)
此时使head节点指向该curr节点,curr = 4;
接下来reverse(3) 接收 reverse(4) 的返回值,此时 当前pre 是4结点 ,执行剩余的代码;
pre.next = curr; 将 4 结点 的下一个结点指向 3 结点;
curr.next = null; 将 3 结点 的下一个结点指向 null (使3 结点原本指向指向4结点的指针断开)
reverse(3) 执行完毕后,将返回值(返回的是3结点)给 reverse(2) , 然后出栈。
紧接着 reverse(2) 接收 reverse(3) 的返回值 此时 当前pre 是3结点 ,执行剩余代码;
pre.next = curr; 将 3 结点 的下一个结点指向 2 结点;
curr.next = null; 将 2 结点 的下一个结点指向 null; (使2结点原本指向指向3结点的指针断开)
reverse(2) 执行完毕后,将返回值(返回的是2结点)给 reverse(1) , 然后出栈。
最后reverse(1) 接收 reverse(2) 的返回值 此时pre 是2结点 ,执行剩余的代码;
pre.next = curr; 将 2 结点 的下一个结点指向 1 结点;
curr.next = null; 将 1 结点 的下一个结点指向 null ;(使1结点原本指向指向2结点的指针断开)
reverse(1) 执行完毕后,将返回值(返回的是1结点)给 reverse() 方法(该方法没有使用变量接收返回值 ), 然后出栈。
至此,程序执行完毕,使用递归反转单链表的功能已经实现。
方法调用内存图如下:
总结
综上,递归实现链表反转代码并不复杂,难度在于去理解这个递归调用的整个逻辑。