递归算法
释义:在调用一个函数的过程中又出现直接或间接的调用该函数本身,成为函数的递归(recursive)调用。
直接递归:
间接递归:
这两种递归调用,在形式上都是无终止的自身调用(实际解决问题时,可通过设置判断条件来跳出递归循环)
递归求解要点:
1.方法:
.列出递归方程/思路
.写出递归函数
.调用递归函数
2.举例:
数学归纳法
证明一个与自然数n有关的命题P(n),有如下步骤:
1.证明当n取第一个值a时命题成立(a对于一般数列取值为0或1)
2.假设当n=k(k>=a,k为自然数)时命题成立,证明当n=k+1时命题也成立。
例:对于求n的阶乘
可分为以上两种思路:阶乘和递归
代码示例:
#include <stdio.h>
#include <stdlib.h>
/*这个程序用来测试通过递归计算n的阶乘*/
long fact(int);//因为结束时返回值可能会十分巨大,所以使用long作为返回值数据类型
int main()
{
int n;
printf("输入需要求阶乘的n:");
scanf("%d",&n);
printf("%d的阶乘是:%ld",n,fact(n));
return 0;
}
long fact(int n)
{
long f;
if(n==1)//通过判断n的值决定是否递归,避免无限循环
f=1;
else
f=n*fact(n-1);//调用自身,形成递归
return f;
}
结果:
解析:
程序中用if语句来控制,只有在某一条件成立时才继续执行递归调用,否则就不再继续,这样,不会出现无终止的递归调用。
递归技巧
代码示例1:
#include <stdio.h>
#include <stdlib.h>
/*这个程序用来测试递归的技巧*/
/*输入一个整数m,要求输出其反序数以及正序数*/
void f1(int);//输出反序数
void f2(int);//输出正序数
int main()
{
int m;
printf("输入m:");
scanf("%d",&m);
f1(m);
printf("\n");
f2(m);
printf("\n");
return 0;
}
void f1(int m)
{
if(m>0)
{
/*先输出,后递归*/
printf("%d",m%10);
f1(m/10);
}
}
void f2(int m)
{
if(m>0)
{
/*先递归,后输出*/
f2(m/10);
printf("%d",m%10);
}
}
结果:
解析:
1.如上,改变递归的位置,得到的结果将是完全相反的。深刻理解这个例子的原理,即可理解递归的规律。
代码示例2:
#include <stdio.h>
#include <stdlib.h>
/*用递归实现二分查找*/
int r_search(int[],int,int,int);
int main()
{
int arr[]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
int low=0,high=14,k;
do{
printf("输入希望查找的数,输入-1结束:\n");
scanf("%d",&k);
if(k==-1)
break;
int i=r_search(arr,low,high,k);
if(i>=0)
printf("在数组的第%d位找到该数,该数为:%d!\n",i,arr[i]);
else
printf("未找到该数!\n");
}while(1);
return 0;
}
int r_search(int arr[],int low,int high,int k)
{
int i,mid;
if(low>high)
i=-1;//如果已经例遍了整个数组
else
{
mid=(low+high)/2;
if(arr[mid]==k)
i=mid;//如果中间值等于期望值
else if(arr[mid]>k)
i=r_search(arr,low,mid-1,k);//如果中间值大于期望值
else
i=r_search(arr,mid+1,high,k);//如果中间值小于期望值
}
return i;
}
结果:
解析:
递归可以取代循环完成很多功能,而递归的代码往往更加简洁。
代码示例3:
#include <stdio.h>
#include <stdlib.h>
/*这个程序用递归思想来解决汉诺塔问题*/
/*
汉诺塔问题
有三根相邻的柱子,
标号为A,B,C,
A柱子上从下到上按金字塔状叠放着n个不同大小的圆盘,
要把所有盘子一个一个移动到柱子B上,
并且每次移动同一根柱子上都不能出现大盘子在小盘子上方,
请问至少需要多少次移动?
*/
static long count=0;//计数变量
void move(int,char,char,char);//核心函数
int main()
{
char a='A',b='B',c='C';//三根柱子
printf("输入汉诺塔层数:");
int n;
scanf("%d",&n);
move(n,a,b,c);
printf("\n总共使用了%ld步",count);
return 0;
}
/*注意!move函数中用到的所有a,b,c都是形参!在递归过程中,a代表的可能是字符A也可能是字符B、C!b和c也同理*/
/*如要理解这个函数,可通过单步调试,设置n为4以内的较小的数进行测试*/
void move(int n,char a, char b,char c)
{
if(n==1)
{
count++;
printf("%c-->%c\n",a,c);//如果现在的问题时把一个盘子从a移动到c,那么直接做就行了。不需要进一步分化问题
return;
}
move(n-1,a,c,b);//要把n个盘子经过b从a移动到c,首先要把最底下最大的那个盘子之上的n-1个盘子经过c从a移动到b。
count++;
printf("%c-->%c\n",a,c);//现在a上只有一个盘子了,直接将他移动到c即可
move(n-1,b,a,c);//现在情况是,a上没有盘子,且要把剩下的盘子从b移动到c。(类比b上没有盘子,要把盘子从a移动到c。可以进入下一次递归)
}
结果:
解析:
1.汉诺塔问题是递归的经典应用,通过这个问题也能更加深刻的理解递归。但递归并不是解决这个问题的最佳方案。
2.如果要输出每一步移动的轨迹,应当使用递归。但如果只要输出移动所需步数,则应当使用迭代!
以下是使用迭代计算汉诺塔、麦子问题的代码示例,与本篇主旨无关。
代码示例:
#include <stdio.h>
#include <stdlib.h>
/*
麦子问题
舍罕王打算奖赏国际象棋的发明人──宰相西萨·班·达依尔。
国王问他想要什么,他对国王说:“陛下,请您在这张棋盘的第1个小格里赏给我一粒麦子,
在第2个小格里给2粒,第3个小格给4粒,以后每一小格都比前一小格加一倍。
请您把这样摆满棋盘上所有64格的麦粒,都赏给您的仆人吧!”
国王觉得这个要求太容易满足了,就命令给他这些麦粒。
当人们把一袋一袋的麦子搬来开始计数时,国王才发现:
就是把全印度甚至全世界的麦粒全拿来,也满足不了那位宰相的要求。
请计算共需要多少麦子才能满足其要求。
*/
/*麦子问题和汉诺塔问题是相似的。用这个程序同样可以计算n层汉诺塔的移动总需步数*/
/*比如输入5,便是5层汉诺塔总共所需移动步数*/
int main()
{
/*难点在于如何表达一个这么大的数,C语言中long long也不足以做到这一点,但unsigned long long 却刚刚好能做到*/
unsigned long long totol=0,now=1;//这几乎是C语言能表达数字最大的基础数据类型了。
int n;
scanf("%d",&n);
while(n--)
{
totol+=now;
now*=2;
}
printf("%llu",totol);//输出的格式字符串也和一般情况下有所不同
return 0;
}
结果:
解析:
1.如上,如果用递归来算64层汉诺塔或者64格棋盘的话,需要几天几夜才可能得到结果。而用迭代只需要1秒多一点。
2.unsigned long long 的输出格式字符串是%llu或%I64u(后者的I是大写的i,且后者原用于输出_int64型数据,如今也可用于输出unsigned long long),long long的输出格式字符串是%lld。