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个圆盘构成的塔。目的是将最左边杆上的盘全部移到右边的杆上,条件是一次只能移动一个盘,且不允许大盘放在小盘的上面。 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的做法 其实一定要按照数学思 |