算法 64式 3、递归算法整理

1算法思想

递归

1.1含义

调用自身的算法,把原问题分解为简单且类同的子问题。

 

1.2特点

包含递归基和递归步

1)递归基: 最简单情况下的函数值,又叫做递归出口

2)递归步: 递归主体,定义一般情况下的函数值

 

1.3适用

1)问题具有子问题描述的性质

2)有限步的子问题有直接的解存在

 

1.4通用解法

递归算法:

递归基代码….

递归步代码…

 

 

 

1.5经典例题讲解

Fibonacci数列

F(n)={1,            n=0或n=1  <= 递归基

        {F(n-1) + F(n-2), n > 1                  <= 递归步

示例代码如下:

int fibonacci(int n)

{

        if (n <= 1) return 1;                       //  <= 递归基

        return fibonacci(n-1)+fibonacci(n-2);        // <= 递归步

}

 

 

 

2 递归系列

类别-编号

题目

遁去的1

1

汉诺塔III

约19世纪末,在欧州的商店中出售一种智力玩具,在一块铜板上有三根杆,最左边的杆上自上而下、由小到大顺序串着由64个圆盘构成的塔。目的是将最左边杆上的盘全部移到右边的杆上,条件是一次只能移动一个盘,且不允许大盘放在小盘的上面。
现在我们改变游戏的玩法,不允许直接从最左(右)边移到最右(左)边(每次移动一定是移到中间杆或从中间移出),也不允许大盘放到下盘的上面。
Daisy已经做过原来的汉诺塔问题和汉诺塔II,但碰到这个问题时,她想了很久都不能解决,现在请你帮助她。现在有N个圆盘,她至少多少次移动才能把这些圆盘从最左边移到最右边?

Input

包含多组数据,每次输入一个N值(1<=N=35)。

Output

对于每组数据,输出移动最小的次数。

计算机考研—机试指南

https://blog.csdn.net/qingyuanluofeng/article/details/47186261

思路:

移动K个圆盘从第一根柱子到第三根柱子需要F[K]次。F[K]:先移动K-1个圆盘道第三根柱子需要F[k-1]次,再将最大圆盘移动到中间柱子 需要1次,再将K-1个圆盘

移回第一根柱子需要F[K-1]次,将中间柱子上的圆盘移动到第三根柱子需要1次,再将k-1个圆盘移回第三根柱子需要F[K-1]次移动,所以F[k] = 3*F[k-1] + 2

递归出口:x=1时,即移动一个盘子从第一根柱子到第三根柱子只需2次

关键:

1 确定递归方式F[k]= 3*F[k-1] + 2

2 确定递归出口k=1时,F[k] = 2

代码:

int hanNuoTa3(int n)

{

        if(n==1)

        {

                 return 2;

        }

        else

        {

                 return (3*hanNuoTa3(n-1) + 2);

        }

}

2

斐波那契数列

写一个函数,输入n,求斐波那契数列的第n项

剑指offer

https://blog.csdn.net/qingyuanluofeng/article/details/39092369

关键:

f(n)={0,n=0

     {1,n=1

         {f(n-1)+f(n-2),n>1

代码:

const int MAXSIZE = 100000;

long long fib[MAXSIZE];

long long fibonacci(int n)

{

        fib[0] = 0;

        fib[1] = 1;

        for(int i = 2 ; i <= n ; i++)

        {

        fib[i] = fib[i-1] + fib[i-2];

        }

        return fib[n];

}

3

基于递归的插入排序:

将待插入的关键字插入到已经排好序的序列中

算法设计与分析

https://blog.csdn.net/qingyuanluofeng/article/details/47189177

关键:

递归基:当数组元素个数n=1时,只有一个元素,已经是排序的

递归步:如果前面k-1个元素已经排序,只要将第k个元素逐渐与

前面k-1个元素比较,把他插入到适当位置,即可完成k个元素的排序

递归的规律总结:

一般先执行递归,再执行递归体,这样就可以把基本的操作放在最后完成了。

代码:

void insertSort_recursion(int* pArr,int n)

{

        if(!pArr || n <= 0)

        {

                 return;

        }

        n--;//注意,我们保存的数组元素范围仍然是0~n-1

        int iReserveNum;//记录待比较元素

        if(n > 0)//递归主体

        {

                 insertSort_recursion(pArr,n);//先执行递归,这样最小操作会放在最后完成

                 iReserveNum = pArr[n];

                 int k = n - 1;

                 while(k >= 0 && pArr[k] > iReserveNum)//前>后,后移

                 {

                         pArr[k+1] = pArr[k];//后移

                         k--;

                 }

                 pArr[k+1] = iReserveNum;

        }

}

4

买书问题

在节假日的时候,书店一般都会做促销活动。由于《哈利波特》系列相当畅销,店长决定通过促销活动来回馈读者。上柜的《哈利波特》平装本系列中,一共有五卷。假设每一卷单独销售均需8欧元。如果读者一次购买不同的两卷,就可以扣除5%的费用,三卷则更多。假设具体折扣的情况如下:

本数   折扣

2      5%

3      10%

4      20%

5      25%

在一份订单中,根据购买的卷数及本数,就会出现可以应用不同折扣规则的情况。但是,一本书只会应用一个折扣规则。比如,读者一共买了两本卷一,一本卷二。那么,可以享受到5%的折扣。另外一本卷一则不能享受折扣。如果有多种折扣,希望计算出的总额尽可能的低。

要求根据以上需求,设计出算法,能够计算出读者所购买的一批书的最低价格。

编程之美

https://blog.csdn.net/qingyuanluofeng/article/details/39272993

关键:

1 int _iCostArr[MAXSIZE][MAXSIZE][MAXSIZE][MAXSIZE][MAXSIZE];//用作记忆化搜索,原来可以设置5维数组

2 void my_sort(int& a,int& b,int& c,int& d,int& e)//注意这些值返回后继续用的,所以要用引用

  a = _iSortArr[0];//注意再重新返回a,因此形参要用引用

3  my_sort(a,b,c,d,e);//按照从大到小排序

 int& iRet = _iCostArr[a][b][c][d][e];//注意,这里使用引用,可以提高速度,并且会用iRes为cost赋值

 if(iRet != -1)//记忆化搜索,如果已经求解过,就直接返回,避免重复递归

 {

  return iRet;

4 if(a >= 1 && b < 1)//如果只剩下一个种类的书了,那么最大值自然就是,没有优惠,自己付钱

5  else if(a >= 1 && e >= 1)//a,b,c,d,e均有剩余,可以享受7.5折

 {

  return iRet =  min(5*8*75 + buyBook(a-1,b-1,c-1,d-1,e-1), 4*8*80 + buyBook(a-1,b-1,c-1,d-1,e) ,

   3*8*90 + buyBook(a-1,b-1,c-1,d,e),2*8*95 + buyBook(a-1,b-1,c,d,e) ,

   800 + buyBook(a-1,b,c,d,e));

 

代码:

const int MAXSIZE = 15;

int _iCostArr[MAXSIZE][MAXSIZE][MAXSIZE][MAXSIZE][MAXSIZE];//用作记忆化搜索,原来可以设置5维数组

int _iSortArr[5];

 

bool compare(int a,int b)

{

        return a > b;

}

 

void my_sort(int& a,int& b,int& c,int& d,int& e)//注意这些值返回后继续用的,所以要用引用

{

         _iSortArr[0] = a;

         _iSortArr[1] = b;

         _iSortArr[2] = c;

         _iSortArr[3] = d;

         _iSortArr[4] = e;

         sort(_iSortArr,_iSortArr+5,compare);

         a = _iSortArr[0];//注意再重新返回a,因此形参要用引用

         b = _iSortArr[1];

         c = _iSortArr[2];

         d = _iSortArr[3];

         e = _iSortArr[4];

}

 

int min(int a,int b)

{

        return a < b ? a : b;

}

 

int min(int a,int b,int c)

{

        return min( min(a,b),c);

}

 

int min(int a,int b,int c,int d)

{

        return min(min(a,b),min(c,d));

}

 

int min(int a,int b,int c,int d,int e)

{

        return min( e,min( min(a,b) , min(c,d) ) );

}

 

int buyBook(int a,int b,int c,int d,int e)

{

         assert(a >= 0 && b >= 0 && c >= 0 && d >= 0 && e >=0);

         if(!a &&  !b && !c && !d && !e)//递归出口,如果每本书都是0本,那么最少的价钱自然是0

         {

          return 0;

         }

         my_sort(a,b,c,d,e);//按照从大到小排序

         int& iRet = _iCostArr[a][b][c][d][e];//注意,这里使用引用,可以提高速度,并且会用iRes为cost赋值

         if(iRet != -1)//记忆化搜索,如果已经求解过,就直接返回,避免重复递归

         {

          return iRet;

         }

         //我们应该从最大的开始算,要不然就不好求最小值了

         if(a >= 1 && b < 1)//如果只剩下一个种类的书了,那么最大值自然就是,没有优惠,自己付钱

         {

          return iRet = (800 + buyBook(a-1,b,c,d,e));

         }

         else if(a >= 1 && b >= 1 && c < 1)//如果a与b均剩余,其余三类书空了,可以享受95折

         {

          return iRet =  min( 2*8*95 + buyBook(a-1,b-1,c,d,e) , 800 + buyBook(a-1,b,c,d,e));

         }

         else if(a >= 1 && c >= 1 && d < 1)//如果a,b,c均有剩余,可享受9折

         {

          return iRet =  min( 3*8*90 + buyBook(a-1,b-1,c-1,d,e), 2*8*95 + buyBook(a-1,b-1,c,d,e) ,

           800 + buyBook(a-1,b,c,d,e) );

         }

         else if(a >= 1 && d >= 1 && e < 1)//a,b,c,d,均剩余,可享受8折

         {

          return iRet =  min( 4*8*80 + buyBook(a-1,b-1,c-1,d-1,e) , 3*8*90 + buyBook(a-1,b-1,c-1,d,e),

           2*8*95 + buyBook(a-1,b-1,c,d,e) , 800 + buyBook(a-1,b,c,d,e));

         }

         else if(a >= 1 && e >= 1)//a,b,c,d,e均有剩余,可以享受7.5折

         {

          return iRet =  min(5*8*75 + buyBook(a-1,b-1,c-1,d-1,e-1), 4*8*80 + buyBook(a-1,b-1,c-1,d-1,e) ,

           3*8*90 + buyBook(a-1,b-1,c-1,d,e),2*8*95 + buyBook(a-1,b-1,c,d,e) ,

           800 + buyBook(a-1,b,c,d,e));

         }

         else

         {

         }

}

5

计算字符串的相似度

对于不同的字符串。我们定义一套操作方法来把两个不相同的字符串变相同,具体方法:

1修改一个字符(如把'a'变成'b')

2增加一个字符(如把'abdd'变成'aebdd')

3删除一个字符(如把"travelling"变为"traveling")

比如,对于“abcdefg”和"abcdef"这两个字符串来说,我们认为可以通过增加/减少一个"g"的方式来达到目的。上面的两种方案都仅需要一次操作。把

这个操作需要的次数定义为两个字符串的距离,而相似度等于"距离+1"的倒数,也就是说"abcdefg" 和 "abcdef"的距离为1,相似度为1/2 = 0.5

给定任意两个字符串,你能否写出一个算法来计算他们的相似度呢?

编程之美

https://blog.csdn.net/qingyuanluofeng/article/details/47187583

分析:

两个字符串分距离肯定不超过它们的长度之和(可以通过删除操作把两个串都转化为空串)。考虑把这个问题转化为规模较小的问题。如果有两个串A=xabcdae

和B=xfdfa,它们的第一个字符时相同的,只要计算A[2,...,7]=abcdae和B[2,...,5]=fdfa的距离就可以了。但是如果两个串的第一个字符不相同,可以进行下面的

操作,lenA和lenB分别表示A串和B串的长度

1删除A串的第一个字符,然后计算A[2,...,lenA]和B[1,...,lenB]的距离

2删除B串的第一个字符,然后计算A[1,...,lenA]和B[2,...,lenB]的距离

3修改A串的第一个字符为B串的第一个字符,然后计算A[2,...,lenA]和B[2,...,lenB]的距离

4修改B串的第一个字符为A串的第一个字符,然后计算A[2,...,lenA]和B[2,...,lenB]的距离

5增加B串的第一个字符到A串的第一个字符之前,然后计算A[1,...,lenA]和B[2,...,lenB]的距离

6增加A串的第一个字符到B串的第一个字符之前,然后计算A[2,...,lenA]和B[1,...,lenB]的距离

不在乎两个字符串变得相等之后的字符串是怎样的,可以将上面6个操作合并为:

1一步操作之后,再将A[2,...,lenA]和B[1,...,lenB]变成相同字符串

2一步操作之后,再将A[1,...,lenA]和B[2,...,lenB]变成相同字符串

3一步操作之后,再将A[2,...,lenA]和B[2,...,lenB]变成相同字符串

这个递归程序有些类似于前序和中序建立后序二叉树

优化:采用记忆化搜索,设定一个剪枝数组,如果有这个值,直接返回

 

代码:

int calStrDistance(char* strA,char* strB,int iBegA,int iEndA,int iBegB,int iEndB,int g_iDisArr[][100])

{

        if(iBegA > iEndA)//如果起始位置大于终点位置,表明连最后一个字符也判断结束了,此时,计算另一个字符串中结束位置-开始位置+1,就是距离值

        {

                 if(iBegB > iEndB)

                 {

                         return g_iDisArr[iBegA][iBegB] = 0;

                 }

                 else

                 {

                         return g_iDisArr[iBegA][iBegB] = (iEndB - iBegB + 1);

                 }

        }

        if(iBegB > iEndB)

        {

                 if(iBegA > iEndA)

                 {

                         return g_iDisArr[iBegA][iBegB] = 0;

                 }

                 else

                 {

                         return g_iDisArr[iBegA][iBegB] = (iEndA - iBegA + 1);

                 }

        }

        if(strA[iBegA] == strB[iBegB])//如果两个字符相同,那么双方都继续向下各自遍历

        {

                 if(g_iDisArr[iBegA+1][iBegB+1] != -1)//如果已经计算过了,那么直接返回

                 {

                         return g_iDisArr[iBegA+1][iBegB+1];

                 }

                 else

                 {

                         return g_iDisArr[iBegA+1][iBegB+1] = calStrDistance(strA,strB,iBegA+1,iEndA,iBegB+1,iEndB,g_iDisArr);

                 }

        }

        else

        {

                 int iDis1,iDis2,iDis3;

                 if(g_iDisArr[iBegA+1][iBegB] != -1)

                 {

                         iDis1 = g_iDisArr[iBegA+1][iBegB];

                 }

                 else

                 {

                         iDis1 = g_iDisArr[iBegA+1][iBegB] = calStrDistance(strA,strB,iBegA+1,iEndA,iBegB,iEndB,g_iDisArr);//如果不同,参见删除另一个字符串的当前字符,然后双方继续向下比较

                 }

                 if(g_iDisArr[iBegA][iBegB+1] != -1)

                 {

                         iDis2 = g_iDisArr[iBegA][iBegB+1];

                 }

                 {

                         iDis2 = g_iDisArr[iBegA][iBegB+1] = calStrDistance(strA,strB,iBegA,iEndA,iBegB+1,iEndB,g_iDisArr);//如果不同,参加把一个字符串的当前字符加到另一个字符串的首位置,继续比较

                 }

                 if(g_iDisArr[iBegA+1][iBegB+1] != -1)

                 {

                         iDis3 = g_iDisArr[iBegA+1][iBegB+1];

                 }

                 else

                 {

                         iDis3 = g_iDisArr[iBegA+1][iBegB+1] = calStrDistance(strA,strB,iBegA+1,iEndA,iBegB+1,iEndB,g_iDisArr);//如果不同,参见修改一个字符串的第一个字符为另一个字符串的第一个字符

                 }

                 return g_iDisArr[iBegA][iBegB] =  ( min(iDis1,iDis2,iDis3) + 1 );//注意因为本身已经有一个值不同了,因此这里要加上1

        }

}

6

瓷砖覆盖地板

原来的地板铺有N*M块正方形瓷砖,商店只提供长方形瓷砖,现在一块长方形瓷砖相当于于原来的两块正方形瓷砖,能否用1*2的瓷砖去覆盖N*M的地板呢

编程之美

https://blog.csdn.net/qingyuanluofeng/article/details/47187829

本质:

斐波那契递推数列公式

f(i) = f(i-1) + f(i-2)

分析:

N*M的地板有以下几种可能:

1如果N=1,M为偶数的话,显然1*2的瓷砖可以覆盖1*M的地板,需要M/2块瓷砖

2如果N*M为奇数,也就是N和M都为奇数,则肯定不能用1*2的瓷砖去覆盖它

证明:

假设能共用k快1*2的瓷砖去覆盖N*M的地板,因为瓷砖总面积为2k为偶数,而地板总面积为N*M为奇数,面积不符

3N和M中至少有一个为偶数,不放设M为偶数,既然可以用1*2的地板覆盖1*M的地板,就可以重复N次覆盖1*M的做法,必定可以覆盖N*M的做法

其实一定要按照数学思

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值