本题要求实现一个用选择法对整数数组进行简单排序的函数。_如何运用递归思维解决问题——冒泡、选择、合并排序...

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

ca28ebe8f25e7a2893fb10f74afd227f.png

学习递归的目的,最终是运用递归解决问题。

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

4c3f1a6a255379667a39b5bbc0f948ca.png

考察递归函数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.递归函数函数体的程序框架:

a7bcf16c502cee586d059395256ec83c.png

确定最小问题,即确定递归边界,直接解决最小问题;

当问题规模较大时,对问题进行分解,用递归调用解决分解出的小问题,用小问题的解构造出函数首部定义的原问题的解。

递归的三要素:定义描述问题;解决最小问题;分解大问题、用递归解决分解出的小问题。

用递归思维解决问题,我们不必关注如何处理及求解问题的详细过程和步骤,你甚至可以不知道计算解题过程就把问题给编程解决了。岂不是美死了!!!就像孙悟空懂规则,但不知道具体如何玩汉诺塔,可他居然完成了移动汉诺塔的目标(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的问题等,立即用递归得到解决。

112bd9d74670bf97afcf49a6e227d90c.png

我们还可以这样定义问题题:输出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之间的整数的递归函数,容易之极。

2d993ad3aa45c513b98d6d4789757b33.png

例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
af05c97e39a8a428d21560813a5d4663.png
赵冯平:汉诺塔、递归, 为什么孙悟空可以轻松玩转?​zhuanlan.zhihu.com
af05c97e39a8a428d21560813a5d4663.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值