从理论上讲,只要允许使用栈,所有的递归程序都可以转化成迭代。

但是并非所有递归都必须用栈,不用堆栈也可以转化成迭代的,大致有两类

  1. 尾递归:可以通过简单的变换,让递归作为最后一条语句,并且仅此一个递归调用。

// recursive int fac1(int n) {if (n <= 0) return 1;return n * fac1(n-1); } // iterative int fac2(int n) {int i = 1, y = 1;for (; i <= n; ++i) y *= i;return y; }

  1. 自顶向下->自底向上:对程序的结构有深刻理解后,自底向上计算,比如 fibnacci 数列的递归->迭代转化。

// recursive, top-down int fib1(int n) {if (n <= 1) return 1;return fib1(n-1) + fib1(n-2); } // iterative, down-top int fib2(int n) {int f0 = 1, f1 = 1, i;for (i = 2; i <= n; ++i) {int f2 = f1 + f0;f0 = f1; f1 = f2;}return f1; }

对于非尾递归,就必须使用堆栈。可以简单生硬地使用堆栈进行转化:把函数调用和返回的地方翻译成汇编代码,然后把对硬件 stack 的  push, pop 操作转化成对私有 stack 的 push, pop ,这其中需要特别注意的是对返回地址的 push/pop,对应的硬件指令一般是 call/ret。使用私有 stack 有两个好处:

  1. 可以省去公用局部变量,也就是在任何一次递归调用中都完全相同的函数参数,再加上从这些参数计算出来的局部变量。
  2. 如果需要得到当前的递归深度,可以从私有 stack 直接拿到,而用递归一般需要一个单独的 depth 变量,然后每次递归调用加 1。

我们把私有 stack 元素称为 Frame,那么 Frame 中必须包含以下信息:

  1. 返回地址(对应于每个递归调用的下一条语句的地址)
  2. 对每次递归调用都不同的参数

通过实际操作,我发现,有一类递归的 Frame 可以省去返回地址!所以,这里又分为两种情况:

  • Frame 中可以省去返回地址的递归:仅有两个递归调用,并且其中有一个是尾递归。

