问题描述,在一个数组中的所有数据均成无序排序,编程实现求取数组中的最大数值与最小数值,要求是时间复杂度越小越好,
且元素的比较的次数越小越好。
在学习分而治之算法思想之前,通常我们使用的编程语句如下:
//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 ,所以从整体上减少了比较的次数。