C++课程笔记——函数与递归总结


一、知识梳理(常用的几个知识点)

1、函数定义与声明:一般来说有两种写法:一种写在主函数之前,一种写在主函数之后(即先声明,再具体实现),不过无论哪种写法,都要遵循函数先声明后操作的原则。其次:对于第二种写法在声明时一定注意加分号

2、函数类型:bool、void、int。。。。。。须要注意的是返回值一定要和函数类型相对应。

3、函数的调用及实参与形参间的传递(因为之前做过一次小总结,所以在这里就简单写一了下):

    ①、传值调用:该种调用方法较简单,注意形参与实参间一一对应即可,就不再多说了。

    ②、传引用:

            例如 int &a=b,可以理解为给b起了一个别名;当再次对a操作时即对b进行操作,例如a=6,这时b==6。

    ③、指针传递:

            例如 int *p,即定义了一个指向整形变量的指针p;若在定义时对其赋值,int *p=?,那么给p的应当是一个地址,之后再对其操作,如*p=6;表示给p所指向的变量赋值6。

    ④、数组的地址:

            最后,关于数组,因为其本身的特殊性,所以注意要几个小问题:比如,a[i]所表示的是具体的数值,而a+i表示的则是第i+1的元素的地址,同理,*(a+i)表示的就是一个数值了。

4、递归函数:

        关于递归,个人感觉它是许多算法的基础,所以还是很重要的,当然刚开始理解起来也有一定的难度。递归的定义是函数直接或间接的调用自身。在设计递归函数时,最重要的是要找到递归边界及递归的方法。(当然,有的题目对于递归边界可以不用明显的写出来,比如在迷宫类问题中),说起来是挺简单的,但关键还是得具体问题具体分析。下面以几个简单题目为例在加深一下对递归及函数的理解。


二、题目分类汇总(简单例题为主,感觉POJ上的题目多为dfs枚举)。

1、公式类递归:

最典型的的是求阶乘(递归实现):

#include<bits/stdc++.h>
using namespace std;
int fac(int x)
{
    if(x==1)return 1;  //关键是找递归边界,及递推式;
    return x*fac(x-1);
}
int main()
{
    int n;
    cin>>n;
    cout<<int (fac(n))<<endl;
    return 0;
}


2、枚举型递归:

①全排列问题(数组中1~n元素的所有排列方式,假设无重复数据):

#include<bits/stdc++.h>
using namespace std;
int a[10010],memmor[10010];
bool flag[10010];
int n;
void search(int x)
{
    if(x>n)
    {
        for(int i=1;i<=n;++i)cout<<memmor[i]<<" ";
        cout<<endl;
        return ;
    }
    for(int i=1;i<=n;++i)
    {
        if(flag[i]==0)
        {
            memmor[x]=a[i];
            flag[i]=1;
            search(x+1);   //dfs回溯;
            flag[i]=0;
            memmor[x]=0;

        }
    }
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;++i)cin>>a[i];
    cout<<endl;
    search(1);
    return 0;

}

另一种较高级的写法(可以好好领悟领悟):

#include<bits/stdc++.h>
using namespace std;
int a[10010];
void search(int b[],int k,int m)
{
   if(k==m)
   {
       for(int i=1;i<=m;++i)cout<<a[i]<<" ";
       cout<<endl;
   }
   for(int j=k;j<=m;++j)
   {
       swap(b[k],b[j]);
       search(b,k+1,m);
       swap(b[k],b[j]);
   }
}
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;++i)cin>>a[i];
    cout<<endl;
    search(a,1,n);
    return 0;

}

②半数集问题:

n的半数集中元素个数f(n)==1+(f(1)+f(2)+f(3)+......+f(n/2));

所以有递归代码:

#include<bits/stdc++.h>
using namespace std;
int sett(int x)  //
{
    int ans=1;
    if(x==1)return 1;
    for(int i=1;i<=x/2;++i)
         ans+=sett(i);  //算式公式枚举并用了;
    return ans;
}
int main()
{
    int n;
    cin>>n;
    cout<<int(sett(n))<<endl;
    return 0;
}

三、函数与递归心得。

        刚开始学习函数时其实觉的许多题目写成函数的形式挺麻烦的,不过随着后面做的题目难度的增加,发现函数还是很强大的。比如在做某些复杂点的问题时,为了能使自己的思路更清晰,我们常常会先写出一个程序的大体框架,然后在已有的框架基础上去增改代码,但因为在之前写框架的过程中,对于许多子问题我们常常是仅仅简单描述一下这部分代码应该实现什么样的功能,而对于怎么具体实现这样的功能我们并没有做太多思考或者说只是想了个大概(至少我是这样),所以这个时候,我们就可以用几个小函数来逐个解决这些子问题,当然如果子问题比较简单,我们也可以直接写,不过个人感觉大多数时候,写成函数的形式能有效减小我们的思维量,同时也能更好的增加我们程序的可读性,后面debug时也会变得更容易一些。

        关于递归,个人感觉对于递归问题的解决,最关键的还是划分子问题与寻找递归边界,难点也在于怎么缩小问题的规模,我们常常要尝试找出一种通解来作为我们的递推关系,比如前两天POJ上做的一个分苹果的题目就挺具代表性的,当然具体细节就不在这里阐述了。其次对于递归过程的理解,在刚开始学的时候,感觉特别不好理解,不过多想想,慢慢也就懂得一点了,递归的过程其实就是一个建立栈的过程,在正向递归的过程中我们不断的建立栈,同时我们当前的数据和未完成的语句都会被保存下来,直至到达递归边界,之后逐层退栈,即我们所说的回溯,同时更新对应层的数据并继续执行对应层中子函数后面未完成的代码,直至返回最后结果。最后关于递归应用,最近最常做的还是dfs类问题,感觉POJ上的题目也有不少是深搜,尽管用递归写的代码确实思路更清晰一些,不过感觉dfs类问题一般很少会用裸的递归,因为耗时长,消耗空间大是递归的一大缺点,所以我们常常要对其进行剪枝,去掉那些没有必要的操作(可以说是dfs的难点之一)。

        最后,无论是说函数还是说递归,它们都有一个共同的特点,就是能增强我们程序的可读性,能把我们的思路更清晰的呈现出来。毕竟,我们写的代码不可能只留给自己观赏,而在我们将自己的程序拿给别人看的时候,谁都不希望看到的是一堆乱糟糟,甚至是只有一个主函数的代码。所以,在以后写程序的过程中,我们要在保证运行效率的前提下,尽可能的增强程序的可读性,让别人或是自己在调bug时能一眼就看出这段代码要做什么,让人知道它的存在对于整个程序或是某种功能有什么意义。

        C++的路还很长,继续加油吧!!!

        The end;


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值