// here used a function 'partition', but don't implement it tempalte<class RandIter> void QuickSort1(RandIter beg, RandIter end) {if (end - beg <= 1) return;RandIter pos = partition(beg, end);QuickSort1(beg, pos);QuickSort1(pos + 1, end); } tempalte<class RandIter> void QuickSort2(RandIter beg, RandIter end) {std::stack<std::pair<RandIter> > stk;stk.push({beg, end});while (!stk.empty()) {std::pair<RandIter, RandIter> ii = stk.top(); stk.pop();if (ii.second - ii.first) > 1) {RandIter pos = partition(beg, end);stk.push({ii.first, pos});stk.push({pos + 1, ii.second});}} }

  • Frame 中必须包含返回地址的递归,这个比较复杂,所以我写了个完整的示例:

    • 以MergeSort为例,因为 MergeSort 是个后序过程,两个递归调用中没有任何一个是尾递归
    • MergeSort3 使用了 GCC 的 Label As Value 特性,只能在 GCC 兼容的编译器中使用
    • 单纯对于这个实例来说,返回地址其实只有两种,返回地址为 0 的情况可以通过判断私有栈(varname=stk)是否为空,stk为空时等效于 retaddr == 0。如果要精益求精,一般情况下指针的最低位总是0,可以把这个标志保存在指针的最低位,当然,如此的话就无法对 sizeof(T)==1 的对象如 char 进行排序了。
    • #include <stdio.h> #include <string.h> # if 1 #include <stack> #include <vector> template<class T> class MyStack : public std::stack<T, std::vector<T> > { }; #else template<class T> class MyStack {union {char* a;T* p;};int n, t; public:explicit MyStack(int n=128) {this->n = n;this->t = 0;a = new char[n*sizeof(T)];}~MyStack() {while (t > 0)pop();delete[] a;}void swap(MyStack<T>& y) {char* q = y.a; y.a = a; a = q;int z;z = y.n; y.n = n; n = z;z = y.t; y.t = t; t = z;}T& top() const { return p[t-1];}void pop() {--t;p[t].~T();}void push(const T& x) {x.print(); // debugp[t] = x;++t;}int size() const { return t; }bool empty() const { return 0 == t; }bool full() const { return n == t; } }; #endif template<class T> struct Frame {static T* base;T *beg, *tmp;int len;int retaddr;Frame(T* beg, T* tmp, int len, int retaddr): beg(beg), tmp(tmp), len(len), retaddr(retaddr){}void print() const { // for debugprintf("%4d %4d %d/n", int(beg-base), len, retaddr);} }; template<class T> T* Frame<T>::base; #define TOP(field) stk.top().field template<class T> bool issorted(const T* a, int n) {for (int i = 1; i < n; ++i) {if (a[i-1] > a[i]) return false;}return true; } template<class T> void mymerge(const T* a, int la, const T* b, int lb, T* c) {int i = 0, j = 0, k = 0;for (; i < la && j < lb; ++k) {if (b[j] < a[i])c[k] = b[j], ++j;elsec[k] = a[i], ++i;}for (; i < la; ++i, ++k) c[k] = a[i];for (; j < lb; ++j, ++k) c[k] = b[j]; } template<class T> void MergeSort1(T* beg, T* tmp, int len) {if (len > 1) {int mid = len / 2;MergeSort1(beg , tmp , mid);MergeSort1(beg+mid, tmp+mid, len-mid);mymerge(tmp, mid, tmp+mid, len-mid, beg);memcpy(tmp, beg, sizeof(T)*len);}else*tmp = *beg; } template<class T> void MergeSort2(T* beg0, T* tmp0, int len0) {int mid;int cnt = 0;Frame<T>::base = beg0;MyStack<Frame<T> > stk;stk.push(Frame<T>(beg0, tmp0, len0, 0));while (true) {++cnt;if (TOP(len) > 1) {mid = TOP(len) / 2;stk.push(Frame<T>(TOP(beg), TOP(tmp), mid, 1));continue; L1:mid = TOP(len) / 2;stk.push(Frame<T>(TOP(beg)+mid, TOP(tmp)+mid, TOP(len)-mid, 2));continue; L2:mid = TOP(len) / 2;mymerge(TOP(tmp), mid, TOP(tmp)+mid, TOP(len)-mid, TOP(beg));memcpy(TOP(tmp), TOP(beg), sizeof(T)*TOP(len));} else*TOP(tmp) = *TOP(beg); int retaddr0 = TOP(retaddr);stk.pop();switch (retaddr0) {case 0: return;case 1: goto L1;case 2: goto L2;}} } // This Implementation Use GCC's goto saved label value // Very similiar with recursive version template<class T> void MergeSort3(T* beg0, T* tmp0, int len0) { MyEntry:int mid;int retaddr;Frame<T>::base = beg0;MyStack<Frame<T> > stk;stk.push(Frame<T>(beg0, tmp0, len0, 0)); #define Cat1(a,b) a##b #define Cat(a,b) Cat1(a,b) #define HereLabel() Cat(HereLable_, __LINE__) #define RecursiveCall(beg, tmp, len) /stk.push(Frame<T>(beg, tmp, len, (char*)&&HereLabel() - (char*)&&MyEntry)); /continue; /HereLabel():; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // retaddr == 0 是最外层的递归调用, // 只要到达这一层时 retaddr 才为 0, // 此时就可以返回了 #define MyReturn /retaddr = TOP(retaddr); /stk.pop(); /if (0 == retaddr) { /return; /} /goto *((char*)&&MyEntry + retaddr); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~while (true) {if (TOP(len) > 1) {mid = TOP(len) / 2;RecursiveCall(TOP(beg), TOP(tmp), mid);mid = TOP(len) / 2;RecursiveCall(TOP(beg)+mid, TOP(tmp)+mid, TOP(len)-mid);mid = TOP(len) / 2;mymerge(TOP(tmp), mid, TOP(tmp)+mid, TOP(len)-mid, TOP(beg));memcpy(TOP(tmp), TOP(beg), sizeof(T)*TOP(len));} else*TOP(tmp) = *TOP(beg); MyReturn;} } template<class T> void MergeSortDriver(T* beg, int len, void (*mf)(T* beg_, T* tmp_, int len_)) {T* tmp = new T[len];(*mf)(beg, tmp, len);delete[] tmp; } #define test(a,n,mf) /memcpy(a, b, sizeof(a[0])*n); /MergeSortDriver(a, n, &mf); /printf("sort by %s:", #mf); /for (i = 0; i < n; ++i) printf("% ld", a[i]); /printf("/n"); int main(int argc, char* argv[]) {int n = argc - 1;int i;long* a = new long[n];long* b = new long[n];for (i = 0; i < n; ++i)b[i] = strtol(argv[i+1], NULL, 10);test(a, n, MergeSort1);test(a, n, MergeSort2);test(a, n, MergeSort3);printf("All Successed/n");delete[] a;delete[] b;return 0; }

