递归与分治策略(2)

5整数划分问题

将正整数n表示成一系列正整数之和:n=n1+n2+…+nk,其中n1≥n2≥…≥nk≥1,k≥1。

正整数n的这种表示称为正整数n的划分。正整数n的不同划分个数称为正整数n的划分数,记作p(n)。

例如正整数6有如下11种不同的划分,所以p(6) = 11:

6;

5+1;

4+2,4+1+1;

3+3,3+2+1,3+1+1+1;

2+2+2,2+2+1+1,2+1+1+1+1;

1+1+1+1+1+1。

前面的几个例子中,问题本身都具有比较明显的递归关系,因而容易用递归函数直接求解。

在本例中,如果设p(n)为正整数n的划分数,则难以找到递归关系,因此考虑增加一个自变量:在正整数n的所有不同划分中,将最大加数n1不大于m的划分个数记作q(n,m)。可以建立q(n,m)的如下递归关系。

(1) q(n,1)=1,n >= 1;当最大加数n1不大于1时,任何正整数n只有一种划分形式,

即n = 1 + 1 + 1 + … +1.

(2) q(n,m) = q(n,n),m >= n; 最大加数n1实际上不能大于n。因此,q(1,m)=1。(3) q(n,n)=1 + q(n,n-1); 正整数n的划分由n1=n的划分和n1 ≤ n-1的划分组成。

(4) q(n,m)=q(n,m-1)+q(n-m,m),n > m >1;正整数n的最大加数n1不大于m的划分由 n1 = m的划分和n1 ≤ m-1 的划分组成。

前面的几个例子中,问题本身都具有比较明显的递归关系,因而容易用递归函数直接求解。

在本例中,如果设p(n)为正整数n的划分数,则难以找到递归关系,因此考虑增加一个自变量:将最大加数n1不大于m的划分个数记作q(n,m)。可以建立q(n,m)的如下递归关系。

q(n,m) = 1 n = 1, m = 1

q(n,m) = q(n,n) n = 1, m = 1

q(n,m) = 1 + q(n,n-1) n = m

q(n,m) = q(n,m-1) + q(n-m,m) n > m > 1

正整数n的划分数p(n) = q(n,n)。

实现:

/* 主题:整数划分使用递归实现* 作者:chinazhangjie* 邮箱:chinajiezhang@gmail.com* 开发语言:C++* 开发环境:Code::Blocks 10.05* 时间: 2010.10.07*/#include <iostream>using namespace std;//int __int_partition(int n,int m){    if (n < 1 || m < 1)        return 0;    if (n == 1 || m == 1)        return 1;    if (n < m)        return __int_partition(n,n);    if (n == m)        return __int_partition(n,m - 1) + 1;    return __int_partition(n,m - 1) + __int_partition(n - m,m);}int integer_partition(int n){    return __int_partition(n,n);}int main(){    for (int i = 1; i < 7; ++ i) {        cout << "integer_patition("             << i             << ") = "             << integer_partition(i)             << endl;    }    return 0;}

例6Hanoi塔问题

设a,b,c是3个塔座。开始时,在塔座a上有一叠共n个圆盘,这些圆盘自下而上,由大到小地叠在一起。各圆盘从小到大编号为1,2,…,n,现要求将塔座a上的这一叠圆盘移到塔座b上,并仍按同样顺序叠置。在移动圆盘时应遵守以下移动规则:

规则1:每次只能移动1个圆盘;

规则2:任何时刻都不允许将较大的圆盘压在较小的圆盘之上;

规则3:在满足移动规则1和2的前提下,可将圆盘移至a,b,c中任一塔座上。

递归与分治策略(2)

实现:

