递归的概念
递归算法:直接或间接地调用自身的算法
递归函数:用函数自身给出定义的函数
递归方法的构造
构造递归方法的关键在于建立递归关系。这里的递归关系可以是递归描述的,也可以是递推描述的。下面由一个求n的阶乘的程序为例,总结出构造递归方法的一般步骤。
[例1]从键盘输入正整数N(0<=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的递归关系中,对大于k的Un的求解将最终归结为对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)=1,A(N)=A(N-1)+A(N-2)(N>=3)。从键盘输入N,输出A(N)。
[分析]递归关系十分明显,由A(N)的表达式给出。需要注意的是本例中对于N>=3,A(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)); } }