Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1488059
  • 博文数量: 226
  • 博客积分: 3997
  • 博客等级: 少校
  • 技术积分: 2369
  • 用 户 组: 普通用户
  • 注册时间: 2010-06-19 17:26
个人简介

Never save something for a special occasion. Every day in your life is a special occasion.

文章分类

全部博文(226)

文章存档

2018年(5)

2017年(11)

2016年(1)

2015年(17)

2014年(14)

2013年(30)

2012年(5)

2011年(52)

2010年(107)

分类: C/C++

2013-08-28 22:12:29


某些算法逻辑,用递归很好表述,程序也很好写。理论上所有的递归都是可以转换成非递归的。
如果有些场合要求不得使用递归,那就只好改成非递归了。通常改成非递归算法的思路,就是使用一个栈来存放计算的临时值。

下面通过2示例展示递归函数非递归化:
示例一: f(n) = 4*f(n-1) - f(n-2)
示例二:二叉树遍历 之 先序遍历 和 中序遍历 


示例一:
假设有如下的递归函数
f(1)=3
f(2)=11
f(n)=4*f(n-1)-f(n-2)
那么写成代码,这个递归函数就是如下:

点击(此处)折叠或打开

  1. // 递归实现
  2. int f_rec(int n)
  3. {
  4.     int res;
  5.     if(n == 1)
  6.             res = 3;
  7.     else if(n==2)
  8.             res = 11;
  9.     else
  10.             res = 4*f_rec(n-1)-f_rec(n-2);
  11.     cout<<"f_rec("<<n<<")="<<res<<endl;
  12.     return res;
  13. }
递归函数非递归实现,需要使用循环。
“递归方式求 f(n) 需要使用 f(n-1)和f(n-2)” 改为非递归则变成 “已知 f(n-2)和f(n-1) 则可计算 f(n)”。
已知条件是 f(1)=3,f(2)=11,循环计算直到参数为n。

点击(此处)折叠或打开

  1. // 非递归实现1
  2. int f_flat1(int n)
  3. {
  4.     int res;
  5.     int i;
  6.     int fn_2 = 3;  // 已知f(n-2)
  7.     int fn_1 = 11; // 已知f(n-1)


  8.     if (n == 1)
  9.         res = 3;
  10.     else if (n == 2)
  11.         res = 11;
  12.     else {
  13.         for (i = 3; i <= n; i++) {
  14.             res = 4 * fn_1 - fn_2;  // 计算f(n)
  15.             printf("f(%d)=4*%d-%d=%d\n",
  16.                             i, fn_1, fn_2, res);
  17.             fn_2 = fn_1;
  18.             fn_1 = res;
  19.         }
  20.     }

  21.     return res;
  22. }

  23. // 非递归实现2
  24. int f_flat2(int n)
  25. {
  26.     int res;
  27.     stack<int> st;
  28.     int i;

  29.     st.push(3);
  30.     st.push(11);
  31.     for(i=3; i<=n; i++)
  32.     {
  33.             int fn_1 = st.top(); // 已知 f(n-1)
  34.             st.pop();
  35.             int fn_2 = st.top(); // 已知 f(n-2)
  36.             st.pop();
  37.             res = 4*fn_1 - fn_2; // 计算 f(n)
  38.             printf("f(%d)=4*%d-%d=%d\n",
  39.                             i, fn_1, fn_2, res);
  40.             st.push(fn_1)
  41.             st.push(res);  
  42.     }
  43.     res = st.top();

  44.     return res;
  45. }

  46. //测试
  47. int main()
  48. {
  49.         int res;
  50.         int n = 5;
  51.         res = f_rec(n);
  52.     //    res = f_flat1(n);
  53.     //    res = f_flat2(n);
  54.     cout<<"result:"<<res;

  55.     return 0;
  56. }


