ACM选修(递归)

递归算法的构成:基本项和归纳项


例题一 求n的阶乘n!(n>0)

终止状态:当n=1时,n!=1

归纳项:当n>1时,n!=n*(n-1)!

int fact(int n)
{
  if(n==1) return 1;
  else return n*fact(n-1);
}

例题二 Fibonacci序列:1,1,2,3,5,8,13…

定义为:F0=F1=1   Fi=Fi-1+Fi-2(i>1)

终止状态:当i=1或i=0时,Fi=1

归纳项:当i>1时,Fi=Fi-1+Fi-2

int Fib(int i)
{
   if(i==1||i==0) return 1;
   else return Fib(i-1)+Fib(i-2);
}

 1.求单链表的表长——递归算法

方法一

int length(struct node *h)
{
   if(h==0) return 0;
   else return  1+length(h->next);
}
方法二

int counter=0;
void length(struct node*h)
{
   if(h==0) return;
   else
   {
     counter++;length(h->next);
   }
}
方法三

int counter=0;
void length(struct node*h)
{
   if(h==0) return;
   else
   {
     length(h->next);counter++;
   }
}
方法四

void length(struct node*h,int &x)
{
   if(h==0) return;
   else
   {
     x++;length(h->next,x);
   }
}

2.输出单链表各结点的元素值

方法一

void print{struct node*head)//正向输出
{
  if(head==0) return;
  else 
  {
    cout<<head->data<<" ";
   print(head->next);
  }
}

 方法二 
void print{struct node*head)//反向输出
{
  if(head==0) return;
  else 
  {
    print(head->next);
    cout<<head->data<<" ";
  }
}


问题:出栈序列

算法设计思路 穷举法

思想:就是用穷举的方法列出所有的情况

算法:用递归算法进行所有情况的搜索

void counter1(int sout,int sin,int &sum)
//sin表示在栈中的元素个数
//sout表示未进栈的元素个数,sum为统计数目
{
  if(sout==1) sum++;
  else counter1(sout-1,sin+1,sum);
  if(sin>0) counter1(sout,sin-1,sum);
}

void counter2(int sout,int sin,int &sum)
//sin表示在栈中的元素个数
//sout表示未进栈的元素个数,sum为统计数目
{
  if(sout==0) {sum++;return;}
  else counter2(sout-1,sin+1,sum);
  if(sin>0) counter2(sout,sin-1,sum);
}

问题:

张明与朋友们一同去上海世博会参观,世博会的门票为50元,张明发现一个奇怪的现象:在排队购票的2n个人中,总有n个人拿着面值为100元的钞票,而另外n个人拿的是面值为50元的钞票。张明想知道在这种情况下这2n个人共有多少中排队方式,使售票处不至于出现找不开钱的局面(假设售票处原来没有零钱的情况)?

算法设计思路
思路:根据影响因素进行函数关系的构建
由于题目中影响序列个数的因素主要是拿50元和100元的当前人数,因此,我们可以设定:
x为拿50元的当前人数,y为拿100元的当前人数。
于是有我们就可以构建出函数f(x,y)。

分情况讨论函数关系
y=0,则只有拿50元的人,于是f(x,0)=1;
X<y,因为拿100元的人多于50元的人,最后会出现无法找零,于是f(x,y)=0;
其余情况:
–考虑x+y个人排队的情况,第x+y个人站在第x+y-1个人的后面,那么第x+y个人的排队方案可以按以下两种情况来分析:
(1)第x+y个人拿100元,那么他之前的x+y-1个人中有x个人50
元,有y-1个人拿100元,此时为f(x,y-1);
(2)第x+y个人拿50元,那么他之前的x+y-1个人中有x-1个人拿50元,有y个人拿100元,此时为f(x-1,y);

int f(int x, int y) 
{ 
     if (y==0) return 1; 
     else if (x<y) return 0; 
     else return f(x-1,y)+f(x,y-1); 
}

设有n条封闭曲线画在平面上,而任何两条封闭曲线恰好相交于两点,且任何三条封闭曲线不相交于同一点,问这些封闭曲线把平面分割成的区域个数。设fn为n条封闭曲线把平面分割成的区域个数,则有:
f2-f1=2   
f3-f2=4
f4-f3=6
……
fn-fn-1=2*(n-1)
int f(int n) 
{ 
     if (n==1) return 1; 
     else if (n>1) return f(n-1)+2*(n-1); 
}


问题描述:

将整数n分成k份,且每份不能为空,任意两种分法不能相同(不考虑顺序),问有多少种不同的分法。(6<n<=200,1<k<7)

说明:n=7,k=3,则下面三种分法被认为是相同的。
 1,1,5    1,5,1   5,1,1
样例:输入:7 3   输出:4   
1 1 5,1 2 4,1 3 3,2 2 3   
算法分析
1递归算法:分析处理过程,找到递推公式,用递归算法来解决问题。
首先分析处理过程,当n=15,k=6时的分法
1 1 1 1 1 10
1 1 1 1 2 9
1 1 1 1 3 8
1 1 1 1 4 7
……
1 2 3 3 3 3
2 2 2 2 2 5
2 2 2 2 3 4
2 2 2 3 3 3
共26种。n=12,k=3时

1  1  10          2  2  8         3  3  6       4  4  4
1  2  9             2  3  7         3  4  5
1  3  8             2  4  6          
1  4  7             2  5  5
1  5  6
思路
逐步将范围缩小。
每个位置都进行逐个数值的处理分析。
即第一个数的选择方法,接着考虑第二数的选择方法,如此类推。