/* 主题:hanoi使用递归实现* 作者:chinazhangjie* 邮箱:chinajiezhang@gmail.com* 开发语言:C++* 开发环境:Code::Blocks 10.05* 时间: 2010.10.07*/#include <iostream>using namespace std;void __move(char t1,char t2){    cout << t1 << " -> " << t2 << endl;}// 把n个圆盘,从t1塔移至t2塔通过t3塔void hanoi(int n,char t1,char t2,char t3){    if (n > 0) {        hanoi(n-1,t1,t3,t2);        __move(t1,t2);        hanoi(n-1,t3,t2,t1);    }}int main(){    cout << "hanoi(1,'a','b','c'): " << endl;    hanoi(1,'a','b','c');    cout << endl;    cout << "hanoi(1,'a','b','c'): " << endl;    hanoi(2,'a','b','c');    cout << endl;    cout << "hanoi(3,'a','b','c'): " << endl;    hanoi(3,'a','b','c');    cout << endl;    return 0;}

递归小结

优点:结构清晰,可读性强,而且容易用数学归纳法来证明算法的正确性,因此它为设计算法、调试程序带来很大方便。

缺点:递归算法的运行效率较低,无论是耗费的计算时间还是占用的存储空间都比非递归算法要多。

解决方法:在递归算法中消除递归调用,使其转化为非递归算法。

1、采用一个用户定义的栈来模拟系统的递归调用工作栈。该方法通用性强,但本质上还是递归,只不过人工做了本来由编译器做的事情,优化效果不明显。

2、用递推来实现递归函数。

3、通过变换能将一些递归转化为尾递归,从而迭代求出结果。

后两种方法在时空复杂度上均有较大改善,但其适用范围有限。

分治法的适用条件

分治法所能解决的问题一般具有以下几个特征:

1、该问题的规模缩小到一定的程度就可以容易地解决;

2、该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质

3、利用该问题分解出的子问题的解可以合并为该问题的解;

4、该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。(这条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然也可用分治法,但一般用动态规划较好。)

分治法的基本步骤

divide-and-conquer(P)

{

if (|P| <= n0) adhoc(P); // 解决小规模的问题

divide P into smaller subinstances P1,P2,...,Pk;//分解问题

for (i=1,i<=k,i++)

yi=divide-and-conquer(Pi); //递归的解各子问题

return merge(y1,...,yk); //将各子问题的解合并为原问题的解

}

人们从大量实践中发现,在用分治法设计算法时,最好使子问题的规模大致相同。即将一个问题分成大小相等的k个子问题的处理方法是行之有效的。这种使子问题规模大致相等的做法是出自一种平衡(balancing)子问题的思想,它几乎总是比子问题规模不等的做法要好。

分治法的复杂性分析

一个分治法将规模为n的问题分成k个规模为n/m的子问题去解。设分解阀值n0=1,且adhoc解规模为1的问题耗费1个单位时间。再设将原问题分解为k个子问题以及用merge将k个子问题的解合并为原问题的解需用f(n)个单位时间。用T(n)表示该分治法解规模为|P| = n的问题所需的计算时间,则有:

递归与分治策略(2)

通过迭代法求得方程的解:

递归与分治策略(2)

二分搜索技术

给定已按升序排好序的n个元素a[0:n-1],现要在这n个元素中找出一特定元素x。

分析:

1、该问题的规模缩小到一定的程度就可以容易地解决;

2、该问题可以分解为若干个规模较小的相同问题;

3、分解出的子问题的解可以合并为原问题的解;

4、分解出的各个子问题是相互独立的。

很显然此问题分解出的子问题相互独立,即在a[i]的前面或后面查找x是独立的子问题,因此满足分治法的第四个适用条件。

二分搜索实现:

/* 主题:二分搜索* 作者:chinazhangjie* 邮箱:chinajiezhang@gmail.com* 开发语言:C++* 开发环境:Code::Blocks 10.05* 时间: 2010.10.07*/#include <iostream>using namespace std;// 查找成功返回value索引,查找失败返回-1template <class T>int binary_search(T array[],const T& value,int left,int right){    while (right >= left) {        int m = (left + right) / 2;        if (value == array[m])            return m;        if (value < array[m])            right = m - 1;        else            left = m + 1;    }    return -1;}int main(){    int array[] = {0,1,2,3,4,5,6,7,8,9};    cout << "0 in array position: " << binary_search(array,0,0,9) << endl;    cout << "9 in array position: " << binary_search(array,9,0,9) << endl;    cout << "2 in array position: " << binary_search(array,2,0,9) << endl;    cout << "6 in array position: " << binary_search(array,6,0,9) << endl;    cout << "10 in array position: " << binary_search(array,10,0,9) << endl;    return 0;}

