基于分治法的求最大最小问题

16 篇文章 0 订阅

问题描述,在一个数组中的所有数据均成无序排序,编程实现求取数组中的最大数值与最小数值,要求是时间复杂度越小越好,

且元素的比较的次数越小越好。


在学习分而治之算法思想之前,通常我们使用的编程语句如下:


//method for find max and min in array


void minMax ( int A [] , int &min , int &max , int n )
{
	min = A[0];
	max = A[0] ;
	for ( int i = 1 ; i < n ; i++ )
	{
		if ( A[i] > max )
		{
			max = A[i] ;
		}
		if ( A[i] < min )
		{
			min = A[i] ;
		}
	}
}

这种实现方法中时间复杂度是 O(n) ,即算法在执行的时候,对数组A中的所有元素进行扫描一次即可,

对于元素的比较次数是 2n -2 其中数值 n 表示的是数组 A 中元素的个数, 2n -2 这个数值是如何得到的呢? 

这个简单, 首先对应在算法开始的时候对数据 min , max 进行初始化的时候, 对应将数组 A 中的首个元素进行访问,

接下来对数组 A 中的 n-1 个元素进行扫描和比较, 扫描元素的个数是 n-1 ,并且每次扫描一个元素,就会将这个元素

分别于 min 和 max 两个数据值进行 2 次的比较操作, 所以一次扫描下来,总的比较次数是 2*(n-1) = 2n -2 次。


下面在使用分治法来找出最大最小元素之前,我们先来看一下,分而治之法的算法思想吧。


对于分而治之法的设计原理就是,对于一个规模为 n 的问题 P(n) , 我们可以将这个大问题分解为 k 个规模较小的子问题, 

{P(1), P(2) , ....., P(k) }这些子问题互相是独立的,并且对应的结构与大问题 P(n) 的结构是相同的。在求解这些子问题的时候,

又可以对其进行更细一步的分解,一直到分解到某一个阈值  n0 才停止分解, 而对应的达到阈值的问题规模刚好是可以

通过题目中的描述进行求解的问题规模大小,于是将分解的子问题各自进行求解, 然后再通过相应的合并函数将这些

求得的子问题的解进行合并, 所得到的合并的解,即为规模为 n 的问题 P(n) 的解。


即,


P(n) -> { P(1) , P(2) , ... , P(k)}


for i = 1 --> k

{

   yi <- func (  P(i) ) 

}



yn  <-- merge( y 1 , y2, ... yk )


yn  即是 P(n) 问题的解


其中上述的三段伪代码分别代表的是,在分治法中的三个重要的步骤: 1. 划分步骤  2. 治理步骤 3. 组合步骤

在划分步骤中, 通常是将输入的问题根据问题的描述和给定的阈值,划分成 k 个子问题 ; 一般来说通常是将这 k 个问题

划分成结构和模式相同的方式,这一点比较重要,因为相同的结构利于后续递归方法的调用,和边界条件的计算。。


在治理步中,治理步是分治法的核心所在,通常在将问题分解到达到某一个设定阈值的时候,就会将停止对这个问题继续向下划分,

并这个问题的甩给治理步骤的函数调用中来计算该子问题的解。

而之所以说治理步骤是很重要的,也是因为治理步骤是否执行,是完全取决于这个分治法中的阈值的选取的,而对于分而治之方法

中阈值的选取对算法的性能影响是很大的,所以又很多人在研究对于分而治之方法中阈值的选取这个方向的。


在统计步,统计步也叫做组合步,在这个步骤中主要进行的工作就是,将上述分解的 k 个子问题进行组合起来,使之合并之后的解

为所求的大规模问题的解。


这三个步骤是很重要的,不仅仅是因为对于这些步骤的划分将比较晦涩难懂的分治法,进行了量化,使我们在解决分治法问题的时候,

有了一套比较完整的思考方向,而且更值得学习的是,这三个步骤分别影响了一个分治法的时间复杂度。


下面我们来看一下,对于一个分治法的通用时间复杂度的求解方程


f(1) =1  (a)
f(n) = kf(n/k) + bn  ; n>1 (b) 

分而治之算法所消耗的时间 与其中治理步骤中的子算法, 组合步骤中的组算法,以及划分的子问题的个数 k 之间有着很密切的关系。

其中治理步骤中的子算法决定的是递归方程中的初始值 ,即 f( initial ) =  initial value  ---(a) .

而组合步骤中的子算法决定的是递归方程中的非齐次项的大小 。

同时对于阈值 n0 的选取决定的是将整个的问题划分成子问题的个数 : k , 其中子问题的个数决定了递归方程低阶项的系数。


好了,上面的就是分治法中比较重要的求解分析方法,下面来看一下如何使用分治法来求取一个数组中的最大最小项,以一种比较次数更少的方式:


//method for find max and min in array

#include <cstdio>
#include <cstdlib>


void minmax ( int A [] , int &min , int &max , int low , int high )
{
	int mid , x1 ,y1 , x2 , y2 ;

	if ((high-low) <= 1)
	{
		if ( A[high] > A[low] )
		{
			min = A[low] ;
			max = A[high] ;
		}

		else
		{
			max = A[low] ;
			min = A[high] ;
		}
	}

	else
	{
		mid = (low+high)/2 ;

		minmax(A , x1 , y1 , low , mid ) ;
		minmax( A , x2 , y2 , mid+1 , high) ;

		min = min_(x1 , x2) ;
		max = max_( y1 , y2 ) ;
	}
}

int main ( void )
{
	int A [] = {8 , 3 , 6 , 2 , 1 , 9 , 5 ,4,21} ;

	int min__ , max__ ;
	minmax(A , min__ , max__ , 0 , 8) ;
	printf("here is the min %d  , here is the max :%d ", min__ , max__) ;

	system("pause") ;

	return 0 ;
}



对于上述算法的总的比较次数的分析是:


C(2) = 1
C(n) = 2*C(n/2) +2 


上述的方程是整个算法所基于的递归方程,通过求解方程可以得到 C(n) = 3/2* n -2 ,所以从整体上减少了比较的次数。












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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值