递归版本的计算过程: 
rec_fun(2)=11
rec_fun(1)=3
rec_fun(3)=41
rec_fun(2)=11
rec_fun(4)=153         // f(n-1)
rec_fun(2)=11
rec_fun(1)=3
rec_fun(3)=41           // f(n-2)
rec_fun(5)=571            // f(n)
result:571
计算 f(n) 需要先计算 f(n-1)和f(n-2),它们的计算也都是递归,因此有重复计算。

非递归版本的计算过程:
f(3)=4*11-3=41
f(4)=4*41-11=153
f(5)=4*153-41=571
result:571






示例二:遍历二叉树
二叉树的先序遍历,中序遍历,后序遍历,通常是递归实现的,很好理解。
假设有一个二叉树:
A->{B,C}
B->{D,E}
C->{F,G}
E->{H,I}


先用代码构造出这棵树,然后遍历之。

点击(此处)折叠或打开

  1. class node{
  2. public:
  3.     string value;
  4.     node *lchild, *rchild;
  5.     node(){}
  6.     node(string val)
  7.     {
  8.         value = val;
  9.         lchild = NULL;
  10.         rchild = NULL;
  11.     }
  12.     void assignChild(node *left, node *right)
  13.     {
  14.         lchild = left;
  15.         rchild = right;
  16.     }
  17.     bool hasLeftChild()
  18.     {
  19.         return lchild != NULL;
  20.     }
  21.     bool hasRightChild()
  22.     {
  23.         return rchild != NULL;
  24.     }

  25. }

点击(此处)折叠或打开

  1. void travalFirst_rec(node *root);
  2. void travalmiddle_rec(node *root);
  3. void travalFirst_flat(node *root);
  4. void travalmiddle_flat(node *root);
  5. int main()
  6. {
  7.     node nA("a");
  8.     node nB("b");
  9.     node nC("c");
  10.     node nD("d");
  11.     node nE("e");
  12.     node nF("f");
  13.     node nG("g");
  14.     node nH("h");
  15.     node nI("i");
  16.     nA.assignChild(&nB, &nC);
  17.     nB.assignChild(&nD, &nE);
  18.     nC.assignChild(&nF, &nG);
  19.     nE.assignChild(&nH, &nI);

  20.     // traval
  21.     cout<<"traval first:"<<endl;
  22.     travalFirst_rec(&nA);
  23.     cout<<"\ntraval middle:"<<endl;
  24.     travalmiddle_rec(&nA);
  25.     cout<<"\ntraval first_flat:"<<endl;
  26.     travalFirst_flat(&nA);
  27.     cout<<"\ntraval middle_flat:"<<endl;
  28.     travalmiddle_flat(&nA);
  29. return 0;
  30. }

  31. // 先序遍历_递归实现
  32. void travalFirst_rec(node *root)
  33. {
  34.     if(root==NULL)
  35.             return;
  36.     // visit first
  37.     cout<<root->value<<endl;
  38.     if(root->hasLeftChild())
  39.         travalFirst_rec(root->lchild);
  40.     if(root->hasRightChild())
  41.         travalFirst_rec(root->rchild);
  42. }

  43. // 中序遍历_递归实现
  44. void travalmiddle_rec(node *root)
  45. {
  46.     if(root==NULL)
  47.             return;
  48.     if(root->hasLeftChild())
  49.         travalmiddle_rec(root->lchild);
  50.     // visit second
  51.     cout<<root->value<<endl;
  52.     if(root->hasRightChild())
  53.         travalmiddle_rec(root->rchild);
  54. }

  55. // 先序_非递归实现
  56. // 对于子树:先访问根结点,再访问左子树,然后访问右子树。
  57. // 访问根结点后,用栈把右子树保存,访问完左子树后再访问右子树。因为对于左子树也这这个过程,所以前面将右子树入栈时,把左子树也入栈,然后开始下一个循环。
  58. void travalFirst_flat(node *root)
  59. {
  60.     stack<node *> st;
  61.     node *nd, *ln, *rn;

  62.     if(root == NULL)
  63.             return;
  64.     st.push(root);
  65.     while(!st.empty())
  66.     {
  67.         nd = st.top();
  68.         st.pop();
  69.         cout<<nd->value<<endl;

  70.         // due to stack's FILO character, "push right child before right child" makes "pop left child before right child"
  71.         if(nd->hasRightChild())
  72.                 st.push(nd->rchild);
  73.         if(nd->hasLeftChild())
  74.                 st.push(nd->lchild);
  75.     }
  76. }

  77. // 中序_非递归实现
  78. // 先访问左子树,再访问根结点,最后是右子树。
  79. // 对于左子树也是这个过程。所以对于子树,先访问的是最左结点,从根到最左结点之间的所有结点都入栈。
  80. // 访问完最左结点,再访问其父结点,最后是右子树。怎样访问右子树呢?这是递归过程,非递归化后当然是在下一次循环中处理。
  81. void travalmiddle_flat(node *root)
  82. {
  83.     stack<node *>st;
  84.     node *nd, *ld, *rd;
  85.     st.push(root);
  86.     
  87.         // push left edge
  88.         do {
  89.             nd = st.top();
  90.             cout<<"push left child for:"<<nd->value<<endl;
  91.             if(nd->hasLeftChild())
  92.                     st.push(nd->lchild);
  93.             else
  94.             {
  95.                 cout<<"no left child for:"<< nd->value<<endl;
  96.                 break;
  97.             }
  98.         }while(1);

  99.     while(!st.empty())
  100.     {
  101.         // pop and visit most left node
  102.         nd = st.top();
  103.         st.pop();
  104.         cout<<nd->value<<endl;
  105.         // push right child
  106.         if(nd->hasRightChild())
  107.         {
  108.             cout<<"push right child for:"<<nd->value<<endl;
  109.                 st.push(nd->rchild);
  110.         // push left edge
  111.         do {
  112.             nd = st.top();
  113.             cout<<"push left child for:"<<nd->value<<endl;
  114.             if(nd->hasLeftChild())
  115.                     st.push(nd->lchild);
  116.             else
  117.             {
  118.                 cout<<"no left child for:"<< nd->value<<endl;
  119.                 break;
  120.             }
  121.         }while(1);
  122.         }
  123.         else
  124.         {
  125.             cout<<"no right child for:"<< nd->value<<endl;
  126.         }
  127.     }
  128. }



