返回数据时赋值

在函数中,返回值语句可以是一条计算赋值语句

发现一个以前没有注意到的地方

#include <iostream>

using namespace std;

int getans(){
    
    
    int a = 4;
    int b = 5;
    int ans;
    return ans = a + b;
}

int main()
{
    
    
    printf("%d",getans());
    return 0;
}
//运行结果
9

挺有意思的,在main中调用了getans()函数,getans()中,最后一句是先计算a + b,之后将a + b 赋值给ans,再之后返回ans,此时getans()中ans 的值已经是9了,虽然调用结束之后,getans()所在的内存被释放掉了。
这可以用来干什么呢?准确来说,在函数中,返回值语句可以是一条计算赋值语句,这一点可以做什么呢?
其实可以用来优化用递归法来求斐波那契数列。

斐波那契数列

斐波那契数列都知道,F(0) = 0,F(1) = 1,F(N) = F(N-1) + F(N-2),这几乎是学习递归的必备知识。
首先来看一般的斐波那契数列求法。

#include <iostream>
#include <ctime>

using namespace std;

int getans(int n){
    
    
    if(n == 0 || n == 1) return n;
    else return getans(n - 1) + getans(n - 2);
}

int main()
{
    
    
    int n;
    cin>>n;

    int st = clock();
    printf("%d\n",getans(n));
    printf("用时%d",clock() - st);//输出一下用时
    return 0;
}
//运行用时
1134903170
用时7297

上面令n = 45,可以看到F(45) = 1134903170,但是用时为7297ms,这可以说是很慢了。因为涉及了大量的重复计算。没有利用起来。
现在可以使用上面的知识点“在函数中,返回值语句可以是一条计算赋值语句”。

#include <iostream>
#include <ctime>
#include <cstring>

using namespace std;

int F[50];//斐波那契数列

int getans(int n){
    
    
    if(F[n] != -1) return F[n];
    else return F[n] = getans(n - 1) + getans(n - 2);//重要
    //计算getans(n-1)与getans(n-2),并将结果赋给F[n],之后返回F[n]
}

int main()
{
    
    
    int st = clock();
    memset(F,-1,sizeof(F));//初始化为-1,意为该位置还没有计算出数据
    F[0] = 0;//将F[0] F[1]初始化为正确的值
    F[1] = 1;

    printf("%d\n",getans(45));
    printf("用时%d",clock() - st);
    return 0;
}

//运行结果
1134903170
用时1

结果非常明显,使用了这条结论,可以使得时间变为了1毫秒。
当然了,如果是仅仅求斐波那契数列,最好还是使用递推。

#include <iostream>
#include <ctime>
#include <cstring>

using namespace std;

int main()
{
    
    
    int F[50];
    int st = clock();
    F[0] = 0;
    F[1] = 1;
    for(int i = 2;i <= 45;i++)
        F[i] = F[i-1] + F[i-2];
    printf("%d\n",F[45]);
    printf("用时%d",clock() - st);

    return 0;
}
//运行结果
1134903170
用时0

不过可以看到,使用优化过的递归与递推,运行时间相差无几。

求排列组合C(n,m)

以下出自《算法笔记》
排列组合里面C(n,m)意为从n个数里面选择m个不同的数。且有C(n,n) = 1,C(n,0) = 1。
可以使用公式来计算,但是非常容易超出int类型的数据范围。
可以递归来做。这种选取可以分为两种不同的方案数之和:一是不选最后一个数,从前n - 1个数中选m个数;第二种方案是选最后一个数,并且从前n - 1个数中选m - 1个数。
即C(n,m) = C(n-1,m)+ C(n-1,m-1)。
从直观上来看,该公式总是将n减1,而m要么保持原样,要么减1,直到达到边界m等于n或者是m等于0。
于是可以很快得到下面的代码

long long getans(int n,int m){
    
    
    if(m == n || m == 0) return 1;
    return getans(n-1,m-1) + getans(n-1,m);
}

这个计算完全不涉及到阶乘,但是有另外的问题:重复计算。在计算C(n,m)的过程中,有很多的数据是已经计算过得了,但是没有在再次使用时直接使用,而是又去计算了一遍。因此不妨将已经计算过了的数据进行记录,下次碰到时,直接使用。

//res数组中res[n][m]为C(n,m);
long long res[67][67]={
    
    0};//初始值为0

long long getans(int n,int m){
    
    
    if(m == n || m == 0) return 1;
    if(res[n][m]) return res[n][m];//如果res[n][m]已经计算过了,直接返回即可
    return res[n][m] = getans(n-1,m-1) + getans(n-1,m);//否则先计算两种拆开的方案,在赋值给res[n][m],再返回
}

递推

//res数组中res[n][m]为C(n,m);
long long res[67][67]={
    
    0};//初始值为0,res是一个下三角阵

void getans(){
    
    
    for(int i = 0;i < 67;i++) 
        res[i][0] = res[i][i] = 1;//初始化边界
    for(int i = 2;i < 67;i++){
    
    //注意i的开始,从2开始
        for(int j = 1;j <= i / 2;j++){
    
    
            res[i][j] = res[i-1][j-1] + res[i-1][j];//使用公式
            res[i][j] = res[i][i-j];//根据定义C(n,m) = C(n,n-m)
        }
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_44321570/article/details/114376900