算法复杂度分析:

每执行一次算法的while循环, 待搜索数组的大小减少一半。因此,在最坏情况下,while循环被执行了O(logn) 次。循环体内运算需要O(1) 时间,因此整个算法在最坏情况下的计算时间复杂性为O(logn) 。

大整数的乘法

请设计一个有效的算法,可以进行两个n位大整数的乘法运算

小学的方法:O(n^2) 效率太低

分治法:

X = a b;

Y = c d;

X = a2^(n/2) + b Y = c2^(n/2) + d

XY = ac2^n + (ad + bc)2^(n/2) + b*d

算法复杂度分析:

T(n) = O(1) n = 1

T(n) = 4T(n/2) + O(n) n > 1

T(n) = O(n^2)没有改进

为了降低时间复杂度,必须减少乘法的次数

(1)XY = ac2^n + ((a-b)(d-c)+ac+bd)2^(n/2) + b*d

(2)XY = ac2^n + ((a+b)(d+c)-ac-bd)2^(n/2) + bd

细节问题:两个XY的复杂度都是O(nlog3),但考虑到a+b,d+c可能得到n+1位的结果,使问题的规模变大,故不选择第2种方案。

算法复杂度分析:

T(n) = O(1) n = 1

T(n) = 3T(n/2) + O(n) n > 1

T(n) = O(n^log3) = O(n^1.59)较大的改进

小学的方法:O(n^2) 效率太低

分治法: O(n^1.59) 较大的改进

更快的方法?? 如果将大整数分成更多段,用更复杂的方式把它们组合起来,将有可能得到更优的算法。

Strassen矩阵乘法

对于两个n*n的矩阵A,B,求其乘积

传统方法:O(n^3)

A和B的乘积矩阵C中的元素C[i,j]定义为递归与分治策略(2)

若依此定义来计算A和B的乘积矩阵C,则每计算C的一个元素C[i][j],需要做n次乘法和n-1次加法。因此,算出矩阵C的个元素所需的计算时间为O(n^3)

分治法:

使用与上例类似的技术,将矩阵A,B和C中每一矩阵都分块成4个大小相等的子矩阵。由此可将方程C=AB重写为:

递归与分治策略(2)

由此可得:

递归与分治策略(2)

算法复杂度分析

T(n) = O(1) n = 2

T(n) = 8T(n/2) + O(n^2) n > 2

T(n) = O(n^3)

为了降低时间复杂度,必须减少乘法的次数。

递归与分治策略(2)

算法复杂度分析

T(n) = O(1) n = 2

T(n) = 7*T(n/2) + O(n^2) n > 2

T(n) = O(n^log7) = O(n^2.81)较大的改进

更快的方法??

Hopcroft和Kerr已经证明(1971),计算2个2×2矩阵的乘积,7次乘法是必要的。因此,要想进一步改进矩阵乘法的时间复杂性,就不能再基于计算2×2矩阵的7次乘法这样的方法了。或许应当研究3×3或5×5矩阵的更好算法。

在Strassen之后又有许多算法改进了矩阵乘法的计算时间复杂性。目前最好的计算时间上界是 O(n^2.376)

是否能找到O(n^2)的算法?

参考资料 《算法设计与分析基础》 王晓东 编著

授课教师 张阳教授
原文链接: https://www.cnblogs.com/chinazhangjie/archive/2010/10/07/1845159.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/15851

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月7日 下午3:54
下一篇 2023年2月7日 下午3:55

相关推荐