
学习递归的目的,最终是运用递归解决问题。
理解递归的运行模型(https://zhuanlan.zhihu.com/p/166173378)之后,就能根据递归函数的静态代码推算执行结果了。实际上,对递归函数的执行结果的推算,可从另一个途径进行,即运用类似于数学归纳法的思想。下面以计算阶乘的递归函数为例说明。

考察递归函数int fact(int n)
1.它确实能正确计算0的阶乘的值;
2.当执行函数fact(1)调用时,它返回的值是1* fact(0),即1,所以1!计算正确。
3.当执行函数fact(2)调用时,它返回的值是2* fact(1),只要fact(1)能计算正确(上一步骤已证明正确),那么fact(2)也能正确计算2!
……
4.当执行函数fact(n)调用时,它返回的值是n*fact(n-1),从计算式子可推断出,只要fact(n-1)能计算出(n-1)!,那么,fact(n)就能计算出n!
根据数学归纳法的思想,就推断证明了fact(n)能计算出n!
切入正题——用递归思维编程解决问题
当描述问题解答的算法本身是递归式子时,我们很容易写出程序的递归函数。
对于一般的问题,设计递归函数的步骤、方法如下:
1.首先要明确地描述问题、定义问题,设计函数首部,用函数的参数刻画待解决问题的数量特征及其关系本质,特别地,函数参数(即变量)描述问题的规模,并描述函数的功能,或者说要非常明确函数要完成的任务,对设计者来说,无论口头和书面,都能清晰地、准确无误地、一般化地描述。
2.递归函数函数体的程序框架:

确定最小问题,即确定递归边界,直接解决最小问题;
当问题规模较大时,对问题进行分解,用递归调用解决分解出的小问题,用小问题的解构造出函数首部定义的原问题的解。
递归的三要素:定义描述问题;解决最小问题;分解大问题、用递归解决分解出的小问题。
用递归思维解决问题,我们不必关注如何处理及求解问题的详细过程和步骤,你甚至可以不知道计算解题过程就把问题给编程解决了。岂不是美死了!!!就像孙悟空懂规则,但不知道具体如何玩汉诺塔,可他居然完成了移动汉诺塔的目标(https://zhuanlan.zhihu.com/p/168684834)。
在一定的时间、空间限制下,人的体力有限,思维力也有限,递归思维对实践最有用的指导,就是把脑力集中于定义问题这个关键点上,不用去找解题的过程。定义(问题)即解决(问题),定义即解决! 定义即解决!! 定义即解决!!! 有点像具备先进导弹的现代先进战斗机,它们具备“发现即摧毁”的能力。
上面说的是不是让人觉得云里雾里,是不是吹牛。马上给您醍醐灌顶。
例1 输入整数n,输出1~n的整数。例如n=5时,输出12345。 这例子够简单。
问题:要解决的问题直截了当,即输出1~n的整数,设计函数首部void prn1n(int n);确定并解决最小问题;定义函数void prn1n(int n)明确其任务后,“定义即解决”指的是在设计函数体时,认为比1~n规模小的问题都已得到解决,都立即为我所用,即输出1~n-1或1~n-2的问题等,立即用递归得到解决。

我们还可以这样定义问题题:输出s~t之间的整数,函数首部void prnst(int s,int t); 表示输出s~t之间的整数;确定并解决最小问题;定义函数void prnst(int s,int t) 明确其任务后,“定义即解决”指的是在设计函数体时,认为比s~t规模小的问题都已得到解决,都立即为我所用,例如输出s+1~t、s+1~t-1或s~t-2或s~(s+t)/2等等,做法分别是prnst(s+1,t)、prnst(s+1,t-1)、prnst(s,t-2)、prnst(s ,(s+t)/2)。所以,要写出输出s~t之间的整数的递归函数,容易之极。

例1高明之处在于,简单的输出1~n之间的整数的问题,玩出如此之多的花样,对于线性参数表示的问题,它的递归方式(对问题的切分方式)基本上类似于以上几种方法之一。如数组的选择、冒泡、快速、合并排序,一维的背包问题等等,概莫能外。
递归思维解题的另一个关键点是划(切)分问题。
例2 冒泡排序,全递归实现。定义函数:void bubbleSort(int a[],int n); 它的任务是对数组a的前n个元素排序。 按照“定义即解决”原则,在设计函数体时,极易做到(用递归)排序数组a的前i(i<n)个元素。定义函数:void bubble(int a[],int n); 它的任务是对数组a的前n个元素进行冒泡处理,使a[n-1]最大。 按照“定义即解决”原则,在设计函数体时,极易做到(用递归)对数组a的前i(i<n)个元素冒泡,使a[i-1]最大。基于此,设计递归函数实现冒泡排序不费吹灰之力。
#include <iostream> //全递归实现冒泡排序
using namespace std;
void bubble(int *a, int n) ; //把数组a的前n个元素中最大冒泡到 a[n-1]位置
void bubbleSort (int a[],int n); //冒泡排序
void outputArr(int *a,int n){
for(int i=0;i<n;i++) cout<<a[i]<<' ';
}
int main()
{
int a[1000],n,i; //定义数组等变量
cin>>n;
for(i=0;i<n;i++)cin>>a[i]; //输入待排序数据到数组
bubbleSort(a,n); //调用排序函数
outputArr(a,n); //输出
}
void bubbleSort (int a[],int n) //冒泡排序
{
if(n==1)
;
else
{
//把数组a的前n个元素中最大冒泡到 a[n-1]位置
bubble(a,n) ;
//排序a[1]~a[n-1]
bubbleSort (a,n-1);
}
}
void bubble(int *a, int n){ //把数组a的前n个元素中最大冒泡到 a[n-1]位置
if(n==1)
;
else{
bubble(a,n-1);
if(a[n-2]>a[n-1]) swap(a[n-2],a[n-1]); else ;
}
}
例3 合并排序。void mergeSort(int a[],int s,int t)与例1方法5的递归函数相似度惊人!!
#include <iostream> //合并排序
using namespace std;
void merge(int *a,int s,int k,int t);//前置条件,数组a,a[s]~a[k]有序,a[k+1]~a[t]有序。使a[s]~a[t]有序
void mergeSort(int a[],int s,int t); //合并排序
void outputArr(int *a,int n){ //输出
for(int i=0;i<n;i++) cout<<a[i]<<' ';
}
int main()
{
int a[1000],n,i; //定义数组等变量
cin>>n;
for(i=0;i<n;i++)cin>>a[i]; //输入待排序数据到数组
mergeSort(a,0,n-1); //调用排序函数
outputArr(a,n); //输出
}
void mergeSort(int a[],int s,int t) //合并排序
{
if(s<t){
int mid=(s+t)/2;
mergeSort(a,s,mid);
mergeSort(a,mid+1,t);
merge(a,s,mid,t); //前置条件数组a中,a[s]~a[mid]有序,a[mid+1]~a[t]有序,使a[s]~a[t]有序
}
}
void merge(int a[],int s,int k,int t)//前置条件,数组a,a[s]~a[k]有序,a[k+1]~a[t]有序。使a[s]~a[t]有序
{
int b[1000];
int i=s,j=k+1,p=s;
for(;i<=k&&j<=t;)
{
if(a[i]<=a[j])
{ b[p]=a[i]; p++;i++; }
else
{ b[p]=a[j]; p++;j++; }
}
for(;i<=k;i++){ b[p]=a[i]; p++; } //前段剩余元素移至b数组
for(;j<=t;j++){ b[p]=a[j]; p++; } //后段剩余元素移至b数组
for(i=s;i<=t;i++)a[i]=b[i];
}
例4 选择排序,全递归实现
#include <iostream> //递归选择排序
using namespace std;
void selectMin(int *a, int n) ; //把数组a的前n个元素中最小的元素交换到a[0]位置
void sort(int a[],int n); //选择排序
void outputArr(int *a,int n){
for(int i=0;i<n;i++) cout<<a[i]<<' ';
}
int main()
{
int a[1000],n,i; //定义数组等变量
cin>>n;
for(i=0;i<n;i++)cin>>a[i]; //输入待排序数据到数组
sort(a,n); //调用排序函数
outputArr(a,n); //输出
}
void sort(int a[],int n) //选择排序
{
if(n==1) //只有一个元素时,不用排序
;
else
{
selectMin(a,n) ; //把数组a的前n个元素中最小的元素交换到a[0]位置
sort(a+1,n-1); //排序a[1]~a[n-1]
}
}
void selectMin(int a[],int n) //数组的形式参数说明,指明处理数组a的元素:a[0]、a[1] ...... a[n-1]
{ //函数功能 : 把a[0]~a[n-1]中的最小值调整到a[0]
if(n==1)
;
else{
selectMin(a,n-1) ; //先递归完成:把数组a的元素:a[0]、a[1] ...... a[n-2]的最小值调整到a[0]
if(a[0]>a[n-1]) swap(a[0],a[n-1]); else ;
}
}
研究比较例1的各种递归方法,您很容易理解选择排序、冒泡排序和合并排序以及其中的冒泡函数void bubble(int *a, int n)和函数void selectMin(int *a, int n)的递归实现。
赵冯平:递归思维——插入排序、快速排序的全递归实现
什么是递归思维? 有请各位高见。
赵冯平:理解递归函数调用的最好模型(没有之一)zhuanlan.zhihu.com

本文探讨如何运用递归思维解决编程问题,通过递归函数实现整数数组的选择排序、冒泡排序和合并排序。通过数学归纳法证明递归函数的正确性,并提供不同递归策略的例子,如输出1到n的整数、冒泡处理和合并排序。递归思维的关键在于定义问题和问题的划分,使得定义本身就包含了问题的解决方案。
1679

被折叠的 条评论
为什么被折叠?



