关于递归和栈。
递归调用的本质就是栈的先进后出,递归可分为三个阶段:
- 前进段:依次执行递归语句之前的代码,保存当前函数的现场(参数、局部变量等),并压入栈中。
- 临界值:终止递归的前进段,通过 return 语句开始进入返回段。
- 返回段:对于栈顶的函数,基于在前进段保存的函数的现场,依次执行递归语句之后的代码,执行完后弹出栈(执行 return 语句即出栈)。
一、实战解析:两两交换链表中的结点
仿照 LeetCode 第 24 题 两两交换链表中的结点,(如果不熟悉这道题,建议先去 LeetCode 把这道题做了)有以下问题:
给定一个链表,两两交换其中相邻的结点,并返回交换后的链表。你不能只是单纯的改变结点内部的值,而是需要实际的进行结点交换。示例:
给定 a->b->c->d, 你应该返回 b->a->d->c.
这里采用递归的方式解题,代码如下:
public static class ListNode{
char val;
ListNode next;
ListNode(char ch){
val = ch;
next = null;
}
}
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null){ // No.1
// 判断临界条件
return head; // N0.2
} else {
ListNode nextNode = head.next; // No.3
head.next = swapPairs(nextNode.next); // No.4
nextNode.next = head; // No.5
return nextNode; // No.6
}
}
对于上面的递归算法,按照「前进段 -> 临界条件 -> 返回段」的流程,有以下解析:
1. 前进段
注: 这里在进行说明的时候,用双引号包裹字符表示该字符的结点(ListNode),如用 “a” 表示这是结点 a。
当函数 swapPairs() 开始执行的时候,先将当前函数入栈,标记为函数 ①。从第 No.1 行代码开始依次执行程序,此时有结点 head = “a”,不满足临界条件,然后执行代码 ListNode nextNode = head.next;
,执行后的链表结点情况如下:
执行到第 No.4 行的递归语句 swapPairs(nextNode.next)
的时候,保存当前函数的现场,将结点 nextNode.next = "c"
作为参数传递到下一个将要入栈的函数中。
(注意, 第 No.4 行左边的代码 head.next =
位于递归语句之后,在返回段的时候才会执行。)
然后通过递归语句 swapPairs(nextNode.next)
进入结点 head = “c” 的函数中,先将当前函数入栈,标记为函数 ②。依次执行代码 ListNode nextNode = head.next;
后的链表结点情况如下:
执行到第 No.4 行的递归语句时,保存当前函数的现场,将结点 nextNode.next = null
作为参数传入到下一个将要入栈的函数中。
然后通过递归语句 swapPairs(nextNode.next)
进入结点 head = null 的函数中,先将当前函数入栈,标记为函数 ③:
依次执行代码,在执行到第 No.1 行的判断语句时有结点 head = null,满足临界条件,此时通过第 No.2 行的 return 语句将栈顶的函数 ③ 出栈。将结点 head = null 作为返回值传递给新的栈顶函数,进入返回段。
2. 返回段
将满足临界条件的函数 ③ 出栈后,进入到返回段的第一个函数,也就是栈顶的函数 ②。此时,基于之前入栈时保存的函数现场,依次执行执行递归语句之后的代码:
// 这里的 swapPairs(nextNode.next) 的值即为上一个栈顶函数的返回值
head.next = swapPairs(nextNode.next); // No.4
nextNode.next = head; // No.5
return nextNode; // No.6
执行完后的链表结点情况如下:
执行到第 No.6 行的 return 语句时,当前函数执行完毕,然后出栈,将结点 nextNode 作为返回值传递给新的栈顶函数。
对于当前栈顶的函数 ①,基于之前入栈时保存的函数现场,依次执行执行递归语句之后的代码:
// 这里的 swapPairs(nextNode.next) 的值即为上一个栈顶函数的返回值
head.next = swapPairs(nextNode.next); // No.4
nextNode.next = head; // No.5
return nextNode; // No.6
执行完后的链表结点情况如下:
执行到第 No.6 行的 return 语句时,当前函数执行完毕出栈。此时递归调用栈的函数全部执行完毕,所以将结点 nextNode 作为最终结果返回:
(完)如有问题,欢迎交流~