一.对于递归的简单理解:
递归在百度百科上的解释:递归做为一种算法在程序设计语言中广泛应用。是指函数/过程/子程序在运行过程中直接或间接调用自身而产生的重入现象。递归调用从上往下,递归返回从下往上。
从字面上递归就是重复的调用自身。这让我们想到程序结构有3种:
既然重复调用,这就对应于我们的循环结构,只不过这是一个封装了的循环体。循环结构中没循环一次,数据规模相对减小:
| 循环结构 | 递归调用 |
相同点: | 1.每循环一次处理的数据规模小。 | 2.数据规模同样减小,想到分治法没有 |
不同点: | 1.因为处理了一部分问题,所以规模缩小了 | 2.先把问题分割缩小,分别处理,合并得到答案。 |
本质上我个人认为他们是相同的,递归调用是循环结构的特例,他们都是为我们为了让计算机去重复的去做一些事情,当然重复到什么结束,这就要设置结束条件了。从这里引出了递归的2个关键问题:怎样缩小问题?什么时候结束?也就是常说的递归方程和边界条件了。我们常见的一些复杂的结构都可以递归的定义:
如线性表,可以理解为2个线性表合并而成的,如下图所示:
同样广义表,树,都可以从递归的角度来定义。
二.递归算法的2种形式:
递归算法一般包括:最小问题(结束条件),处理问题(本次执行应该处理的问题),无解子问题(被剪枝的子问题),有解子问题(可以继续分解的子问题)
有以下2种形式,第一种形式是遇到最小子问题时需要做一些处理,第二种形式是遇到最小子问题时不做处理。
形式1:
If(最小问题)
处理;
else
{
处理问题。。。;
If(有解子问题)继续划分为更小的子问题,调用自身;
}
形式2:
If(!最小问题)
{
处理问题。。。;
If(有解子问题)继续划分为更小的子问题,调用自身;
}
三.递归算法转化为非递归:
(一) 直接转换法:
单向递归:是指递归算法中各递归调用语句的参数之间没有关系,并且这些递归调用语句都处在递归算法的最后。程序中的递归语句,在本程序操作执行前,都已经完成。尾递归是单调递归的特殊形式。他们的共同特点是在化为非递归时都没有非要保存的分支路线。
对于这类问题,我们可以采用自底向上的递推。分治法和动态规划都属于此类。比较经典的列子就是求斐波那契列数列。
其递归算法如下:
int f (int n)
{
If(n==1||n==0)return 1;
Elsereturn f(n-1)+f(n-2);
}
转为为非递归的算法如下:
Int f(int n)
{
Int S=S1=S2=1
If(n==1||n==0) return S;
Else
For(int i=1;i<=n;i++)
{
S=S1+S2;
S1=S2;
S2=S;
}
Return S;
}
还有许多可以采用动态规划的算法,我们一般直接采用非递归的方式实现,当然也可以递归(备忘录方法)。
(二) 间接转换法:
间接转换法,一般是需栈要保存某些状态。因为在递归从上往下的调用过程中要么是多个调用之间相互有关联我们需要在一次调用返回之后,另外的调用可以用到原本的信息(而不是经过第一次调用改变后的信息);另外一种原因是递归从上往下的调用过程中是一种从上往下的递推,每一次过程都产生了一些变量,我们需要保存这些中间的变量。本质上这两种原因都是相同的,想一想递归的实现,我们模拟系统实现递归。回顾一下系统调用的具体流程:1.调用前(保存现场)把工作记录(值参,局部变量,调用后的返回地址)压栈。2.调用后(恢复现场),把栈顶元素弹出,分别给响应参数赋值转向返回地址所给定的位置继续执行。
因此对于间接转换,把递归转化为非递归,需要明智的去判断那些信息需要保留压栈,而那些信息不需要保留压栈(当然保留的信息越多,程序可能越好理解,越好写)。
下面以二叉树的后序遍历为例。
//递归实现//
template<class T>
voidBitree<T>::Postordertraverse(const Binode*h)const
{
if(h!=0)
{
Postordertraverse(h->left);
Postordertraverse(h->right);
cout<<h->data<<"";
}
}
//非递归实现1//
template<class T>
voidBitree<T>::PostordertraverseA()const
{
stack<Binode*>NS;
stack<bool>BS;
Binode*h=head;
while(h!=0||!NS.empty())
{
while(h!=0)NS.push(h),BS.push(false),h=h->left;
if(BS.top())
{
cout<<NS.top()->data<<"";
NS.pop();
BS.pop();
}
else
{
BS.top()=true;
h=NS.top()->right;
}
}
}
//非递归实现2//
template<class T>
voidBitree<T>::PostordertraverseB()const
{
stack<STNode>m_st;
Binode*h=head;
STNodem_STcurNode;//当前处理节点
//初始化,存根节点
m_st.push(STNode(1,h->data,NULL));//存根节点数据
m_st.push(STNode(0,0,h->right));//存右子树
m_st.push(STNode(0,0,h->left));//存左子树
while(!m_st.empty())
{
m_STcurNode=m_st.top();
m_st.pop();
if(m_STcurNode.m_bflag==1)//是数据就输出
{
cout<<m_STcurNode.m_idata<<"";
}
else
{//是可以扩展的节点
if(m_STcurNode.m_pstNode!=NULL)
{
m_st.push(STNode(1,m_STcurNode.m_pstNode->data,NULL));
m_st.push(STNode(0,0,m_STcurNode.m_pstNode->right));
m_st.push(STNode(0,0,m_STcurNode.m_pstNode->left));
}
}
}
}
可以看出在第二种递归方式,栈保存的信息比较复杂,不过应该更好理解。注意在递归算法过程Postordertraverse中,先左子树Postordertraverse(h->left);,后右子树Postordertraverse(h->right);,最后输出data。而在非递归方式PostordertraverseB(),实现却是恰恰相反,我们需要先保存data,然后保存右子树信息,最后保存左子树信息。
参考文献:朱振元 递归算法的非递归化实现 [J] 小型微型计算机系统
BY RH
2012-5-2517:05