找零递归算法
‘壹’ 什么是递归算法
递归做为一种算法在程序设计语言中广泛应用.是指函数/过程/子程序在运行过程序中直接或间接调用自身而产生的重入现像.
程序调用自身的编程技巧称为递归( recursion)。
一个过程或函数在其定义或说明中又直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。用递归思想写出的程序往往十分简洁易懂。
一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
注意:
(1) 递归就是在过程或函数里调用自身;
(2) 在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口,否则将无限进行下去(死锁)。
递归算法一般用于解决三类问题:
(1)数据的定义是按递归定义的。(Fibonacci函数)
(2)问题解法按递归算法实现。(回溯)
(3)数据的结构形式是按递归定义的。(树的遍历,图的搜索)
递归的缺点:
递归算法解题的运行效率较低。在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。
‘贰’ 递归算法举例
int intSum(int n)
{
if(20 == n) //当等于20的时候返回20
{
return n;
}
return n + intSum(n+1); //递归
}
我没验证这个,但是基本就是这样的,你可以试试
‘叁’ C语言,贪心算法,货币找零问题
贪心算法找零就是现实中从最大面额开始找的思路。不代表是最优解,只是算法之一。
由于面额输入顺序不定,我先对输入的面额进行降序排序。
下面代码:
#include <stdio.h>
#include <malloc.h>
int main()
{
int i,j,m,n,*ns=NULL,*cn=NULL,sum=0;
printf("请输入总金额m及零钱种类n:"),scanf("%d",&m),scanf("%d",&n);
printf("请分别输入%d种零钱的面额: ",n);
if(!(ns=(int *)malloc(sizeof(int)*n))) return 1;
if(!(cn=(int *)malloc(sizeof(int)*n))) return 1;
for(i=0;i<n;i++) scanf("%d",&ns[i]);
//------------考虑输入面额顺序不定,先对面额进行降序排列(如按照降序输入,该段可删除)
for(i=0;i<n;i++)
for(j=i+1;j<n;j++)
if(ns[j]>ns[i]) ns[j]^=ns[i],ns[i]^=ns[j],ns[j]^=ns[i];
//-------------------------------------------------------------------
for(i=0;i<n;i++)//贪心算法,从最大面额开始
if(m>=ns[i])
cn[i]=m/ns[i],m=m%ns[i],sum+=cn[i],printf("%d元%d张 ",ns[i],cn[i]);
printf(" 最少使用零钱%d张 ",sum);
return 0;
}
‘肆’ C语言递归算法
本人学c++,c的语法已经淡忘了,但是递归不管什么语言都是一个原理
其实简单一点来说就像数学里面的数列的通项公式:
例如一个数列是2,4,6,8,10......
很容易就可以得到通项公式是a[n]=2*n n是大于0的整数
你肯定学过这个数列的另外一种表示方式就是: a[1]=2, a[n]=a[n-1]+2 n是大于1的整数
其实这就是一个递归的形式,只要你知道初始项的值,未知项和前几项之间的关系就可以知道整个数列。
程序例子:比如你要得到第x项的值
普通循环:
for(int i=1; i<=n; i++)
if (i == x)
cout << 2*i; /*cout 相当于 c里面的printf,就是输出.*/
递归:
int a(int x) {
if (x = 1)
return 2; /* 第一项那肯定是2了,这个也是递归的终止条件! */
else return a(x-1)+2; /* 函数自身调用自身是递归的一个特色 */
比如x=4,那么用数学表示就是a(4)=a(3)+2=(a(2)+2)+2=((a(1)+2)+2)+2
其实递归方法最接近自然,也是最好思考的一个方法,难点就是把对象建模成递归形式,但是好多问题本身就是以递归形式出现的。
普通递归就是数据结构上的堆栈,先进后出。
例如上面x=4,把a(4)放入栈底,然后放入a(3),然后a(2),a(1),a(1)的值已知,出栈,a(1)=2,a(2)出栈a(2)=a(1)+2=2+2=4,a(3)出栈a(3)=a(2)+2=(a(1)+2)+2=6,a(4)出栈a(4)=a(3)+2=(a(2)+2)+2=((a(1)+2)+2)+2=8
再比如楼上的阶乘例子,当n=0 或 1时,0!=1,1!=1,这个是阶乘的初始值,也是递归的终止条件。然后我们知道n!=n*(n-1)!,当n>1时,这样我们又有了递归形式,又可以以递归算法设计程序了。(楼上已给出谭老的程序,我就不写了)。
我给出一种优化的递归算法---尾递归。
从我给出的第一算法可以看出,先进栈再出栈,递归的效率是很低的。速度上完全比不上迭代(循环)。但是尾递归引入了一个新的函数参数,用这个新的函数参数来记录中间值.
普通递归阶乘fac(x),就1个x而已,尾递归用2个参数fac(x,y),y存放阶乘值。
所以谭老的程序就变成
// zysable's tail recursive algorithm of factorial.
int fac(int x, int y) {
if (x == 1)
return y;
else return fac(x-1, y*x);}
int ff(int x) {
if (x == 0)
return 1;
else return fac(x,1);}
对于这个程序我们先看函数ff,函数ff其实是对fac的一个封装函数,纯粹是为了输入方便设计的,通过调用ff(x)来调用fac(x,1),这里常数1就是当x=1的时候阶乘值了,我通过走一遍当x=3时的值即为3!来说明一下。
首先ff(3),x!=0,执行fac(3,1).第一次调用fac,x=3,y=1,x!=1,调用fac(x-1,y*x),新的x=2,y=3*1=3,这里可以看到,y已经累计了一次阶乘值了,然后x还是!=1,继续第三次调用fac(x-1,y*x),新的x=1,y=2*3=6,然后x=1了,返回y的值是6,也就是3!.你会发现这个递归更类似于迭代了。事实上我们用了y记录了普通递归时候,出栈的乘积,所以减少了出栈后的步骤,而且现在世界上很多程序员都在倡议用尾递归取消循环,因为有些在很多解释器上尾递归比迭代稍微效率一点.
基本所有普通递归的问题都可以用尾递归来解决。
一个问题以递归来解决重要的是你能抽象出问题的递归公式,只要递归公式有了,你就可以放心大胆的在程序中使用,另外一个重点就是递归的终止条件;
其实这个终止条件也是包含在递归公式里面的,就是初始值的定义。英文叫define initial value. 用普通递归的时候不要刻意让自己去人工追踪程序,查看运行过程,有些时候你会发现你越看越不明白,只要递归公式转化成程序语言正确了,结果必然是正确的。学递归的初学者总是想用追踪程序运行来让自己来了解递归,结果越弄越糊涂。
如果想很清楚的了解递归,有种计算机语言叫scheme,完全递归的语言,因为没有循环语句和赋值语句。但是国内人知道的很少,大部分知道是的lisp。
好了,就给你说到这里了,希望你能学好递归。
PS:递归不要滥用,否则程序极其无效率,要用也用尾递归。by 一名在美国的中国程序员zysable。
‘伍’ 哪位编程高手能讲讲“递归算法”最好多举几个实例。
递归很简单,但许多人理解不了,其实就是自已调用自己,
首先你要把算法描述成递归,如阶乘 : n!=n*(n-1)!
就是递归了,要计算 n!就是要计算 n与(n-1)!的乘积,
这(n-1)!就是又调用自已了。递归也要有结束递归的情况,
不能无限制的递归,否则,栈溢出了;
线性递归的效率很低,可以改成循环迭代;
‘陆’ 怎们理解递归算法
先不要往里想,越想越乱,先想好递归结束(最终返回)的条件,然后通过调用自己每次都将问题简化,这样说问题可能比较抽象,你看看数据结构书中关于树的部分,那里递归比较多,而且很多递归都不难,比如前序 中序 后序遍历,找些课本上的程序,用一些简单的树为例子一步步走一下,相信你会更清晰的
‘柒’ 什么是递归算法
递归算法就是一个函数通过不断对自己的调用而求得最终结果的一种思维巧妙但是开销很大的算法。
比如:
汉诺塔的递归算法:
void move(char x,char y){
printf("%c-->%c\n",x,y);
}
void hanoi(int n,char one,char two,char three){
/*将n个盘从one座借助two座,移到three座*/
if(n==1) move(one,three);
else{
hanoi(n-1,one,three,two);
move(one,three);
hanoi(n-1,two,one,three);
}
}
main(){
int n;
printf("input the number of diskes:");
scanf("%d",&n);
printf("The step to moving %3d diskes:\n",n);
hanoi(n,'A','B','C');
}
我说下递归的理解方法
首先:对于递归这一类函数,你不要纠结于他是干什么的,只要知道他的一个模糊功能是什么就行,等于把他想象成一个能实现某项功能的黑盒子,而不去管它的内部操作先,好,我们来看下汉诺塔是怎么样解决的
首先按我上面说的把递归函数想象成某个功能的黑盒子,void hanoi(int n,char one,char two,char three); 这个递归函数的功能是:能将n个由小到大放置的小长方形从one 位置,经过two位置 移动到three位置。那么你的主程序要解决的问题是要将m个的"汉诺块"由A借助B移动到C,根据我们上面说的汉诺塔的功能,我相信傻子也知道在主函数中写道:hanoi(m,A,B,C)就能实现将m个块由A借助B码放到C,对吧?所以,mian函数里面有hanoi(m,'A','C','B');这个调用。
接下来我们看看要实现hannoi的这个功能,hannoi函数应该干些什么?
在hannoi函数里有这么三行
hanoi(n-1,one,three,two);
move(one,three);
hanoi(n-1,two,one,three);
同样以黑盒子的思想看待他,要想把n个块由A经过B搬到C去,是不是可以分为上面三步呢?
这三部是:第一步将除了最后最长的那一块以外的n-1块由one位置经由three搬到two 也就是从A由C搬到B 然后把最下面最长那一块用move函数把他从A直接搬到C 完事后 第三步再次将刚刚的n-1块借助hannoi函数的功能从B由A搬回到C 这样的三步实习了n块由A经过B到C这样一个功能,同样你不用纠结于hanoi函数到底如何实现这个功能的,只要知道他有这么一个神奇的功能就行
最后:递归都有收尾的时候对吧,收尾就是当只有一块的时候汉诺塔怎么个玩法呢?很简单吧,直接把那一块有Amove到C我们就完成了,所以hanoni这个函数最后还要加上 if(n==1)move(one,three);(当只有一块时,直接有Amove到C位置就行)这么一个条件就能实现hanoin函数n>=1时将n个块由A经由B搬到C的完整功能了。
递归这个复杂的思想就是这样简单解决的,呵呵 不知道你看懂没?纯手打,希望能帮你理解递归
总结起来就是不要管递归的具体实现细节步骤,只要知道他的功能是什么,然后利用他自己的功能通过调用他自己去解决自己的功能(好绕口啊,日)最后加上一个极限情况的条件即可,比如上面说的1个的情况。
‘捌’ 什么是递归算法有什么作用
作者名:不详 来源:网友提供 05年7月7日
无法贴图 ,自己到 http://51zk.csai.cn/sjjg/NO00223.htm 看去吧
1、调用子程序的含义:
在过程和函数的学习中,我们知道调用子程序的一般形式是:主程序调用子程序A,子程序A调用子程序B,如图如示,这个过程实际上是:
@当主程序执行到调用子程序A语句时,系统保存一些必要的现场数据,然后执行类似于BASIC语言的GOTO语句,跳转到子程序A(为了说得简单些,我这里忽略了参数传递这个过程)。
@当子程序A执行到调用子程序B语句时,系统作法如上,跳转到子程序B。
@子程序B执行完所有语句后,跳转回子程序A调用子程序B语句的下一条语句(我这又忽略了返回值处理)
@子程序A执行完后,跳转回主程序调用子程序A语句的下一条语句
@主程序执行到结束。
做个比较:我在吃饭(执行主程序)吃到一半时,某人叫我(执行子程序A),话正说到一半,电话又响了起来(执行子程序B),我只要先接完电话,再和某人把话说完,最后把饭吃完(我这饭吃得也够累的了J)。
2、认识递归函数
我们在高中时都学过数学归纳法,例:
求 n!
我们可以把n!这么定义
也就是说要求3!,我们必须先求出2!,要求2!,必须先求1!,要求1!,就必须先求0!,而0!=1,所以1!=0!*1=1,再进而求2!,3!。分别用函数表示,则如图:
我们可以观察到,除计算0!子程序外,其他的子程序基本相似,我们可以设计这么一个子程序:
int factorial(int i){
int res;
res=factorial(I-1)*i;
return res;
}
那么当执行主程序语句s=factorial(3)时,就会执行factorial(3),但在执行factorial(3),又会调用factorial(2),这时大家要注意,factorial(3)和factorial(2)虽然是同一个代码段,但在内存中它的数据区是两份!而执行factorial(2)时又会调用factorial(1),执行factorial(1)时又会调用factorial(0),每调用一次factorial函数,它就会在内存中新增一个数据区,那么这些复制了多份的函数大家可以把它看成是多个不同名的函数来理解;
但我们这个函数有点问题,在执行factorial(0)时,它又会调用factorial(-1)。。。造成死循环,也就是说,在factorial函数中,我们要在适当的时候保证不再调用该函数,也就是不执行res=factorial(I-1)*i;这条调用语句。所以函数要改成:
int factorial(int i){
int res;
if (I>0) res=factorial(I-1)*i; else res=1;
return res;
}
那么求3!的实际执行过程如图所示:
3、如何考虑用递归的方法来解决问题
例:求s=1+2+3+4+5+6+……+n
本来这个问题我们过去常用循环累加的方法。而这里如要用递归的方法,必须考虑两点:
1) 能否把问题转化成递归形式的描述;
2) 是否有递归结束的边界条件。
设:函数s(n)=1+2+3+…+(n-1)+n
显然递归的两个条件都有了:
1) s(n) =s(n-1)+n
2) s(1)=1
所以源程序为:
int progression(int n){
int res;
if (n=1 )res=1 else res=progression(n-1)+n;
return res;
}
4、递归的应用
中序遍历二叉树
void inorder (BinTree T){
if (T){
inorder(T->lchild);
printf(“%c”,T->data);
inorder(T->rchild);
}
}
现假设树如图(为了讲解方便,树很简单)
@执行第一次调用inorder1,T指向顶结点,T不为空,所以第二次调用inorder2;
@T指向顶结点的左子树结点也就是B,不为空,所以第三次调用inorder3;
@T指向B结点的左子树结点,为空,所以什么都不执行,返回inorder2;
@打印B结点的DATA域值“b”;
@第四次调用inorder4,去访问B子树的右结点
@T指向B结点的右子树结点,为空,所以什么都不执行,返回inorder2;
@返回inorder1;
@打印A结点的DATA域值“a”;
@第五次调用inorder5,去访问A子树的右结点;
@T指向A结点的右子树结点,为空,所以什么都不执行,返回inorder1;
@inorder1执行完毕,返回。
‘玖’ 递归主方法
递归的主要方法是什么?
一、递归算法
递归算法(英语:recursion algorithm)在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法。递归式方法可以被用于解决很多的计算机科学问题,因此它是计算机科学中十分重要的一个概念。绝大多数编程语言支持函数的自调用,在这些语言中函数可以通过调用自身来进行递归。计算理论可以证明递归的作用可以完全取代循环,因此在很多函数编程语言(如Scheme)中习惯用递归来实现循环。
二、递归程序
在支持自调的编程语言中,递归可以通过简单的函数调用来完成,如计算阶乘的程序在数学上可以定义为:
这一程序在Scheme语言中可以写作:
1
(define (factorial n) (if (= n 0) 1 (* n (factorial (- n 1)))))
不动点组合子
即使一个编程语言不支持自调用,如果在这语言中函数是第一类对象(即可以在运行期创建并作为变量处理),递归可以通过不动点组合子(英语:Fixed-point combinator)来产生。以下Scheme程序没有用到自调用,但是利用了一个叫做Z 算子(英语:Z combinator)的不动点组合子,因此同样能达到递归的目的。
1
(define Z (lambda (f) ((lambda (recur) (f (lambda arg (apply (recur recur) arg)))) (lambda (recur) (f (lambda arg (apply (recur recur) arg)))))))(define fact (Z (lambda (f) (lambda (n) (if (<= n 0) 1 (* n (f (- n 1))))))))
这一程序思路是,既然在这里函数不能调用其自身,我们可以用 Z 组合子应用(application)这个函数后得到的函数再应用需计算的参数。
尾部递归
尾部递归是指递归函数在调用自身后直接传回其值,而不对其再加运算。尾部递归与循环是等价的,而且在一些语言(如Scheme中)可以被优化为循环指令。 因此,在这些语言中尾部递归不会占用调用堆栈空间。以下Scheme程序同样计算一个数字的阶乘,但是使用尾部递归:
1
(define (factorial n) (define (iter proct counter) (if (> counter n) proct (iter (* counter proct) (+ counter 1)))) (iter 1 1))
三、能够解决的问题
数据的定义是按递归定义的。如Fibonacci函数。
问题解法按递归算法实现。如Hanoi问题。
数据的结构形式是按递归定义的。如二叉树、广义表等。
四、递归数据
数据类型可以通过递归来进行定义,比如一个简单的递归定义为自然数的定义:“一个自然数或等于0,或等于另一个自然数加上1”。Haskell中可以定义链表为:
1
data ListOfStrings = EmptyList | Cons String ListOfStrings
这一定义相当于宣告“一个链表或是空串行,或是一个链表之前加上一个字符串”。可以看出所有链表都可以通过这一递归定义来达到。