算法分析与设计(第2版)递归与分治策略
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
问题核心: 子问题的重复计算
public static int fibonacci(int n) 1 n=0 { if(n<=1) return 1; else return fibonacci(n-1)+fibonacci(n-2); n! = n(n-1)! n>0 } public static int factorial(int n) { if(n==0) return 1; else return n*factorial(n-1); } 例2.2 Fibonacci数列 1 n=0,1
假设X和Y是n位的二进制数,且n是2的幂,则XY的位数 不大于2n。现将X和Y分成位数均位n/2的两段,从而把计算 XY的一个n位乘以n位的问题分解成4个n/2位乘以n/2位的子 问题。 X = a 2n/2 + b Y = c 2n/2 + d XY = ac 2n + (ad+bc) 2n/2 + bd 该算法的时间复杂度可以通过求解递归方程 O(1) n=1 T(n)= 4T(n/2)+O(n) n>1 来获得。
1 2 3 4
2 1 3 4
例2.5 正整数的划分问题 对于正整数n,若存在正整数n1,n2,…,nk,使得 n= n1+n2+…+nk 则称n1,n2,…,nk是n的一个划分。正整数n的不同划分数称 为n的划分数,记为p(n)。 在正整数n的不同划分中,将最大加数不大于m的划分 个数记作q(n,m),则 1 n=1,m=1 q(n,n) n<m q(n,m)= 1+q(n,n-1) n=m q(n,m-1)+q(n-m,m) n>m>1
要点:子问题的平衡性!
O(1) n=n0 n>n0
T(n)=
kT(n/m)+f(n)
分治法所能解决的问题一般具有以下几个特征: 该问题的规模缩小到一定的程度就可以容易地解决; 该问题可以分解为若干个规模较小的相同问题,即该 问题具有最优子结构性质利用该问题分解出的子问题的解 可以合并为该问题的解; 该问题所分解出的各个子问题是相互独立的,即子问 题之间不包含公共的子问题。
• • • • • • • • • • • •
表示6=3+1+1+1和6=4+1+1
表示6=3+2+1
• • • • • •
设n=6,则p(6)=11
• • • • • •
表示6=3+3和6=2+2+2
• • • • • •
表示6=1+1+1+1+1+1和6=6
• • • • • •
表示6=2+1+1+1+1和6=5+1
2.2 分治法的基本思想
将一个难以解决的规模为n的问题分解成k个规模较小 的子问题,这些子问题是相互独立的,并且与原问题具有 相同的性质。递归地解这些子问题,然后将这些子问题的 解合并得到原问题的解。 假设需要解决的原问题是P,其规模是|P|=n,则用分 治法解决问题P的算法结构是: divide_and_conquer(P) { if(n<=n0) adhoc(P); else { divide P into smaller subinstances P1,P2,…,Pk; for(i=1;i<=k;i++) yi=divide_and_conquer(Pi); return merge(y1,y2,…,yk); } }
排列问题的另一种解法(以n=4为例)
1 1 1 4 4 1 1 1 3 3 3 4
2 2 4 1 1 4 3 3 1 1 4 3
3 4 2 2 3 3 4 2 2 4 1 1
4 3 3 3 2 2 2 4 4 2 2 2
4 3 3 3 2 2 2 4 4 2 2 2
3 4 2 2 3 3 4 2 2 4 1 1
F(n) =
F(n-1)+F(n-2) n>1
2 n=1,m=0 例2.3 Ackerman函数 1 n=0,m≥0 A(n,m) = n+2 n≥2,m=0 问题核心: A(A(n-1,m),m-1) n,m>1 子问题的重复计算
public static long ackerman(int n,int m) { if(n=1&&m==0) return 2; else if(n==0&&m>=0) return 1; else if(n>=2&&m==0) return n+2; else return ackerman(ackerman(n-1,m),m-1); }
表示6=2+2+1+1和6=4+2 能够从上面的分析中找到 什么规律?
例2.6 Hanoi问题 A B C
A
B
C
前n-1张盘 第n张盘
int count; void a_step(int m,char from,char to) { printf(“step%4d is %4d: %c---> %c\n”,++count,m,from,to); } void move(int n,char f,char t,char u) { if(n==1) a_step(1,f,t); /* 把第1张盘从f直接移到t */ else { move(n-1,f,u,t); /* 把前n-1张盘从f,借助t,移到u */ a_step(n,f,t); /* 把第n张盘从f直接移到t */ move(n-1,u,t,f); /* 把前n-1张盘从u,借助f,移到t */ } } /* 把n张盘从f,借助u,移到t */ main() { int n; count=0; printf(“Please input n:”); scanf(“%d”,&n); move(n,’A’,’C’,’B’); }
例2.4 排列问题
设R={r1,r2,…,rn}是要进行排列的n个元素,R的全排 列记为perm(R),Ri=R-{ri},(ri)perm(Ri)表示集合Ri的全 排列中每个排列前增加一个前缀所形成的所有排列。于是 当n=1时,perm(R)=(r),其中r是R中的唯一元素; 当n>1时,perm(R)由(r1)perm(R1), (r2)perm(R2),…, (rn)perm(Rn)构成。 显然, O(1) n=1 T(n)= nT(n-1)+O(n) n>1
递归小结
• 优点:结构清晰,可读性强,而且容易 用数学归纳法来证明算法的正确性,因 此它为设计算法、调试程序带来很大方 便。
• 缺点:递归算法的运行效率较低,无论 是耗费的计算时间还是占用的存储空间 都比非递归算法要多。
关于递归算法 必须进一步思考和研究的问题:
递归的消除
即递归算法到非递归算法的转化
分析:很显然此问题分解出的子问题相互独立,即在a[i]的前面 或后面查找x是独立的子问题,因此满足分治法的第四个适用条 件。
public static int binarysearch(int []a,int x,int n) { int left=0; int right=n-1; while(left<=right) { int middle=(left+right)/2; if(x==a[middle]) return middle; else if(x<a[middle]) right=middle-1; else left=middle+1; } return -1; } 思考题:给定a,用二分法设计出求an的算法。
T(n)
=
n
T(n/k)
T(n/k)
T(n/k)
T(n/k)
对这k个子问题分别求解。如果子问题的规模仍然不够小, 则再划分为k个子问题,如此递归的进行下去,直到问题规模足 够小,很容易求出其解为止。
T(n)
n/2
=wenku.baidu.com
n/2
n
n/2 n/2
T(n/4)T(n/4)T(n/4)T(n/4) T(n/4)T(n/4)T(n/4)T(n/4) T(n/4)T(n/4)T(n/4)T(n/4) T(n/4)T(n/4)T(n/4)T(n/4)
这条特征涉及到分治法的效率,如果各子问题是不独立的, 则分治法要做许多不必要的工作,重复地解公共的子问题, 此时虽然也可用分治法,但一般用动态规划较好。
2.3 二分搜索技术
给定已按升序排好序的n个元素a[0:n-1],现要在这n个元素中 找出一特定元素x。 分析:
该问题的规模缩小到一定的程度就可以容易地解决; 该问题可以分解为若干个规模较小的相同问题; 分解出的子问题的解可以合并为原问题的解; 分解出的各个子问题是相互独立的。
2 n=1,m=0 例2.3 Ackerman函数 1 n=0,m≥0 A(n,m) = n+2 n≥2,m=0 问题核心: A(A(n-1,m),m-1) n,m>1 子问题的重复计算 public static long ackerman1(int n,int m) { for(i=0; i<=n; i++) for(j=0; j<=m; j++) if(i=1&&j==0) a[i][j]=2; else if(i==0&&i>=0) a[i][j]=1; else if(i>=2&&j==0) a[i][j]=n+2; else a[i][j]=a[a[i-1][j]][j-1]; return a[n][m]; }
将求出的小规模的问题的解合并为一个更大规模的问题的解, 自底向上逐步求出原来问题的解。
分治法的设计思想是,将一个难以直接解决的大问题, 分割成一些规模较小的相同问题,以便各个击破, 分而治之。 凡治众如治寡,分数是也。 ----孙子兵法
2.1 递归的概念
递归算法: 直接或者间接调用自身的算法。 递归算法实现过程中的堆栈问题及其对算法复杂性的 影响。 例2.1 阶乘函数
2 2 4 1 1 4 3 3 1 1 4 3
1 1 1 4 4 1 1 1 3 3 3 4
算法的思想: 1. 给排列中的每个元素均赋予 一个向左或向右的箭头。 2. 如果元素k的箭头指向的是与 其相邻但小于k的元素,则称元 素k是活动的。 3. 从排列 1 2 3 … n 开始,找 其中的最大活动元素k,将该元 素k与它所指向的相邻元素交换 位置,并改变所有大于k的元素 的方向。
0 list
前缀
k
i
m
public static void perm(Objict []list, int k, int m) { if(k==m) { for(i=0; i<=m; i++) System.out.print(list[i]); System.out.println(); } else for(i=k; i<=m; i++) { MyMath.swap(list,k,i); perm(list,k+1,m); MyMath.swap(list,k,i); } }
public static int q(int n,int m) { if(n<1||m<1) return 0; else if(n==1||m==1) return 1; else if(n<m) return q(n,n); else if(n==m) return q(n,m-1)+1; else return q(n,m-1)+q(n-m,m); } 关于正整数划分问题的再思考
第2章 递归与分治策略
递归和分治的思想方法: 将一个难以解决的规模较大的问题,分割成一些规 模较小的求解思路一致的子问题,以便各个击破,分而 治之。如果原问题可以分割成k个子问题,1<k≤n,并 且这些子问题都是可解的,同时可以利用这些子问题的 解求出原问题的解,那么这种分治便是可行的。 递归函数论:作为一种计算模型,它的计算能力等 价于Turing机。
要点:字典中的元素按照查找关键子有序。
通过一次比较排除字典中的一半元素,从而使得 算法的时间复杂度为O(logn)。
2.4 大整数的乘法
首先以二进制数的乘法为例说明计算机中实现乘法的 算法的思想方法。假设被乘数是10110,乘数是01101。
10110 乘数 01101 积 0000000000 被乘数10110 × 01101 0101100000 (1加右移) 10110 0010110000 (0右移) 00000 0110111000 (1加右移) 10110 1000111100 (1加右移) 10110 0100011110 (0右移) + 00000 积100011110 算法的思想:初始化积为全0;由低向高取 乘数的每一位;当乘数的当前位是1时,将 该算法的 被乘数加在积的高位部分,然后右移积一 时间复杂度是 位;否则只右移积一位;直到取完了乘数的 O(n*n) 所有位。