递归和非递归实现
先序遍历和中序遍历过程:

traval first:
a
b
d
e
h
i
c
f
g


traval middle:
d
b
h
e
i
a
f
c
g


traval first_flat:
a
b
d
e
h
i
c
f
g


traval middle_flat:
push left child for:a
push left child for:b
push left child for:d
no left child for:d
d
no right child for:d
b
push right child for:b
push left child for:e
push left child for:h
no left child for:h
h
no right child for:h
e
push right child for:e
push left child for:i
no left child for:i
i
no right child for:i
a
push right child for:a
push left child for:c
push left child for:f
no left child for:f
f
no right child for:f
c
push right child for:c
push left child for:g
no left child for:g
g
no right child for:g


如果你动手实践以上代码,你会可能遇到2个问题:
1、包含哪些头文件
2、怎样编译
这2个示例的头文件和Makefile都是一样的(我在Cygwin上编译)

点击(此处)折叠或打开

  1. #include <iostream>
  2. #include <stack>

  3. using namespace std

点击(此处)折叠或打开

  1. srcs=main.cpp
  2. objs=$(srcs:.c=.o)
  3. CC=g++

  4. all: a test

  5. a: $(objs)
  6.     $(CC) -o $@ $^
  7. %.o: %.c
  8.     $(CC) -c $^ -o $@

  9. clean:
  10.     rm -f *.o
  11.     rm -f a

  12. test:
  13.     ./a

下面是非递归化需要使用的数据结构
std::stack::top - cppreference.com


REF:
http://blog.chinaunix.net/uid-29052194-id-3869354.html

Update:
2013-8-29
阅读(2310) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~