一、递归函数的原理
一个直接调用自己或者通过一系列的调用语句间接调用自己的函数,称做递归函数。
原理:系统用栈保存未完成的工作,在适当的时候从栈中取出并执行。 系统保存了工作的数据和状态,数据就是函数的局部变量,状态就是程序指针。
有很多递归的问题,许多递归问题有类似的模式。一种好的判断是否可以用递归解决问题的线索是看这个问题能否分解为子问题。当你听见一个问题以下面句子开头的话,它经常(并不是全部)可以用递归解决。例如:“设计一个算法来计算第n个。。。”,“写一段代码列出前n个。。”,“实现一个方法计算全部的。。。”等。
使用递归的一般方法:
1、先考虑一下这个问题依赖几个子问题。比如说二叉树类的问题,可能是依赖两个子问题。
2、解决基本问题。如f(0),f(1)。
3、解决f(2)
4、思考怎么用f(2)推导出f(3)。这就是理解经过什么处理能够用f(2)解决f(3)
5、推导出f(n)的推导公式。
注意的问题:
1、所有可以用递归实现的问题,都可以用迭代实现(虽然代码会变得很复杂)。在你准备用递归实现问题的时候,先问问自己,如果用迭代实现问题会有多复杂。权衡一下。
2、递归在空间利用率方面非常低。每一层递归都在栈中添加一层,这就意味着,如果你用时间复杂度为O(n)的算法,空间复杂度也可能是O(n)。这是很浪费的。
体会:很多递归的问题都有一个类似树形(或者图形,一层一层,下层是上层的子问题或者父问题)的状态结构,这是一个解题的突破口。
二、非递归程序原理
1. 和递归函数的原理相同,只不过是把由系统负责保存工作信息变为程序自己保存,这样能减少保存数据的冗余(主要是节省了局部变量的空间),提高存储效率。
2. 把程序要完成的工作分成两类:手头工作和保存在栈中的待完成的工作。手头工作指程序正在做的工作。由于某些工作不能一步完成,必须暂缓完成,于是可把它保存在栈中,这就是待完成的工作。
3. 手头工作必须有其结束条件,不能永远做下去;保存的待完成工作必须含有完成该项工作的所有必要信息。
4. 程序必须有秩序地完成各项工作。如,可把手头工作恰当处理(直接处理或暂时保存)后,才能继续接手下一步的工作。
5. 待完成工作必须转换成手头工作才能处理。
三、汉诺塔
递归实现
#include <iostream>
#include <iomanip>
using namespace std;
void move(int *a,int *b)
{
int temp;
int i=0;
while(a[i++]!=0);
i=i-2;
temp = a[i];
a[i]=0;
i=0;
while(b[i++]!=0);
i=i-1;
b[i]=temp;
}
void hanoi(int n,int* x,int* y, int * z)
{
if(n==0)
return;
else
{
hanoi(n-1,x,z,y); //把n-1个盘子从x移到y上
move(x,z); //然后把x最上面的盘子从x移到z上
hanoi(n-1,y,x,z); //最后把n-1个盘子从y移到z上;
}
}
int main()
{
int a[10]={4,3,2,1};
int b[10]={0};
int c[10]={0};
hanoi(4,a,b,c);
for(int i=0;i<4;++i)
{
printf("%d ",c[i]);
}
printf("\n");
return 0;
}
栈实现:
#include <stdio.h>
#define N 25
typedef struct HANOIDATA
{
int a[10];
int b[10];
int c[10];
int n;
}HANOIDATA;
void move(int *a,int *b)
{
int temp;
int i=0;
while(a[i++]!=0);
i=i-2;
temp = a[i];
a[i]=0;
i=0;
while(b[i++]!=0);
i=i-1;
b[i]=temp;
}
void swap(int *a,int *b)
{
int temp;
temp=*a;
*a=*b;
*b=temp;
}
void swaphan(int *a,int *b)
{
int i=0;
while(i<10)
{
swap(&(a[i]),&(b[i]));
i++;
}
}
void hanoi(HANOIDATA hanoiData)
{
HANOIDATA stack[N];
int top = -1; // stack pointer
while (hanoiData.n || top != -1) // 存在手头工作或待完成工作
{
while (hanoiData.n) // 处理手头工作直到无现成的手头工作,
// 即下次的手头工作必须从栈中取得
{
hanoiData.n --;
stack[++top] = hanoiData; // 保存待完成工作
swaphan(hanoiData.b,hanoiData.c); // 新的手头工作
}
if (top != -1) // 存在待完成工作
{
hanoiData = stack[top--]; // 从栈中取出
move(hanoiData.a, hanoiData.c); // 直接处理
swaphan(hanoiData.c,hanoiData.a);// 未处理完的转换成手头工作
swaphan(hanoiData.b,hanoiData.c);
// stack[++top]=hanoiData;
}
}
}
int main()
{
int a[10]={4,3,2,1};
int b[10]={0};
int c[10]={0};
HANOIDATA hanoiData;
for(int j=0;j<10;j++)
{
hanoiData.b[j]=0;
hanoiData.c[j]=0;
if(j<4)
{
hanoiData.a[j]=4-j;
}
else
hanoiData.a[j]=0;
}
hanoiData.n=4;
hanoi(hanoiData);
int i;
for(i=0;i<4;++i)
{
printf("%d ",hanoiData.a[i]);
}
for( i=0;i<4;++i)
{
printf("%d ",hanoiData.b[i]);
}
for(i=0;i<4;++i)
{
printf("%d ",hanoiData.c[i]);
}
printf("\n");
return 0;
}
上面代码是为了实现非递归而写的,一些处理方式可能不是是很好,主要是理解非递归实现。
四、后序遍历
递归实现:
void PostTraverse(BINARYTREE root)
{
if (root == NULL) return;
PostTraverse(root->LChild);
PostTraverse(root->RChild);
Visit(root);
}
非递归实现:
void PostTraverse(BINARYTREE p)
{
while ( p != NULL || !Stack.IsEmpty() )// 存储工作(手头或待完成)
{
while (p != NULL) // 处理手头工作,直到无现成手头工作
{
Stack.Push(p, RCHILD_AND_ITSELF);
p = p->LChild;
}
if (!Stack.IsEmpty()) // 是否存在待完成工作
{
Stack.Pop(p, Tag);
if (Tag == RCHILD_AND_ITSELF) // 情况一: RChild & Itself
{
Stack.Push(p, ONLY_ITSELF) // 保存待完成工作
p = p->RChild; // 新的手头工作
}
else // tag == ONLY_ITSELF, 情况二: Only Itself
{
visit(p);
p = NULL; // 已无现成的手头工作
}
}
}
}
熟能生巧!多加联系。
参考文章:http://www.cnblogs.com/etata/archive/2008/05/20/1203529.html