关于迭代和递归的一些思考
今天看书遇到一个题目,反转链表
题目:定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头节点。链表定义如下
struct ListNode
{
int m_nKey;
ListNode* m_pNext
};
这个题,我之前做过。当时是用的头插法,在这个链表的基础上重新创建了一个链表,也把答案解出来了。当时那样做可能不算正规解法吧。今天又看到这个题,看到有两种解法,一种是迭代即从头结点开始遇到一个节点就改变该节点的指向。这样当遇到尾节点时,改变尾节点的指向并返回尾节点即可。实现代码如下:
ListNode* ReverseList(ListNode* pHead)
{
ListNode* pReversedHead = nullptr;
ListNode* pNode = pHead;
ListNode* pPrev = nullptr;
while(pNode != nullptr)
{
ListNode* pNext = pNode->m_pNext;
if(pNext == nullptr)
pReversedHead = pNode;
pNode->m_pNext = pPrev;
pPrev = pNode;
pNode = pNext;
}
return pReversedHead;
}
关于这个题,书上有个扩展是用递归实现同样的反转链表的功能。那我当然准备写了,写的过程中陷入了误区。这个递归的出口条件不知道怎样设置。当时想着递归就是划分子问题呀。那反转一个链表,可以划分成反转 头结点 和以头结点的next为头的子链表。如果子链表是反转好的,那让子链表的尾结点指向留下的那个头结点就可以了。那么这就有个问题,子链表的尾结点我怎么获取,如果通过返回值来获得是可以。但是这样的话,题目要求最后返回反转后链表的头结点这个要求就无法完成。所以当时就乱了。
然后在网上查看到了这段代码,别人的递归实现
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null){
return head;
}
// 遍历到链表尾部
ListNode newHead = reverseList(head.next);
// 反转
head.next.next = head;
head.next = null;
return newHead;
}
发现自己陷入了误区,想用递归来实现迭代的思想。递归应该是自底向上,从后往前的。而迭代是从前往后的。
关于从前往后和从后往前举一个简单的例子就是 Fibonacci数列的递归和迭代实现。
public class Solution {
public int Fibonacci(int n) {
if(n>39){
return -1;
}
if(n<=0){
return 0;
}
if(n==1){
return 1;
}
else{
return Fibonacci(n-1)+Fibonacci(n-2);
}
}
} // 代码是复制他人的
上面代码是递归的实现,可以看出 这个函数的想法是 从 数列中的第n个数开始,从未知的数开始一直向前推,直到推到确切的值,再返回。是从后向前的。
return Fibonacci(n-1)+Fibonacci(n-2); // 从后向前推
然后看一下迭代实现
public class Solution {
public int Fibonacci(int n) {
int st =0;
int nd =1;
int sum =0;
if(n>39){
return -1;
}
if(n<=0){
return 0;
}
if(n==1){
return 1;
}
for(int i=2;i<=n;i++){
sum = st + nd;
st = nd;
nd = sum;
}
return sum;
}
}
这里就可以很明显的看出来,迭代是从前向后推,从已知的确定的数推到后面的数。
for(int i=2;i<=n;i++){ // 从前向后推
sum = st + nd;
st = nd;
nd = sum;
}
return sum;
最后小小总结一下,递归是从后向前的改变,迭代是从前往后的改变。如果想要把迭代写的代码改用递归写,一定不能按照原来的思想去构思递归的代码,应该反转迭代的想法去实现。这样也想起了,栈和递归的密切关系,栈是后进先出,递归是从后往前。二者很像,难怪二者结合密切,哈哈。