【万字总结】探讨递归与迭代的区别与联系及如何求解10000的阶层
来源:互联网 发布:三年自然灾害真相 知乎 编辑:程序博客网 时间:2024/05/22 00:53
递归和迭代
这两个概念也许很多童鞋依旧分不清楚,下面通过求解斐波那契数来看看它们俩的关系吧。
斐波那契数的定义:
递归:
(factorial 6)(* 6 (factorial 5))(* 6 (* 5 (factorial 4)))(* 6 (* 5 (* 4 (factorial 3))))(* 6 (* 5 (* 4 (* 3 (factorial 2)))))(* 6 (* 5 (* 4 (* 3 (2 (factorial 1))))))(* 6 (* 5 (* 4 (* 3 (* 2 1)))))(* 6 (* 5 (* 4 (* 3 2))))(* 6 (* 5 (* 4 6)))(* 6 (* 5 24))(* 6 120)720
迭代:
(factorial 6)(factorial 1 1 6)(factorial 1 2 6)(factorial 2 3 6)(factorial 6 4 6)(factorial 24 5 6)(factorial 120 6 6)(factorial 720 7 6)720
递归的核心在于:不断地回到起点。
迭代的核心在于:不断地更新参数。
在下面的代码中:
递归的核心是sum的运算,sum不断的累乘,虽然运算的数值不同,但形式和意义一样。
而迭代的核心是product和counter的不断更新。如上表中,product就是factorial的前2个参数不断的累乘更新成第一个参数;而第二个参数则是counter,其不断的加1来更新自己。
product <- counter * product counter < - counter + 1
#include <iostream>using namespace std;int factorialRecursive(int n);int factorialIteration(int product, int counter, int max_count);int main(){ int n; cout<<"Enter an integer:"<<endl; cin>>n; cout<<factorialRecursive(n)<<endl; cout<<factorialIteration(1,1,n)<<endl; return 0;}int factorialRecursive(int n){ int sum=1; if(n==1) sum*=1; else sum=n*factorialRecursive(n-1); return sum;}int factorialIteration(int product, int counter, int max_count){ int sum=1; if(counter>max_count) sum*=product; else factorialIteration((counter*product),(counter+1),max_count);}
补充问题:
关于上面的factorialIteration函数,今天收到一份邮件,我也通过再次分析学到了很多,这里罗列一下。
第一个问题:
首先来看相对简单的问题,该童鞋在函数内以两种不同方式加上another_sum=2却有着不同的结果。
int factorialIteration(int product, int counter, int max_count){ int sum=1; int another_sum=2; if(counter>max_count) { sum*=product; another_sum*=product; } else factorialIteration((counter*product),(counter+1),max_count);}
int factorialIteration(int product, int counter, int max_count){ int sum=1; int another_sum=2; if(counter>max_count) { another_sum*=product; sum*=product; } else factorialIteration((counter*product),(counter+1),max_count);}
因为这个函数声明的是int型的返回类型,但没有用return语句,所以C++自动将其运行的最后一行语句作为了返回语句。所以这两个函数类似于:
int factorialIteration(int product, int counter, int max_count){ int sum=1; int another_sum=2; if(counter>max_count) { sum*=product; return another_sum*=product; } else factorialIteration((counter*product),(counter+1),max_count);}int factorialIteration(int product, int counter, int max_count){ int sum=1; int another_sum=2; if(counter>max_count) { another_sum*=product; return sum*=product; } else factorialIteration((counter*product),(counter+1),max_count);}
然而我在CodeBlocks中写的代码不用return是可以的,但在Visual Studio中却是会报错的。
有了这个发现,我原来的代码也可以这样来写:
#include <iostream>using namespace std;int factorialRecursive(int n);int factorialIteration(int product, int counter, int max_count);int main(){ int n; cout<<"Enter an integer:"<<endl; cin>>n; cout<<factorialRecursive(n)<<endl; cout<<factorialIteration(1,1,n)<<endl; return 0;}int factorialRecursive(int n){ int sum=1; if(n==1) sum*=1; else sum=n*factorialRecursive(n-1); // return sum; // 去掉这里的return语句}int factorialIteration(int product, int counter, int max_count){ int sum=1; if(counter>max_count) return sum*=product; // 在这里加上return语句 else factorialIteration((counter*product),(counter+1),max_count);}
现在来看另一个问题:
#include <iostream>using namespace std;int test(int n);int sum;int main(){ cout<<test(1)<<endl; return 0;}int test(int n){ sum = 1; sum += n; if (sum < 5) test(n+1);}
如果设sum为全局变量,那么会在test函数中每一次调用sum=1时都将sum重新赋值为1。整个程序最后输出为5。这个应该没有什么悬念吧?
如果设sum给test内的局部变量,则会在每一次执行int sum=1语句时都会创建一个新的sum对象,它的存放地址和之前的sum并不相同。然后整个程序最后输出意外的是4。
#include <iostream>using namespace std;int test(int n);int main(){ cout<<test(1)<<endl; return 0;}int test(int n){ int sum = 1; sum += n; if (sum < 5) return test(n+1); // return sum; 此处有这一行代码命名为程序1,没有这行代码命名为程序2}
程序1的输出是5,程序2的输出是4。具体函数执行过程如下:
第一步,调用test(1):
int sum=1sum=2return test(2)
第二步,调用test(2):
int sum=1sum=3return test(3)
第三步,调用test(3):
int sum=1sum=4return test(4)
第四步,调用test(4):
int sum=1sum=5
执行到第四步的时候,由于sum以及不比5小了,所以程序1没有进入if语句而是执行下一句return sum,所以输出为1。
而如果是程序2,也就是没有return sum语句,那么程序在执行完第四步后就会返回到第三步,最终调用(return) sum=4,输出4。
第三个问题:
该童鞋还提到了尾递归,这里我就来说说我的理解,如有问题欢迎大家直接评论或邮件给我。
上面代码中的递归函数factorialRecursive应该没问题的吧。
上面的代码我给其命名为迭代。
int factorialIteration(int product, int counter, int max_count){ int sum=1; if(counter>max_count) sum*=product; else factorialIteration((counter*product),(counter+1),max_count);}
通过在main函数中调用如下代码来执行该函数:
cout<<factorialIteration(1,1,n)<<endl;
当然,也可以另外写一个函数如下:
int factorialIter(int n){ return factorialIteration(1,1,n);}
并通过在main函数中直接调用该函数来做计算:
cout<<factorialIter(n)<<endl;
函数factorialIteration中的max_count我们称其为“循环不变量”,也就是对于整个运算过程而言这个变量是不变的。为了让大家更加印象深刻,将前面出现过的东西再来复制一遍:
(factorial 6)(factorial 1 1 6)(factorial 1 2 6)(factorial 2 3 6)(factorial 6 4 6)(factorial 24 5 6)(factorial 120 6 6)(factorial 720 7 6)720
从第二行开始的factorial的第三个参数”6“就是循环不变量。
尾递归:
在计算机科学中,尾调用是一个作为过程最后一步的子例程调用执行。如果尾调用可能在以后的调用链中再调用这同一个子例程,那么这个子例程就被称为是尾递归,它是递归的一个特殊情况。尾递归非常有用,在实现中也容易处理。尾调用可以不通过在调用堆栈中添加新的栈帧而实现。
传统上,尾部调用消除是可选的。然而,在函数式编程语言中,尾调用消除往往由语言标准作为保障,这种保证允许使用递归,在特定情况下的尾递归,来代替循环。在这种情况下,尽管用它作为一种优化是不正确的(尽管它可能是习惯用法)。在尾递归中,当一个函数调用它自身这种特殊情况下,可能调用消除比传统的尾调用更加合适。
迭代:
迭代是一个重复过程,它的目的是接近既定的目标或结果。每次重复的过程也称为”迭代“,作为迭代的结果都将作为下一次迭代的起点。
迭代在计算中是指的计算机程序中的重复的语句块。它可以表示两个专业术语,同义重复,以及描述一种具有可变状态重复的具体形式。然后令人费解的是,它也可以表示通过显式重复结构的任何重复,而不管其可变性。
在第一个意义上,递归是迭代的一个例子,但通常用”递归“来标记,而不作为”迭代“的例子。
在第二个意义上,(更加狭义地)迭代描述了一种编程风格。这与一个有着更有声明性方法的递归有着鲜明的对比。
第三个意义上,使用while或for循环,以及使用map或fold的函数也被视为迭代。
(以上定义部分摘自英文维基百科)
关于递归和尾递归在函数式编程中的应用也可以看这里:【Scheme归纳】3 比较do, let, loop
下面我也列出了相关的Scheme语言的代码:
(define (factorial n) (if (= n 1) 1 (* n (factorial (- n 1)))))
(define (factorial n) (fact-iter 1 1 n))(define (fact-iter product counter max-count) (if (> counter max-count) product (fact-iter (* counter product) (+ counter 1) max-counter)))
以上分别是递归和迭代的阶层,下面是Common Lisp语言版的斐波那契数求法:
(defun fib (n) (fib-iter 1 0 n))(defun fib-iter (a b count) (if (= count 0) b (fib-iter (+ a b) a (- count 1))))
借助递归树求解递归式
前面我们已经看到了递归式,也看到了递归树,那么如何借助递归树来求解递归式呢?接下来就来看看吧。
在递归树中,每个结点都表示一个单一问题的代价,子问题对应某次递归函数调用。
通过对树中每层的代价进行求和,就可以得到每层的代价;然后将所有层的代价求和,就可以得要到所有层次的递归调用的总代价。
我们通常用递归树来得出一个较好的猜测结果,然后用代入法来证明猜测是否正确。但是通过递归树来得到结果时,不可避免的要忍受一些”不精确“,得在稍后才能验证猜测是否正确。
因为下面的示例图太难用键盘敲出来了,我就用了手写,希望大家不介意。
如下所示,有一个递归式,我们要借助它的递归树来求解最终的结果。前面所说的忍受“不精确”这里就有2点:
1)我们要关注的更应该是解的上界,因为我们知道舍入对求解递归式没有影响,因此可以将
2)我们还将
图a所示的是
这棵树有多高(深)呢?
我们发现对于深度为
有了深度还需要计算每一层的代价。其中每层的结点数都是上一层的3倍,因此深度为
因此对于
递归树的最底层深度为
至于这最后的
不知道大家看不看得清,上面的两行文字是“我们要证的是
霍纳规则
在看如何求解1000的阶层之前,我们不妨先看看一个简单点的:霍纳规则。当然,您也可以停顿下来先自己琢磨琢磨。
一、背景
霍纳(Horner)规则是采用最少的乘法运算策略,来求多项式
在x0处的值。
该规则为
二、分析
如果光看着式子或许会有点烦躁,不妨手动设定几个值到式子中去来手工运算一番,这样一来也会有些亲身的理解。
通过分解我们注意到,从右往左来看,每一个小式子都是如此:
三、代码
C语言版
#include <stdio.h>#include <stdlib.h>int hornerRule(int list[],int m,int x0);int main(){ int m,x0; printf("Enter an integer (length of list): \n"); scanf("%d",&m); int list[m]; printf("Enter some integers for list: \n"); int i; for(i=m-1;i>=0;i--) { scanf("%d",&list[i]); } printf("Enter an integer for x0: \n"); scanf("%d",&x0); printf("%d",hornerRule(list,m,x0)); return 0;}int hornerRule(int list[],int m,int x0){ if(m<=1) return list[0]; else return list[0]+(hornerRule(list+1,m-1,x0))*x0;}
C++语言版
#include <iostream>using namespace std;int hornerRule(int list[],int m,int x0);int main(){ int m,x0; cout<<"Enter an integer (length of list):"<<endl; cin>>m; int list[m]; cout<<"Enter some integers for list:"<<endl; for(int i=m-1;i>=0;i--) { cin>>list[i]; } cout<<"Enter an integer for x0:"<<endl; cin>>x0; cout<<hornerRule(list,m,x0); return 0;}int hornerRule(int list[],int m,int x0){ if(m<=1) return list[0]; else return list[0]+(hornerRule(list+1,m-1,x0))*x0;}
四、测试
五、进阶
(PS:博主有一段时间没有碰Scheme有点忘了,所以下面的代码可能有些……粗糙)
关于Scheme可以看这里:
专栏:SICP练习
专栏:Scheme归纳
(define (Horner list m x0) (define (Horner-iter ls n) (if (<= n 1) (car ls) (+ (car ls) (* (Horner-iter (cdr ls) (- n 1)) x0)))) (Horner-iter list m))(define list '(1 2 1 0 3 1));Value: list(Horner list 6 10);Value: 130121
如何求解10000的阶层
看到过一个蛮有意思的题,是问“100!”的尾数有多少个零。
尾数有多少个零,实际上指的是从这个数的最后一个不为0的数的下一个(也就是0)开始计数,一直到最后一个数(这些数自然都是0)有多少个0。
好吧,也就是说13330330000的尾数有4个零……
一个整数若含有因子5,则必然在求解100!时产生一个0,也就是说我们从5开始for循环,每次循环都给加上5,然后计数器加1。同时如果该整数还能被25整除,计数器还应该再加上1。(关于这段话的详细解释请看下文)
因此代码如下:
#include<stdio.h>int main(){ int a,count =0; for(a=5;a<=100;a+=5) { ++count; if(!(a%25)) ++count; } printf("100!的尾数有%d个零。\n",count); return 0;}
题目后面进一步问了如何求出任意N!的尾数有多少个零。
#include<stdio.h>int main(){ int n; printf("请输入N:\n"); scanf("%d",&n); if(n<0) printf("%d的阶层无意义。\n",n); else if(n<=4) printf("%d的阶层的尾数没有零。\n"); else { int a,count =0; for(a=5;a<=n;a+=5) { ++count; if(!(a%25)) ++count; } printf("100!的尾数有%d个零。\n",count); } return 0;}
本文就这样结束了吗?
题目的解答中有这么一段话:先求出100!的值,然后数一下末尾有多少个零。事实上,由于计算机所能表示的整数范围有限,这是不可能的。
首先,什么叫计算机所能表示的整数范围?应该叫int等数据类型的整数范围有限才对,计算机嘛……撑死了只能说不能存储而非不能表示。
另外100的阶层真的求不出来吗?请往下读。
我的博客中有大量关于Lisp,或者说Scheme的博文,使用这个语言,几行代码就能搞定了不是吗?欢迎阅读我的其他博文……
(define (fact n) (if (= n 1) 1 (* n (fact (- n 1)))));Value: fact
1000的阶层也能求,截图为证……
闲得无聊,以下是10000的阶层,大家可以继续算更大的数,哈哈……
………………
我发现这个CSDN博客写上这么多数字之后博客没法提交,有异常……没办法,只能上传了……下载后觉得有意思记得回来点赞哦……
传送门:10000的阶层
有网友私信问我,为什么一个整数若含有因子5,则必然在求解100!时产生一个0。这里所说的一个整数,自然是在求100的阶层时需要计算的从1到100这些整数。我下列出一些等式:
1x2=22x3=66x4=2424x5=120120x6=720720x7=50405040x8=4032040320x9=362880362880x10=36288003628800x11=399168003991680x12=47900160047900160x13=6227020800622702080x14=871782912008717829120x15=1307674368000…… ……
看到上式就会发现每次尾部增加0都是因为成了一个因子是5的整数。那么一直乘到100都会是这样吗?当然是。但这样就能证明?显然不能。
我们来看看各个整数的最后一个数:
如果是0的话,也就是说是乘以10或者20、30之类的,那么肯定会加上一个0。而且它也是5的倍数。
如果是1的话,无论乘以谁显然都不可能得到10。(这里的谁是指的的上面那些式子中的乘号左边的数的最后一个不为0的数。
如果是2的话,乘以5会得到10。
如果是4、6、8的话乘以5也会得到10。
如果是3、7、9的话就和1一样不会得到10。(得不到10也就无法增加一个0)
那么为什么是5而不是2、4、6、8呢?因为对于任何一个大于1的数的阶层而言,它的最后一个不为0的数必然是偶数。这又是为什么呢?因为最起码一开始就成了2,结果变成了偶数,而偶数乘以偶数为偶数,偶数乘奇数还是偶数…… 而2、4、6、8都必须和5相乘才可以得到10,以至于增加一个0。
那么5呢?5乘以任意一个偶数不都可以增加一个0吗,比如所10、20、30、40等等。
那么这个问题就得到了较为具体的解答。该网友还问了,为什么一个整数有25的因子,就需要计数再加1呢,很显然25是两个5的乘积呀。那么又为什么不考虑5的三次方也就是125呢?因为我们只乘到了100呀,100的阶层嘛。
如果不信我们就来验算一下呗……
#include<stdio.h>int main(){ int a,count =0; for(a=5;a<=200;a+=5) { ++count; if(!(a%25)) { ++count; if(!(a%125)) ++count; } } printf("200!的尾数有%d个零。\n",count); return 0;}
还有截图为证哦……
后来还看到一个题目,和这个也类似,需要求的是100的阶层的结果的数字中从左到右第一个四位的质数。
代码来源于网络以及别人的解答,感觉这里还是蛮巧妙地。
// C# Code public static class Program { public static void Main(string[] args) { string fac100 = Factorial(100).ToString("F0"); Console.WriteLine("The factorial of 100 is : {0}", fac100); for (int i = 0; i <= fac100.Length - 4; i++) { string substr = fac100.Substring(i, 4); if (CheckPrime(Convert.ToInt32(substr))) { Console.WriteLine("The expected result found and it is : " + substr); return; } } Console.WriteLine("No result as expected!!"); } public static double Factorial(int n) { double result = 1; for (int i = 1; i <= n; i++) result *= i; return result; } public static bool CheckPrime(int n) { if (n == 1 || n == 2) return true; int squareRoot = Convert.ToInt32(Math.Sqrt(n)); for (int i = squareRoot; i > 1; i--) if (n % i == 0) return false; return true; } }
// C++ Code#include <iostream>#include <math.h>using namespace std;double Factorial(int n){ double result = 1; for (int i = 1; i <= n; i++) result *= i; return result;}bool CheckPrime(long n){ if (n == 1 || n == 2) return true; long squareRoot = (long)sqrt(n); for (long i = squareRoot; i > 1; i--) if (n % i == 0) return false; return true;}int main(int argc, char *argv[]){ char buf[1024] = { '\0' }; sprintf_s(buf, "%.f", Factorial(100)); cout << "The factorial of 100 is : " << buf << endl; char substr[5] = { '\0' }; for (int i = 0; i <= strlen(buf) - 4; i++) { memcpy(substr, buf + i, 4); if (CheckPrime(atol(substr))) { cout << "The expected result found and it is : " << substr << endl; return 0; } } cout << "No result as expected!!"; return 0;}
// C Code#include <stdio.h>#include <stdlib.h>#include <math.h>#include <stdbool.h>double Factorial(int n){ double result = 1; int i; for (i = 1; i <= n; i++) result *= i; return result;}bool CheckPrime(long n){ if (n == 1 || n == 2) return true; long squareRoot = (long)sqrt(n); long i; for (i = squareRoot; i > 1; i--) if (n % i == 0) return false; return true;}int main(int argc, char *argv[]){ char buf[1024] = { '\0' }; sprintf(buf, "%.f", Factorial(100)); printf("The factorial of 100 is : %s\n",buf); char substr[5] = { '\0' }; int i; for (i = 0; i <= strlen(buf) - 4; i++) { memcpy(substr, buf + i, 4); if (CheckPrime(atol(substr))) { printf("The expected result found and it is : %s\n",substr); return 0; } } printf("No result as expected!!\n"); return 0;}
欢迎大家点击左上角的“关注”或右上角的“收藏”方便以后阅读。
- 【万字总结】探讨递归与迭代的区别与联系及如何求解10000的阶层
- 递归与迭代的区别与联系
- 迭代算法与递归算法的概念及区别
- 递归与迭代的区别
- 迭代与递归的区别
- 递归与迭代的区别
- 递归与迭代的区别
- 迭代与递归的区别
- 迭代与递归的区别
- 迭代与递归的区别
- 迭代与递归的区别
- 递归与迭代的区别
- 递归与迭代的区别
- 迭代与递归的区别
- 递归与迭代的区别
- 递归与迭代的区别
- 迭代与递归的区别
- 迭代与递归的区别
- 工厂方法(Factory Method)模式
- 围住神经猫
- [Android实例] BLE总结
- Android中Preference的使用以及监听事件分析
- Socket模型详解(转)
- 【万字总结】探讨递归与迭代的区别与联系及如何求解10000的阶层
- chapter 3 -回归试验
- LeetCode Insert Interval
- UML用例图
- pl/sql select into 用法
- 机器学习&深度学习实践(Python版)----Multivariate Linear Regression(多元线性回归)
- 压缩感知(Compressive Sensing)学习之(一)
- 成为伪程序员了
- IOS获取当前位置坐标不执行代理方法