递归

最最首先

欢迎大家来到大连二十四中学金普学校

提前声明:

可能需要一些C++基础才能看懂所有的东西

如果你没有基础

那就睡觉吧

开个玩笑

其实不要紧

只需要理解思想

不需要完全理解代码

如果你感到听不懂

深呼吸 听不懂是正常的

有部分结论 代码 题目 来自网络

在文后有链接 侵删


前言

什么是递归

函数反复调用自身即是递归

举个栗子

递归

我在这篇博客里写了这篇博客的链接

像不像套娃

举个正经栗子

比如我们算nn的阶乘f(n)f(n)

(阶乘就是1234...n1*2*3*4*...*n

f(6)f(6)为例

f(6)f(6)

=> 66 *f(5) f(5)

=> 6 6 * (5(5 *f(4)) f(4))

=> 66 * (5(5 * (4(4 * f(3)))f(3)))

=> 66 * (5(5 * (4(4 * (3(3 * f(2))))f(2))))

=> 66 * (5(5 * (4(4 * (3(3 * (2(2 * f(1)))))f(1)))))

=> 66 * (5(5 * (4(4 * (3(3 * (2(2 * 1))))1))))

=> 66 * (5(5 * (4(4 * (3(3 * 2)))2)))

=> 66 * (5(5 * (4(4 * 6))6))

=> 66 * (5(5 * 24)24)

=> 66 * 120120

=> 720720

看到递归了吗?

代码写出来是这样的

#include<bits/stdc++.h>
using namespace std;
int get(int n)
{
    if(n==1)//回归
        return 1;
    else//递进
        return n*get(n-1);//函数自己调用了自己
}
int main()
{
    cout<<get(6)<<endl;
    return 0;
}

递进,再回归——这就是「递归」

通俗来讲 递归是一种懒人的甩锅思想

如果你自己计算不了

就甩给你的下级去做

我们不能成为这样的人

甩锅的过程就是函数调用自身

递归也可以看作是一个编程技巧

也是一种将规模大的问题转化为规模小的问题的思想


组成部分

明白了什么是递归之后

我们就要知道递归是怎么实现

递归由两部分组成

  1. 递归的终止条件

递归不能无终止的进行下去

一个老板把工作交给了经理

经理又交给了员工

员工总不能把工作给保洁阿姨吧

比如上面的例子

我们算到f(1)的时候就停止了

因为地球人都知道1的阶乘是1

我们总不能再去算0和-1的阶乘吧

如果我们再去不停的调用

程序就会死循环

所以递归的终止条件是十分重要的

if(n==1)
    return 1;

上面代码的这一句就是例题的终止条件

如果你写代码时不注意这一点

就会像下图一样看见满屏的错误

image

  1. 递归关系

这个就跟具体问题有关系了

还是之前的例子

我们要算6的阶乘 首先要知道5的阶乘

要算5的阶乘 首先要知道4的阶乘

要算i的阶乘 首先要知道i-1的阶乘

所以我们计算f(i)时 调用的是f(i-1)

再看一眼代码

#include<bits/stdc++.h>
using namespace std;
int get(int n)
{
    if(n==1)
        return 1;
    else
        return n*get(n-1);//函数自己调用了自己
}
int main()
{
    cout<<get(6)<<endl;
    return 0;
}

例题

1.兔子数列

兔子数列又称斐波那契数列

1,1,2,3,5,8,13...这样一个数列就是斐波那契数列,我们要求第n项的值。

观察数列可得

除了第一项和第二项,所有的数列的值都是前一项和前一项的前一项的和

转换成函数也就是f(n) = f(n-1) + f(n-2)

我们从递归的终止条件递归关系去入手

首先分析递归的终止条件

斐波那契数列的第1项和第2项是特殊的 都是1

所以我们算到第1项或第2项就可以返回了

也就是

if(n==1 || n==2)
    return 1;

然后再分析递归关系

斐波那契的第n项是第n-1项和第n-2项的和

我们要算第n项

就要知道第n-1项 和第n-2项

所以是这么调用的

else
    return f(n-1)+f(n-2);

总的代码是这样的

const int mod = 1000000007;

struct Matrix {
    int a[3][3];
    Matrix() { memset(a, 0, sizeof a); } // 构造函数,矩阵初始化全零
    Matrix operator*(const Matrix &b) const {
        Matrix res;
        for (int i = 1; i <= 2; ++i)
            for (int j = 1; j <= 2; ++j)
                for (int k = 1; k <= 2; ++k)
                    res.a[i][j] = (res.a[i][j] + a[i][k] * b.a[k][j]) % mod;
        return res;
    }
} ans, base;

void init() { // 初始化 ans、base 矩阵
    base.a[1][1] = base.a[1][2] = base.a[2][1] = 1;
    ans.a[1][1] = ans.a[1][2] = 1;
}

void qpow(int b) { // 求
    while (b) {
        if (b & 1) ans = ans * base;
        base = base * base;
        b >>= 1;
    }
}

int main() {
    int n = read();
    if (n <= 2) return puts("1"), 0;
    init();
    qpow(n - 2);
    println(ans.a[1][1] % mod);
}

好像是矩阵快速幂 对不起 重来

只是想给大家整个乐子

下面是正经的

#include<bits/stdc++.h>
using namespace std;
int f(int n)
{
    if(n==1 || n==2)
    {
        return 1;
    }
    else
    {
        return f(n-1)+f(n-2);
    }
}
int main()
{
    int n;
    cin>>n;
    cout<<f(n)<<endl;
    return 0;
}

练习

1159 斐波那契数列

代码跟上面一样

1201 斐波那契数列

这道题多组数据

AC代码

#include<bits/stdc++.h>
using namespace std;
int f(int n)
{
    if(n==1 || n==2)
    {
        return 1;
    }
    else
    {
        return f(n-1)+f(n-2);
    }
}
int main()
{
    int n;
    cin>>n;
    while(n--)
    {
        int a;
        cin>>a;
        cout<<f(a)<<endl;
    }
    return 0;
}

1202 Pell数列

递归关系稍有变化f(n)=2f(n1)+f(n2)f(n)=2*f(n-1)+f(n-2)

注意取模

不放代码了 自己回家慢慢做

加油


2.累加和

1+2+3+......+n1+2+3+......+n的结果

递归的终止条件是算到1的时候返回1

递归关系f(n)=n+f(n1)f(n)=n+f(n-1)

代码如下

#include<bits/stdc++.h>
using namespace std;
int f(int n)
{
    if(n==1)
    {
        return 1;
    }
    else
    {
        return n+f(n-1);
    }
}
int main()
{
    int n;
    cin>>n;
    cout<<f(n)<<endl;
    return 0;
}

题目链接在这里

1158 求1+2+3+...


3.累乘积

123...n1*2*3*...*n的结果

这题是最开始的例子推广到了n

其实是一样的

也是把上一题的加改为乘

递归的终止条件是算到1的时候返回1

递归关系f(n)=nf(n1)f(n)=n*f(n-1)

#include<bits/stdc++.h>
using namespace std;
int f(int n)
{
    if(n==1)
    {
        return 1;
    }
    else
    {
        return n*f(n-1);
    }
}
int main()
{
    int n;
    cin>>n;
    cout<<f(n)<<endl;
    return 0;
}

刚才大佬yzy已经讲完了很多种排序算法

不能只学会了冒泡吧

接下来再说两个算法

快速排序归并排序

试图理解思想

不需理解代码

快速排序

快速排序是一个基于分治的排序方法

没学分治不要紧 明天有大佬wcj讲

给定一个数组a,该数组共有n个元素,我们需要对其进行从小到大的排序。

假设要排序的区间为[l,r][ l , r ]

如果区间长度小于等于 1,那就直接退出(因为只有一个元素不用排序)

这就是递归的终止条件

否则在该区间中选择任意一个元素x作为基准元素。将大于x的元素放到x的右边;将小于x的元素放到x的左边

再对x的左右两边的区间分别进行递归即可。

这就是递归关系

我们看图模拟一下这个过程

图片来源

一文彻底搞懂快速排序算法

image

image

image

image

image

image

代码如下

能看到函数调用自身递归操作

void quick_sort(int l,int r){
    //区间 [l,r] 的元素个数为 r - l + 1
    //如果区间元素个数 <= 1 就直接返回,即 r - l + 1 <= 1
    //化简一下就是 l >= r
    if(l >= r) return;
    swap(a[l] , a[l + rand() % (r - l + 1)]);//随机选择元素
    int x = a[l];
    int i = l ,j = r;
    while(i < j){
        //先从右往左找到第一个 <= x 的元素
        while(i < j && a[j] > x) j--;
        //把找到的元素放到位置 i 上
        if(i < j) a[i++] = a[j];
        //再从左往右找到第一个 >= x 的元素
        while(i < j && a[i] < x) i++;
         //把找到的元素放到位置 j 上
        if(i < j) a[j--] = a[i];
    }
    //最后,i 和 j 同时指向了同一个位置 p
    //我们就把 a[p] 的值赋为 x
    //x 的位置就已经确定了
    a[i] = x;
    //再递归的处理剩下的两个区间,即 [l , p - 1] 和 [p + 1 , r]
    quick_sort(l,i-1);
    quick_sort(i+1,r);
}

归并排序

归并排序,是创建在归并操作上的一种有效的排序算法。

归就是层层递归将数列分开

并就是把排序好的左右数列合并

合并时每次选择左右序列比较小的那个元素

看图理解一下

图片来源

【算法】排序算法之归并排序

image

上图中首先把一个未排序的序列从中间分割成2部分,再把2部分分成4部分,依次分割下去,直到分割成一个一个的数据(递归的终止条件),再把这些数据两两归并到一起,使之有序,不停的归并,最后成为一个排好序的序列

代码如下

同样能看到函数调用自身递归操作

void _MergeSort(int* arr, int begin, int end, int* tmp)
{
	if (begin >= end)
	{
		return;
	}
	//分治递归,让子区间先有序
	int mid = (begin + end) / 2;
	_MergeSort(arr, begin, mid, tmp);
	_MergeSort(arr, mid + 1, end, tmp);
	
	int begin1 = begin;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = end;
	int i = begin1;
	//在arr[begin1,end1]和arr[begin2,end2]两个区间中开始归并
	while ((begin1 <= end1) && (begin2 <= end2))
	{
		//如果arr[begin1]的值小,就让arr[begin1]的值先进入tmp数组
		if (arr[begin1] < arr[begin2])
		{
			tmp[i++] = arr[begin1++];
		}
		//如果arr[begin2]的值小于等于arr[begin1],就让arr[begin2]的值进入tmp数组
		else
		{
			tmp[i++] = arr[begin2++];
		}
	}
	//如果数组1中的数据还没有归并到tmp中完,就直接将剩下的都放到tmp中
	while (begin1 <= end1)
	{
		tmp[i++] = arr[begin1++];
	}
	//如果数组2中的数据还没有归并到tmp中完,就直接将剩下的都放到tmp中
	while (begin2 <= end2)
	{
		tmp[i++] = arr[begin2++];
	}
	//把归并数据拷贝到原数据
	memcpy(arr + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
void MergeSort(int* arr, int n)
{
	//归并排序需要先申请一个辅助数组
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("MergeSort malloc fail");
		exit(-1);
	}
	_MergeSort(arr, 0, n - 1, tmp);
}

到此递归就讲完了

其实是讲不完的

因为递归很常用

学是学不完的

之后的无数算法要用到递归思想

所以要打好基础

回家好好看看

加油加油


引用来源

全面理解递归 - 知乎 (zhihu.com)

一文弄懂递归 - 知乎 (zhihu.com)

快速排序详解-CSDN博客

「一发入魂」一文彻底搞懂快速排序算法 (baidu.com)

【算法】排序算法之归并排序 - 知乎 (zhihu.com)

排序之归并排序-CSDN博客

只供学习使用 侵删