1-递归在算法设计中的应用剖析
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
递归在算法设计中的应用剖析
摘要本文以一道算法设计题目为例,将递归思想贯穿始终,具体运用中则从间接递归、直接递归、动态规划以及回溯等角度进行了算法设计,对递归这一算法设计中极为常用方法和思想进行了深入的剖析,并给出了不同情况下的应用性能比较。
关键词递归剖析
1 引言
递归的最基本思想是“自己调用自己”,即直接或间接地调用自身的算法,称为递归算法。递归算法的优点是结构清晰、可读性强,而且容易用数学归纳法来证明算法的正确性,能够为算法设计和调试程序带来很大的方便。但是,其缺点也是明显的,即运行效率较低,无论是耗费的时间还是占用的存储空间都比非递归算法要多。
总体而言,递归可以用来解决两类问题,生长方向递增和生长方向递减的问题:
a) 生长方向递增,主要是指问题本身能够进行递归定义的问题。经典的如阶乘函数n!的求解。
b) 生长方向递减,主要指的是分治,即将一个难以解决的大问题分割成一些规模较小的相同问题,以便各个击破。
存在如下算法设计题:有n个数的数组,A,B两人轮流从最右或最左取一个数。刚开始,两人的得分均为零。A取下一个数,则将该数的值加到A的得分上,最后谁的得分最高谁就赢了游戏。A首先取数的情况下,假设两人都是非常聪明的,问最后两个人的得分。
本题如果采用非递归的方法分析起来较为困难,采用递归的方法则可以直观、精确地模拟整个取数过程。
2 递归算法设计
2.1 间接递归
本题最为直观的方法为交叉递归。交叉递归是间接递归中的一个典型情况。具体实现代码为:
int funA(int left, int right);//A取数
int funB(int left, int right);//B取数
int funA(int left, int right){
if ((left + 1) == right)return max(a[left], a[right]);
if (left == right)return a[left];
if (left > right)return 0; //以上为递归终止条件设置
int total = l_r_sum[left][right];//剩余区间的数的总和
int leftsum = total - funB(left + 1, right);
int rightsum = total - funB(left, right - 1);
return max(leftsum, rightsum);
}
int funB(int left, int right){
if ((left + 1) == right)return max(a[left], a[right]);
if (left == right)return a[left];
if (left > right)return 0;//以上为递归终止条件设置
int total = l_r_sum[left][right];
int leftsum = total - funA(left + 1, right);
int rightsum = total - funA(left, right - 1);
return max(leftsum, rightsum);
}
其中,整型数组l_r_sum[][]存储各个区间的总和,在递归之前计算完成,可减少递归过程中计算次数,后面的算法均会用到。
由以上算法实现可见,交叉递归能够最为直观地,精确地模拟整个取数过程。
2.2 直接递归
分析以上间接递归中的两个取数函数,可知两人取数的规则是相同的(假设两人都是非常聪明的,而且最后谁的得分最高谁就赢了游戏),这时,可以将两个取数函数用一个取数函数代替,得到直接递归的实现方法,减少代码量。
int fun_r(int left, int right) {
if ((left + 1) == right)return max(a[left], a[right]);
if (left == right)return a[left];
if (left > right)return 0; //以上为递归终止条件设置
int total = l_r_sum[left][right];//剩余区间的数的总和
int leftsum = total - fun_r(left + 1, right);
int rightsum = total - fun_r(left, right - 1);
return max(leftsum, rightsum);
}
2.3 递归转迭代
实际运行当中,发现以上两种递归实现问题空间n≤30时,运行时间尚在可接受范围之内。如何实现在n较大的情况也能在较短时间内获得正确解是本节要实现的内容。
由递归的执行过程可知,每一个递归算法都必然对应一个迭代算法,因此,可以将递归算法转换为迭代算法。
假设例题中数字个数n=5,分析直接递归函数执行过程,可得到如下的递归调用树:
图1 递归调用树
由于最底层函数的结果均可以直接得到,而上一层的函数结果也可以由下一层的函数结果得到,所以,顶层函数结果可以由底层迭代获得。根据此思路,可以得到如下的迭代算法:
int d[1000][1000];//存储递归树每个节点的返回值
int fun_d(int n){
memset(d, -1, sizeof(d));
for (int j = 0; j < n - 1; ++j)
d[j][j + 1] = max(a[j], a[j + 1]);//获取叶子节点的递归函数返回值
for (int j = 2; j < n; j++)//自低向上迭代计算
for (int i = 0; i + j < n; ++i)
d[i][i + j] = max((l_r_sum[i][i + j] - d[i + 1][i + j]),
(l_r_sum[i][i + j] - d[i][i + j - 1]));
return d[0][n - 1];
}
2.4 递归+动态规划策略
通过迭代算法中的递归调用树分析可知,以直接递归算法在计算过程中需要对每一种可能的取法进行实现,并对比结果,造成了算法效率低下。实际上,有些取法在之前的运行过程中已经确定得到了结果。虽然这个问题也可以通过将递归转为迭代算法解决,但分析过程不太直观。此时,我们采用动态规划方法,对那些已