中点的处理策略五大模型(减治策略和分治策略)

在算法设计与分析里,有这么两个算法,减治策略和分治策略。减治我还是第一次听说,分治之前听说过,但说实话,减治和分治什么区别,有时候还真说不上来。今天趁着这个机会,再复习一下这两个算法分析策略。

减治策略

减治(decrease-and-conquer)技术利用了一个问题给定实例的解和同样问题较小实例的解之间的某种关系。一旦建立了这种关系,我们既可以从顶向下,也可以从底向上的来运用该关系。

—- 节选自《算法设计与分析基础》

看不大懂啊,什么意思?

其实就是说,对于一个问题,减治法的思想在于将原问题拆分为更小规模的子问题,原问题的解和其中一个子问题的解有关联,不断缩小规模,然后解决小规模子问题的解,再由子问题与原问题的关系,推出原问题的解。

减治法有3种主要的变化形式:

  1. 减去一个常量
  2. 减去一个常量因子
  3. 减去的规模是可变的

减常量

在减常量变化中,每次算法迭代总是从实例中减去一个相同的常量,一般来说,这个常量等于1,但减去其他常量的情况也偶尔会出现。

中点的处理策略五大模型(减治策略和分治策略)(1)

举一个最简单的例子,计算n!,由其数学公式我们知道, n! = 1 2 3 … n,n!与(n-1)!有关,我们得到其数学公式:

|---- 1 n=0 f(n) = | |---- f(n-1)*n n>0

求解f(n),我们把问题规模减至n-1,继而求解f(n-1),同理,再减,再减。。。

我们既可以采用自顶向下,递归的方式来解决,也可以使用自底向上,迭代的方式来解决问题。

减常量策略一般用的很少,或者说提的不多,一般一个问题涉及到循环遍历,均可抽象为减常量策略,因为问题的规模确实在常量地减少。

减常量因子

减常量因子在算法的每次迭代中,总是从实例的规模中减去一个常数因子。在大多数应用中,这样的常数因子等于2。

中点的处理策略五大模型(减治策略和分治策略)(2)

举一个减常量因子最出名的例子:二分查找

二分查找也叫折半查找,这里的常量因子就是2,每次查找时,将问题的规模除以2,因此每次问题的规模都是原来规模的一半。

减可变规模

再比如求两个整数最大公约数的欧几里得算法,也是减常量因子策略的一个例子。 gcd(a,b) = gcd(b,a mod b) 。当然这里的常量因子就不是2了,而是可变的。因为每次迭代减的因子都不同。

其实减治策略思想非常简单,核心就是将问题的规模不断缩小,然后减到一个可以很简单求解的规模,然后解决子问题,再用子问题的解来推原问题的解。一般情况下,这些子问题的解和原问题有着相同或相似的解决思路。

这种问题,在实际代码上,可以采用递归,也可以采用迭代。

分治策略

分治策略很好理解,就是分而治之。

分治策略也是将原问题,拆分为规模更小的问题,然后对每个子问题进行求解,最后合并这些子问题的解得到原问题的解。

中点的处理策略五大模型(减治策略和分治策略)(3)

这里和减治策略的区别是,减治策略在拆分子问题后,会舍弃一部分子问题,而分治策略不会舍弃,而是对每个子问题都求解。

归并排序

分治策略一个常用的例子就是,归并排序。

将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。

中点的处理策略五大模型(减治策略和分治策略)(4)

如图所示,先将原无序序列分为左右两个子序列,对每个子序列递归进行归并排序,然后再将排好序的两个有序子序列合并,得到原问题的解。

快速排序

在常用的排序算法上,快速排序也是分治策略思想的一个体现。

中点的处理策略五大模型(减治策略和分治策略)(5)

快速排序是将无序的原序列分为两部分,其中一部分比另一部分都要小,然后再对这两部分递归使用快速排序,从而达到对原数列排序的目的。

分治策略对并行计算来说是十分理想的,因为各个子问题都可以由各自的CPU同时进行计算。

例题实战

上面说过了,对于二分查找,使用的是减治思想,大家也很熟悉了。那么,对于下面这样一个变种,能否使用分治思想呢?

对于一个对调有序数组,比如[4,5,6,7,8,9,1,2,3],即把一个有序数组切开,然后前后对调前后部分。如何更高效率的查找元素。 给定一个对调有序数组nums和一个要查找的值target,写一个方法进行高效率查找。返回target在nums的索引,如果未查找到,返回-1。

这个题目很有意思,是二分查找的一个变种,只不过将二分的有序数组做了下手脚,切开做了个对调。

这个题目可以用减治思想。由于这个对调数组,只切了一下,前后两部分对调,那么,当取中间位置时,其左右必然有一个部分是正常的有序区间。

比如上面的[4,5,6,7,8,9,1,2,3],中间位置8,其左边是升序区间。

再换一下,比如[7,8,9,1,2,3,4,5,6],中间位置2,其右边是升序区间。

我们的思路就是,分两步减治:

  1. 先找到升序区间
  2. 如果元素在升序区间内,直接在升序区间对其进行二分查找
  3. 如果元素不在升序区间内,那么我们再对剩下的非升序区间进行步骤1操作。
  4. 找到元素或区间缩小至只有一个元素时结束查找。

代码实现:

func binarySearch(nums []int, start, end int, target int) int { if start > end { return -1 } mid := (start end) / 2 if nums[mid] == target { return mid } if nums[mid] > target { return binarySearch(nums, start, mid-1, target) } if nums[mid] < target { return binarySearch(nums, mid 1, end, target) } return -1 } func search(nums []int, start, end int, target int) int { if start >= end { if nums[start] == target { return start } return -1 } mid := (start end) / 2 // 找出升序区间 ascStart, ascEnd := start, mid nStart, nEnd := mid 1, end if nums[mid] < nums[end] { ascStart = mid ascEnd = end nStart = start nEnd = mid - 1 } // 判断target是否在升序区间内,如果在,直接二分 if target >= nums[ascStart] && target <= nums[ascEnd] { return binarySearch(nums, ascStart, ascEnd, target) } // 如果不在,则继续在剩余非升序区间查找 return search(nums, nStart, nEnd, target) }

总结

其实,不管是减治策略还是分治策略,其核心都是将问题的规模减小到一定程度,然后去解决小问题,解决完小问题,再根据小问题与原问题的关联来解决大问题。这也是为啥很多人把二分查找也归为分治策略的原因,因为其本质差不多的。所以,有时候也没必要纠结名词的问题,减治还是分治,都无所谓啦,重要的是,将大规模问题拆解为小规模问题这种思想。

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页