将递归转化成迭代的通用技术相关推荐

  1. 树形结构:递归转化为迭代,万能通用方法,分治策略基于栈的实现

    前面提到树的3中遍历迭代实现的时候,讲到:还有一种思路,直接针对递归的实现方式,想办法通过栈来实现递归,得到的还是和上面一样 一般的递归改成迭代的方法,你需要知道迭代指针是如何走的,还要借助数据结构才 ...

  2. 递归转化成非递归过程_8086微处理器中的递归和重入过程

    递归转化成非递归过程 As we all know that a procedure is a set of instruction written separately which can be u ...

  3. python filestorage对象怎么转化成字符串_Python面试的10个常见问题及答案,检验你的学习成果吧!...

    导语 Python已经是现在最受欢迎的编程语言,随着这几年云计算,机器学习,人工智能等技术的发展,学习Python的人越来越多,职位的要求也越来越高,下面我收集了10个面试中经常被问到的问题和答案供大 ...

  4. 不爱跳槽、月薪集中在 8K-17k、五成欲晋升为技术Leader|揭晓中国开发者真实现状

    作者 | 苏宓 出品 | CSDN(ID:CSDNnews) 你为什么想当程序员? 当提及这个话题时,有人称始于兴趣,有人无奈调侃还不是因为穷,也有人说,不是我选择了程序员,而是程序员选择了我,天将降 ...

  5. 回溯法之递归回溯和迭代回溯

      回溯法有通用解题法之称,它可以系统的搜索一个问题的所有解或者任意解.它在问题的解空间树中,按深度优先策略从根节点出发搜索解空间树,算法搜索至解空间树的任意一个结点时,先判断该节点如(子树)是否包含 ...

  6. 数字经济核心科技深度报告:AI+5G是数字时代通用技术平台

    核心层:数字经济发展的底层建筑 核心层的范畴及其经济含义 从宏观视角看,核心层是支撑数字经济发展的底层建筑.这个基座主要包括(1)体. (2)信息技术(IT).(3)通信技术.(4)智能硬件四个领域. ...

  7. iphone扫描文件图片转为文字,苹果x手机图片转文字,苹果手机jpg转换成word,ipad怎么把图片转化成文字

    // 为给定 ID 的 user 创建请求 axios.get('/user?ID=12345').then(function (response) {console.log(response);}) ...

  8. 用计算机看影碟是数字化过程,信息与通用技术学业水平考试达标模拟考试

    信息与通用技术学业水平考试达标模拟考试 青海师大附中高二年级 信息与通用技术学业水平考试达标模拟试题 信息技术部分 一.选择题.(共15题:每题2分共30分) 1.小王是个应届毕业生,有一天他看到有个 ...

  9. python将文本转化成语音并播放

    一.问题 在学习的过程中,我们会涉及到将文本信息,转化成语音的过程,比如:我爬取了一个小说的网站,我要将里面的内容进行语音处理. 目前能够进行语音的方法还是很多,比如win32com,百度ai. 二. ...

  10. Java成神之路技术整理

    转载自 Java成神之路技术整理 以下是Java技术栈微信公众号发布的所有关于 Java 的技术干货,会从以下几个方面汇总,本文会长期更新. Java 基础篇 Java 集合篇 Java 多线程篇 J ...

最新文章

  1. fourinone学习笔记一(上手demo)
  2. 力扣——所有可能的满二叉树
  3. mysql性能优化的一些建议
  4. CListCtrl::InsertColumn()和InsertItem()和SetItemText()
  5. [洪流学堂]Hololens开发高级篇1:凝视(Gaze)
  6. C语言小知识---递归函数的使用
  7. android学习笔记---32_文件断点上传器,解决多用户并发,以及自定义协议,注意协议中的漏洞
  8. Linux命令简介之xargs
  9. mysqldump 备份命令使用中的一些经验总结
  10. jQuery 学习笔记之十 (jQuery ajax )
  11. GJB-150砂尘试验,国军标沙尘试验机构
  12. iOS dSYM详解和分析crash,ips文件
  13. 点对点网络带宽测试软件,iperf点对点网络性能测试工具
  14. 聚观早报 | 货拉拉入局跑腿业务;苹果任命首位首席人力资源官
  15. rc列联表_R语言入门之频率表和列联表
  16. 战高端,荣耀亮出“第二把剑”
  17. 文旅部:对不合理低价游、强迫消费等保持高压态势
  18. Both setBehindContentView must be called in onCreate in addition to setContentView.
  19. 设置Symantc内部LiveUpdate服务器注意事项
  20. Direct3D开发配置指南

热门文章

  1. 动网论坛搭建---问题详解
  2. 前端解决跨域CORS 设置代理
  3. matlab中mux和bus的区别
  4. css+js完成黑洞吞噬小球成长功能
  5. Win 10 下运行红警
  6. SPI,SPI_DMA,FSMC配置流程
  7. Android开发知识(十二):30分钟快速接入微信支付SDK
  8. 高德地图自适应(setFitView)部分Marker显示
  9. 【数据处理与分析】景点数据简易处理
  10. 马蜂窝数据总监分享:从数仓到数据中台,大数据演进技术选型最优解