递归与分治策略-学习笔记

 

递归的概念

 

递归算法:直接或间接地调用自身的算法

 

递归函数:用函数自身给出定义的函数

 

 

递归方法的构造

构造递归方法的关键在于建立递归关系。这里的递归关系可以是递归描述的,也可以是递推描述的。下面由一个求n的阶乘的程序为例,总结出构造递归方法的一般步骤。

 

[1]从键盘输入正整数N0<=N<=20),输出N!

 

[分析]N!的计算是一个典型的递归问题。使用递归方法来描述程序,十分简单且易于理解。

 

[步骤1]描述递归关系 递归关系是这样的一种关系。设{U1,U2,U3,,Un}是一个序列,如果从某一项k开始,Un和它之前的若干项之间存在一种只与n有关的关系,这便称为递归关系。

注意到,当N>=1时,N!=N*(N-1)!N=1时,0!=1),这就是一种递归关系。对于特定的K!,它只与K(K-1)!有关。

 

[步骤2]确定递归边界 在步骤1的递归关系中,对大于kUn的求解将最终归结为对Uk的求解。这里的Uk称为递归边界(或递归出口)。在本例中,递归边界为k=0,即0!=1对于任意给定的N!,程序将最终求解到0!

 

确定递归边界十分重要,如果没有确定递归边界,将导致程序无限递归而引起死循环。例如以下程序:

#include <iostream.h>

int f(int x){

 return(f(x-1));

}

main(){

 cout<<f(10);

}

它没有规定递归边界,运行时将无限循环,会导致错误。

 

[步骤3]写出递归函数并译为代码 将步骤1和步骤2中的递归关系与边界统一起来用数学语言来表示,即

 

     N*(N-1)! 当N>=1

n!=

     1       N=0

再将这种关系翻译为代码,即一个函数:

long f(int n){

 if (n==0)

  return(1);

 else

  return(n*f(n-1));

}

 

[步骤4]完善程序 主要的递归函数已经完成,将程序依题意补充完整即可。

#include <iostream.h>

long f(int n){

 if (n==0)

  return(1);

 else

  return(n*f(n-1));

}

main(){

 int n;

 cin>>n;

 cout<<endl<<f(n);

}

 

经典递归问题

 

[2]Fibonacci数列(兔子繁殖)问题:已知无穷数列A,满足:A(1)=A(2)=1A(N)=A(N-1)+A(N-2)N>=3)。从键盘输入N,输出A(N)

 

[分析]递归关系十分明显,由A(N)的表达式给出。需要注意的是本例中对于N>=3A(N)的值与A(N-1)A(N-2)都有关。

#include <iostream.h>

long fibonacci(int x)

{

 if ( (x==1) || (x==2) )

  return(1);

 else

  return(fibonacci(x-1)+fibonacci(x-2));

}

main(){

 int n;

 cin>>n;

 cout>>endl>>fibonacci(n);

}

 [3]Hanoi塔问题。

 

[问题描述]在霍比特人的圣庙里,有一块黄铜板,上面插着3根宝石针(分别为A号,B号和C号)。在A号针上从下到上套着从大到小的n个圆形金片。现要将A针上的金片全部移到C针上,且仍按照原来的顺序叠置。移动的规则如下:这些金片只能在3根针间移动,一次只能一片,且任何时候都不允许将较大的金片压在较小的上面。从键盘输入n,要求给出移动的次数和方案。

 

[分析]由金片的个数建立递归关系。当n=1时,只要将唯一的金片从A移到C即可。当n>1时,只要把较小的(n-1)片按移动规则从A移到B,再将剩下的最大的从A移到C(即中间“借助”B把金片从A移到C),再将B上的(n-1)个金片按照规则从B移到C(中间“借助”A)。

本题的特点在于不容易用数学语言写出具体的递归函数,但递归关系明显,仍可用递归方法求解。

#include <iostream.h>

hanoi(int n,char t1,char t2,char t3){

 if (n==1)

  cout<<"1 "<<t1<<" "<<t3<<endl;

 else

 {

 hanoi(n-1,t1,t3,t2);

  cout<<n<<" "<<t1<<" "<<t3<<endl;

 hanoi(n-1,t2,t1,t3);

 }

}

main(){

 int n;

 cout<<"Please enter the number of Hanoi:";

 cin>>n;

 cout<<"Answer:"<<endl;

 hanoi(n,'A','B','C');

}

 

 

分治策略

基本概念

分治策略是对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。

基本步骤

step1分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;

step2解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题;

step3合并:将各个子问题的解合并为原问题的解。

它的一般的算法设计模式如下:

    Divide-and-Conquer(P)

    if |P|≤n0

     then return(ADHOC(P))

    将P分解为较小的子问题 P1 ,P2 ,...,Pk

     for i←1 to k

     do yi ← Divide-and-Conquer(Pi) △递归解决Pi

    T ← MERGE(y1,y2,...,yk) △合并子问题

     return(T)

其中|P|表示问题P的规模,n0为一阈值,表示当问题P的规模不超过n0时,问题已容易直接解出,不必再继续分解。ADHOC(P)是该分治法中的基本子算法,用于直接解小规模的问题P,因此,当P的规模不超过n0时直接用算法ADHOC(P)求解。算法MERGE(y1,y2,...,yk)是该分治法中的合并子算法,用于将P的子问题P1 ,P2 ,...,Pk的相应的解y1,y2,...,yk合并为P的解。

(例)二分查找

public class partFind {
    public static int whilefind(int[] arr,int val){//5  6  7  10
        int right=0;
        int left=arr.length-1;
        while (right<=left){
            int mid=(right+left)/2;//当范围数值过大可以采用:mid=left+(right-left)/2    0.618
            if (val==arr[mid]){
                while (mid>0&&arr[mid-1]==val) --mid;//如果数组中存在相同元素,且要求返回的是最右边值的下标
                return mid;
            }
            if (val<arr[mid]){
                left=mid-1;
            }
            if (val>arr[mid]){
                right=mid+1;
            }
        }
        return -1;
    }
    public static void main(String[] args) {
        int[] arr={12,12,23,23,23,34,45,56};
        System.out.println(whilefind(arr,12));
    }
}

 

posted @ 2020-08-02 21:59  辰君  阅读(220)  评论(0编辑  收藏  举报