构造递归函数
于是设函数f(n,k)表示总的分法,它可按第一个分解数的不同,分为i类(0<i<=n /k):
i=1时,因为第一个数已经固定为1,那么剩下就为n-1,要划分为k-1分,因此,f1=f(n-1,k-1)
i=2时,因为第一个数固定为2,因此,后面的每个数均不少于2,因此后面的分解就从2开始,再把剩余的数n-2划分为k-1份。因此,f2=f(n-2,k-1)从2开始处理
i=3时,因为第一个数固定为3,因此,后面的每个数均不少于3,所以后面的分解就从3开始,再把剩余的数n-3划分为k-1份。因此,f3=f(n-3,k-1)从3开始处理

我们考虑一般情况,即第一个数为i时,因为,第一数已经固定为i,即其后面的每个数均不少于i,所以后面数的分解就要从i开始处理,再把剩余的数n-i划为k-1份。因此,fi=f(n-i,k-1)从i开始处理因此,我们得出如下的递归函数:F(n,k)=f1+f2+…+f[n/k],又因为,每次均要从上一次所选数开始进行处理,因此我们将函数改写为

F(n,k,now) =F(n-1,k-1,1) + F(n-2,k-1,2) + F(n-n/k,k-1,n/k)  
    int f(int n,int k,int now)   
    {    
       int i,s=0;   
       if (k==1)  return 1;   
       else    
       {for(i=now;i<=n/k;i++)   
        s=s+ f(n-i,k-1,i);//用同样的分法处理规模降1的剩余数  
        return s;    
       }   
    }   
    void main()   
    {   int n=15,k=6;   
        cout<<f(n,k,1);   
    }   
算法分析2
递归算法:分析处理过程,找到递推公式,用递归算法来解决问题。首先分析处理过程,当n=15,k=6时的分法
1 1 1 1 1 10
1 1 1 1 2 9
1 1 1 1 3 8
1 1 1 1 4 7
……
1 2 3 3 3 3
2 2 2 2 2 5
2 2 2 2 3 4
2 2 2 3 3 3
共26种。

构造递归函数
于是设函数f(n,k)表示总的分法,它可按第一个分解数的不同,分为i类(0<i<=n /k):
i=1时,因为第一个数已经固定为1,那么剩下就为n-1,要划分为k-1分,因此,f1=f(n-1,k-1)
i=2时,因为第一个数固定为2,因此,后面的每个数均不少于2,所以就可以先在后面的k-1份中的每一份里放1,再把剩余的数n-2-(k-1)划分为k-1份。因此,f2=f((n-2)-(k-1),k-1)=f(n-k-1,k-1)
i=3时,因为第一个数固定为3,因此,后面的每个数均不少于3,所以就可以先在后面的k-1份中的每一份里放2,再把剩余的数n-2-(k-1)*2分为k-1份。因此,f3=f((n-3)-(k-1)*2,k-1)=f(n-2k-1,k-1)
我们考虑一般情况,即第一个数为i时,因为,第一数已经固定为i,即其后面的每个数均不少于i,所以就可以先在后面的k-1份中的每一份里放i-1,再把剩余的数n-i-(k-1)*(i-1)划分为k-1份。因此,

fi=f((n-i)-(k-1)*(i-1),k-1) = f(n-(i-1)*k-1,k-1
因此,我们得出如下的递归函数:

F(n,k)=f1+f2+…+f[n/k]// 特别地f(n,1)=1,f(n,2)=n/2

F(n,k)=f1+f2+…+f[n/k]// 特别地f(n,1)=1,f(n,2)=n/2
    int f(int n,int k)   
    {   int i, s=0;   
        if (k==2) return n/2; //if (k==1) return 1;   
        else {  s=0;   
                    for(i=1;i<=n/k;i++)   
                         s=s+ f(n-(i-1)*k-1,k-1);   
                    return s;   
                }   
    }   
    void main()   
    {    cout<<f(15,6);    }   
    #include <iostream>  
    using namespace std;  
    int a[60], s = 0;  
    void f(int n, int now, int k, int loop)  
    {  
        int i, j;  
        if (k == 1)  
        {  
            a[now] = (loop - 1)*k + n; s++;  
            for (j = 1; j <= now; j++) cout << a[j];  
            cout << endl;  
        }  
        else  
        {  
            for (i = 1; i < n / k; i++)  
            {  
                a[now] = a[now - 1];  
                f(n - (i - 1)*k - 1, now + 1, k - 1, i);  
            }  
        }  
    }  
      
    int main()  
    {  
        a[0] = 0; f(7, 1, 3, 0); cout << s;  
        return 0;  
    }  

如何输出每种分法的结果

如何保存每种分法的各个取值。
fi=f(n-i,k-1) 从i开始处理,将这个i值保存到数组中

问题升级
任何一个大于1的自然数n,总可以分拆成若干个小于n的自然数之和。
例如:
N=2可以分拆为1种:2=1+1
N=3可以分拆为2种: 3=1+2   3=1+1+1
N=4可以分拆为4种:4=1+3   4=1+1+2   4=1+1+1+1  4=2+2

注意:分拆N=5时,5=2+3    5=3+2 是同一种拆分。

思路分析
拆分方法(以N=7为例)
第一步:将N拆分为2个数
7=1+6 7=2+5 7=3+4       
拆分特点:第2加数总大于第一个加数,因此第一个加数从1变化到n/2。
第二步:只要第2个加数仍然大于1,就可按第一步重复拆分。
7=1+6    可以继续拆分为 7=1+1+5 7=1+2+4 7=1+3+3
其中7=1+1+5又可以继续拆分获得7=1+1+1+4 7=1+1+2+3…

    void  f(int n,int now)   
    {   int i , j;   
    if (n<now) return   
    else    
    { for(i=now;i<=n/2;i++)   
      { s++;//全局变量,统计分法的总数  
        f(n-i,i);     
       }   
    }   
    }  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值