由翻转字符窜再次理解递归

来源:互联网 发布:时尚杂志知乎 编辑:程序博客网 时间:2024/05/29 02:59

要求:输入一个字符串,字符串反序输出。
比如:”hello” ⇒ “olleh”
首先就看代码是什么:

#include <iostream>#include <string>using namespace std;string str;void reverse(int n) // 递归翻转字符串{    if(n < str.size())    {        reverse(n + 1);        cout << str[n];    }}int main(){    cout << "Enter string: ";    cin >> str;    cout << "Reversing string is: ";    reverse(0);    cout << endl;    return 0;}

当然有很多别的思路来完成这个任务,这里主要强调一种递归的巧妙思路:暂存下一条指令的地址。

通过这种较为偏向硬件的说法,来解答算法设计中递归的调用问题。

比如这里,第一次进入的是reverse(0),进入后判断0小于整个string的大小,因此将进入调用自身的指令。注意,此时不能就这样走了,还得暂存当前指令后面的那条指令地址,整个指令就是:
cout << str[n];
也就是输出当前下标为n的字符。但是现在还不能输入,得等到自己对自己的探险结束后才回来输出。

递归调用像是盗梦空间中的梦中梦,而且这个梦中梦可以不止仅仅只有三层!

如此想象:第一次进入梦境,为了回到现实,需要在这个梦后面留个钩子,以从上一层梦境中跳出来。这就需要保存起来,我们用的是栈,看重的是它后入先出的特性。当你进入梦境最深层想回来你是不是得完成最深的那个梦境,虽然最深的梦境是最后进入的却最先完成。
进入第一层梦境后,把回到现实的地址存在了栈中,开始第一层梦境,然后发现条件满足还可以进入下一层梦境,于是深入第二层。当然你可不想困在第二层梦境中出不来,于是在第一层梦境的入口边上也做个标记,并把地址保存到栈顶。如此继续,直到最深层。
最深层执行时发现不能再进入更深了,于是该回去现实了。能一步跨回到现实吗?隔着那么多层梦境呢!饭要一口一口吃,一次只能回去一层。怎么回去?借用栈。
这个时候一看栈顶,好,这是下一步可以去的地方,把这个地址写入PC(程序计数器),顺利回到上一层梦境,这一层的梦境并未执行完毕,因此继续执行剩下部分。到这层梦境的尾声了,这层梦境顺利结束,下一步去哪里?看栈顶地址。跳到栈顶指示的位置开始执行再上一层的梦境的剩下部分。如此循环,直到靠近现实的那场梦境,再到回到现实,执行完现实的最后部分。栈已经空了,现实也结束。

这里只说到保存下一步的地址,实际上不止,会保存当前步骤的所有信息,我们称之为保护现场,比如现实中reverse(0),输出下标为0的字符,这个0就要被保存到寄存器,用来最终完成现实的剩下部分。

讲这么啰嗦,纯粹是因为,递归的思想太重要了。这不是我们记住全排列,翻转斐波那契数列,甚至是汉诺塔的解法就OK,行走江湖就不怕了,不是这样的。不理解执行的步骤推移,很难体会这种数据的游走过程。
虽然我支持设计算法时将这个部分忽略,关注顶层设计就好,要专注于当前任务,分而治之会更优雅,但绝不意味着不该仔细思考背后的硬件操作。

就像前几天写二叉树的递归插入设计,我用BST去接下一层递归的返回地址,总是得不到正确的结果,单步跟踪才理解用BST->Left或者BST->Right去接下一层调用的根一样。
你不理解数据的流动过程,便没有底气说,我这么设计就是对的。

以上。

0 0