递归与分治策略

  1、全排列问题: 设R(n)={r[1],r[2],…,r[n]}是要进行排列的n个元素。集合X中元素的全排列记为Perm(X)。求R(n)的全排列Perm(R(n))。
  用递归算法求解:
  (1)找出递归子结构性质:即原问题的解包含了子问题的解,且子问题的描述与原问题相同。这就可以用子问题的解来构造原问题的解。设R(i)=R(n)-{r[i]},这是一个子问题。设(r[i])Perm(X)表示在全排列Perm(X)的每一个排列前加上前缀r[i]得到的排列。则Perm(R(n))由(r[1])Perm(R(1)),(r[2])Perm(R(2)),...,(r[n])Perm(R(n))构成。这样我们就用子问题的解定义出原问题的解,只不过这里用的是多个子问题的解,很多简单的问题只用一个子问题的解就可定义原问题的解,比如常见的用f(n-1)定义f(n)。
  (2)找出递归终止条件:设计递归算法时必须要有递归终止条件,否则就不能使用递归算法来求解。终止条件必须是一个不再依赖于子问题的可计算出的值。这里n=1时递归终止,这时集合中只有一个元素,因此Perm(R(1))={r[1]},不再依赖于子问题了。

  算法递归地产生所有前缀是r[0]r[1]...r[k-1],后缀是r[k]...r[m]的全排列。算法将r[k]~r[m]中的每一个元素与r[k]交换,然后递归地计算r[k+1]~r[m]的全排列,并将结果作为r[0]...r[k]的后缀。调用Perm(r,0,n-1)即得原问题的解。
  2、整数划分问题: 将一个正整数n表示成一系列正整数之和,n=n[1]+n[2]+...+n[k],其中n[1]>=n[2]>=...>=n[k]>=1,k>=1。正整数n的一个这种表示称为n的一个划分。求n的不同划分个数。
  用递归算法求解:
  (1)递归子结构性质:显然n的一个划分中包含了某个子问题t(<n)的一个划分。这里问题的解是划分数,关键是要研究用子问题的划分数来定义原问题的划分数。在n的所有不同划分中,将其中最大加数n[1]不大于m的划分数个记作q(n,m),则q(n,n)为原问题的解。递归定义如下:
  1)当n<1或m<1时,显然q(n,m)=0;
  2)当m=1时,n只有一种划分形式,n=1+1+...+1,共n个1相加。q(n,1)=1;
  3)当n=1时,显然也只有一种划分形式,q(1,m)=1;
  4)当n<m时,因为n[1]不能大于n,故q(n,m)=q(n,n);
  5)当n=m时,q(n,n)为n的所有划分个数,它由n[1]<=n-1的划分和n[1]=n的划分(只有一个)组成,q(n,n)=q(n,n-1)+1;
  6)当n>m>1时,n[1]不大于m的划分,由n[1]<=m-1的划分和n[1]=m的划分组成,q(n,m)=q(n,m-1)+q(n-m,m)。
  (2)递归终止条件:上述1),2),3)即为递归的终止条件。在分析递归终止条件时特别注意要使所有的子问题都能递归终止,否则如果某个子问题不能递归终止,会导致无穷的递归调用。

  递归算法结构简洁清晰,可读性强,但运行时间和占用的存储空间一般都比非递归算法要多。系统是通过栈结构来完成递归的展开,因此若要把递归算法转化为非递归算法,通常是采用一个用户定义的栈来模拟系统的调用工作栈,以达到消除递归的目的。
  3、线性时间选择问题: 给定n个元素a[0],a[1],...,a[n-1],在线性时间内找出其中第k小的元素,0<=k<=n-1。
  一种方法是对n个元素进行排序,然后直接取出第k小的元素,比如用快速排序,但这需要O(nlogn)的运行时间,不符合要求。我们可以改进快速排序算法来达到O(n)的运行时间,快速排序用到了分治法的思想。
  分治法的基本思想是将一个规模为n的问题分解为m个规模较小的子问题,这些子问题互相独立且与原问题相同,分别解决各个子问题后,然后将各子问题的解合并得到原问题的解。可见分治法也是用各个子问题的解来构造原问题的解,相当于一种特殊的递归算法,只不过一般的递归算法中各个子问题不一定相互独立,可能两个子问题会同时依赖于第三个子问题。比如上述的q(50,6),q(50,5)都会递归展开到q(40,3),因此这个子问题会被重复计算(要消除重复计算需要用动态规划算法)。可见,分治法程序一般是一个自顶向下的递归算法。当然对一些简单的问题,我们也可以自底向上地以非递归的方式计算问题的解,以提高运行效率。
  二分搜索、快速排序、归并排序都是分治法的典型应用。
  (1)问题分解:设用Select(a[],0,n-1,k)返回原问题的解,则可以把它划分成一系列子问题Select(a[],p,r,k)。从大量的实践来看,分治法中的问题分解一般应使各个子问题的规模大致相同,这样最有效率。这里我们采用与快速排序类似的思想,但需要做改进,即把原问题划分成2个规模相当的子问题,这样才能达到O(n)的运行时间。
  快速排序中是以a[p]为划分基准,将a[p:r]划分成3段a[p:q-1],a[q]和a[q+1:r],前一段的任一元素都小于a[q],后一段的任一元素都大于a[q],q在划分过程确定。但这会导致O(nlogn)的平均运行时间,因为这个划分基准不对称,它不能保证子问题a[p:q-1]和a[q+1:r]的规模大致相同。如果我们从a[p:r]中随机地选择一个元素a[u](p<=u<=r)作为划分基准,这样的划分就是对称的,可以证明对称的划分能够在O(n)的平均时间内找出第k小的元素。这样原问题就分解成了两个子问题Select(a[],p,q,k)和Select(a[],q+1,r,k)。

  这里Random(p,r)产生p和r之间的一个随机整数,且产生不同整数的概率相同。
  (2)子问题求解:由于用两个子问题来定义原问题,完成了递归定义,因此可直接对两个子问题进行递归求解,这里的问题求解就是对数组进行排序。p=r时递归终止,直接返回a[p]。
  (3)合并子问题的解:由于排序是就地进行的,在两个子问题都排好序时无需执行任何合并操作,原问题就已经排好序,然后直接返回第k小的元素即可。在归并排序中就有对子问题解的合并过程。

  可以证明这个分治算法的平均运行时间为O(n),在最坏情况下则为O(n**2),例如在找最小元素时总是在最大元素处划分。
  递归算法基本思想:找出递归子结构性质(原问题的解包含了子问题的解)、用子问题的解来递归定义原问题的解、找出递归终止条件。
  分治法基本思想:问题分解(分解成k个规模大致相同的子问题)、子问题递归求解、合并各个子问题的解。

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值