重拾算法之路——递归与分治基础

***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************



这个周末家里有点事,回了趟家,就断了一些学习计划。。

抓紧补上!


第一个算法——递归与分治

都知道,分治算法基本思想是

将一个难以直接解决的问题,分割成一些规模小的相同问题,以便各个击破,分而治之,

这样,我们就可以将一个复杂的算法,类型不变,规模越来越小,最终使子问题容易求解,由此引出递归算法。

其实,分治和递归像一对孪生兄弟,经常可以同时使用在算法设计之中,并由此产生许多高效的算法。


一、递归

概念:直接或间接的调用自身的算法称为递归算法,用函数自身给出定义的函数称为递归函数。

下面列出几个递归的经典例子:

① 阶乘

定义:       

n!等于 1 当n=0

等于n(n-1)! 当n>0

int  factorial(int n)
{
    if( n == 0 )    return 1;
    return n*factorial(n-1);
}

②Fibonacci数列

一个无穷的数列,序列为:1,1,2,3,5,8,13,21,34,55....

定义为:

F(n)= 1 当n=0

=1 当n=1

=F(n-1)+F(n-2) 当n>1

<span style="font-size:18px;">int fibonacci(int n)
{
    if( n <= 1 )    return 1;
    return fibonacci(n-1)+fibonacci(n-2);
}</span>


③Ackerman函数

这是一个双递归函数,当一个函数及它的一个变量由函数自身定义时,称这个函数为双递归函数。

Ackerman函数A(n,m) 有两个独立的整型变量m≥0,n≥0,其定义如下:

A(1,0) = 2 m≥0

A(0,m) = 1 n≥2

A(n,0) = n+2 n≥2

A(n,m) = A(A(n-1,m),m-1) n,m≥1


int Ackerman( int n , int m )
{
    if( n==1 && m==0 )    return 2;
    if( n==0 )    return 1;
    if( m==0 )    return n+2;
    return Ackerman( Ackerman(n-1,m),m-1);
}


④排列问题

对n个元素进行全排列,

当n=1时,Perm(P)=(r),其中r是集合R中唯一的元素

当n>1时,Perm(P)由(r1)Perm(R1),(r2)Perm(R2),.......,(rn)Perm(Rn)构成


template< class Type >

inline void Swap(Type& a,Type& b)
{
    Type temp = a;
    a = b;
    b = temp;
}

void Perm(Type list[],int k ,int m )
{
    if( k==m )
    {
        for( int i = 0 ; i <= m; ++i )    cout<<list[i];
        cout<<endl;
    }
    else
    {
        for( int i = k ; i <= m ; ++i )
        {
            Swap(list[k],list[i]);
            Perm(list,k+1,m);
            Swap(list[k],list[i]);
        }
}



⑤整数划分问题

将正整数n表示成一系列正整数之和,

n=n1+n2+...+nk(其中n1≥n2≥n3≥....≥nk)

例如,对于整数6的划分:

6

5+1

4+2 4+1+1

3+3 3+2+1 3+1+1+1

2+2+2 2+2+1+1 2+1+1+1+1

1+1+1+1+1+1

总共11种划分

按照最大加数n1不大于m的划分个数记作q(n,m),可以建立如下递归关系:

<1> q(n,1) = 1 , n≥1—— 当最大加数n1不大于1时,任何正整数n只有一种划分形式

<2> q(n,m)=q(n,n),n≥m—— 最大加数n1实际上不能大于n。因此q(1,m)=1

<3> q(n,m)=q(n,m-1)+q(n-m,m),n>m>1—— 正整数n的最大加数n1不大于m的划分由n1=m的和n1≤m-1的划分组成。

综上所述,可以总结出:

q(n,m)=

1 n=1,m=1

q(n,n) n<m

1+q(n,n-1) n=m

q(n,m-1)+q(n-m,m) n>m>1

int q( int n , int m )
{
    if( (n<1) || (m<1) )    return 0;
    if( (n==1) || (m==1) )    return 1;
    if( n==m )    return q(n,m-1)+1;
    return q(n,m-1)+q(n-m,m);
}


⑥Hanoi塔问题

非常经典的一个递归问题,问题就不赘述了,直接写算法:

void hanoi( int n , int a , int b , int c )
{
    if( n > 0 ){
        hanoi( n-1,a,c,b);
        move(a,b);
        hanoi( n-1,c,b,a);
    }
}


二、分治

之前也说过,分治法的基本思想就是,把一个规模大的问题,不断分成相互独立且与原问题相同的小规模问题。

它的算法设计模式如下:

divide-and-conquer(P)
{
    if( |P| <= n0 )    adhoc(P);
    divide P into smaller subinstances P1,P2,P3,...,Pk;
    for( i = 1 ; i <= k ; ++i )
        yi = divide-and-conquer(Pi);
    return merge(y1,y2,...,yk);
}

这其中

|P| 表示问题P的规模,

n0 为一阙值,表示当前问题P的规模不超过n0时,问题容易求解,不必再继续分解。

adhoc(P)  是该分治法中的基本子算法,对于容易求解的问题可以直接求解

merge(y1,y2...yk)    是合并子算法,用于将P的子问题P1,P2,...,Pk的解y1,y2,...,yk合并为P的解


分治法的计算效率通常用递归方程来分析,对于上述设计模式,解|P|=n的问题所需的计算时间,有:

T(n) =   O(1) 当n=1

             kT(n/m)+f(n) 当n>1




以上就是递归和分治的一些基本思想和原则,接下来的博文会有具体例子来说明。





***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************


你可能感兴趣的:(基础,递归与分治,重拾算法之路)