对分治算法的几点思考

分治法是算法学习中很基础的一部分。

其基本思想是:对一个原规模为n的问题,将其分解为多个规模较小的子问题,再将这些子问题的解合并求得原问题的解。

分治法不仅仅是分(Divide)和治(Conquer),其中还有个“合(Combine)”的步骤经常会被人忽略。这也是分治法的三个基本步骤:Divide-Conquer-Combine。

让我们先看一个分治法运用的经典案例:归并排序。

Problem: 对一个无序序列{x1...xn}按从从小到大排序;

Solution: 

Divide: 原问题问n规模,将该问题逐次二分,直至递减为常数规模为止。

Conquer: 将这些小规模的排序解决。

Combine: 一一合并已排序好的小规模序列。

Analysis: 

Divide: 对k规模问题二分显然只需求一次中点即可,耗时固定。

Conquer: 假如原问题运算时间是T(n),则对二分后的两个子问题,解决时间为2T(n/2)。

Combine: 对两个已排序好的子序列,Combine的基本步骤如下:

构造一个足够大的数组,从两个子序列的首位开始,依次比较,每次将两个数中较小的数放入构造的数组中。

显然,一共会进行k次比较,k值与两个子序列大小的和有关。也就是

根据以上的分析,我们可以写出归并算法的递归式(忽略基本情况):

其复杂度是:

接下来,第一个问题:分治的关键在哪里?所有的问题都可以用分治吗?

显然不是这样,仔细观察我们会发现,Divide后产生的所有子问题,它们都具有一种相同的解决模式!这是分治算法生效的关键。


第二个问题:分治似乎总是很好,是不是满足了上述条件后,分治就一定有效呢?

这个问题似乎稍显复杂了一些,让我们举一个很典型的例子来看看:

Problem: 

Solution: 这是很常见的求矩阵乘法的问题。

首先介绍一下朴素算法,即利用公式进行计算。显然会有三重循环,也就是复杂度为

很快就有人根据分治法的思想对此作出了“改进”:

学过线性代数的人应该很清楚矩阵的分块运算,如果将矩阵分块的话,对每一小块矩阵(子问题),其运算方法是相同的(解决模式),这似乎已经满足了使用分治法的条件,但是否分治之后会更加优越呢?让我们来分析一下算法:

这是基本的分块策略:C、A、B均被分为了4个n/2×n/2的矩阵块。求解结果为:

可以看出,我们要做8次规模为n/2的矩阵乘法,4次加法。现在可以很容易的写出递归式:

糟糕的事情发生了:我们并未使得问题简化!分析递归式会发现,问题出在系数8上,很明显,我们做了过多次的子问题,子问题的数量是影响分治复杂度的一个重要指标!

你会问:有没有办法改善这个算法呢?当然有,在此不作具体讨论,提示:改善的方法就是降低系数。


第三个问题:分治未必是最佳选择?

我想说的是,对一个有效的分治算法,它未必是最好的选择。我们知道,在问题规模比较大时,分治往往能取得很好的效果,但如果问题规模小到一定程度后,分治将不再是最佳选择,事实上,对大部分问题,当规模较小的时候,分治都是较低效的做法。例如:对归并排序,我们可以选择在规模小时采取插入排序解决子问题,而不再递归的进行划分。


四、分治的实现。

分治是基于递归的一种算法,但是分治的实现就一定要用递归吗?在多数情况下,我们更愿意用循环替代递归,递归占用了过多的内存空间,使得算法不够高效。

下面两段代码均是求x^n,函数f使用递归实现,函数g使用循环实现:

public static double f(double x, int n){
		if(n>1){
			if(n%2==0){
				double d = f(x, n/2);
				return d*d;
			}	
			else if(n%2!=0){
				double d = f(x, (n-1)/2);
				return d*d*x;
			}
			else{
				return 99999;
			}
		}
		else{
			return x;
		}
	}
	public static double g(double x, int n){
		double res=x, p=0;
		if(n%2!=0){
			n--;
			p=1;
		}
		while(n!=1){
			res*=res;
			n/=2;
		}
		if(p==1){
			res*=x;
		}
		return res;
	}
明显,非递归方法更加高效且编码更加简洁。


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值