之前我们介绍过了MapReduce框架,而MapReduce框架的本质就是分治算法,
附上链接MapReduce如何让数据完成一次旅行
如何理解分治算法
分治算法的核心就是,分而治之,也就是将原问题划分成为n个规模比较小,并且和原问题相似的子问题,递归的解决这些子问题,然后再合并结果,得到原问题的解。
当然,分治算法和昨天说的贪心算法的本质也是差不多的,都是一种处理问题的思想,而不是编程的框架。从实际上来说,分治算法一般都适合用递归来实现。
在分治算法的递归实现中,每一层递归都包含了这样三个操作:
- 分解:将原问题分解成一系列子问题。
- 解决:递归地求解各个子问题,若子问题足够小,则直接求解。
- 合并:将子问题的结果合并成原问题。
分治算法的原则如下:
- 原问题和子问题使用相同的计算模式
- 子问题之间相互独立,不会相互影响
- 当子问题足够小,可以直接求解得出答案
- 子问题解决后,还可以合并为原问题期望的结果,而且合并的时间复杂度要低于原问题直接解决的时间复杂度
分治算法应用举例
在这里,我们先引入一下有序度和逆序度的概念:
有序度:有序度是数组中具有有序关系的元素对的个数。有序元素对用数学表达式表示就是这样:
有序元素对a[i]<=a[j],如果i<j
而当我们拥有一个单调递增序列的时候,那么有序度就是最大化的,为n*(n-1)/2,这种情况可以达到满有序度。
逆序度:概念和有序度相反。
然后我用冒泡排序举个例子:
然后回到我们原来的话题。
- 假设我们有n个int类型的数据,如何用代码求出一组数据的有序对个数或者逆序对个数呢?
首先先说一下我们都知道的解决方法:来个n²复杂度,满足条件就存入map,不满足就不存。最后print一波就ok了。那么还有没有更加高效的方法呢?
那就是分治算法了,思路如下:
- 将这些数放入数组,将数组拆成前后两半A1 A2
- 计算A1和A2的逆序对个数K1和K2
- 计算A1与A2之间的逆序对个数K3
- 最终答案就是K1+K2+K3
但分治算法原则上要求合并的代价不能太大,那么如何计算出子问题A1与A2之间的逆序对个数呢?
这就要用到归并排序了,因为归并排序的本质就是将原本有序的小数组合并为大数组,每次调换位置我们就用一个局部变量进行统计,然后就能求出k3了。
这个问题的难点就在于归并排序上了,因为不是很多人都能想到,所以,我们的方法不能局限于此,也要去思考类似于归并排序的排序,可以自己制作一个算法。
而关于分治算法还有很多典型案例,建议多刷题。
分治算法在海量数据中的应用
当我们给10GB的文件按照某种字段排序,首先想到了就是性能问题,而且硬件本身也有一定会受到局限,所以就无法单纯的靠简单的排序算法来解决了。
要解决这种数据量大到硬件无法支撑的情况时,就要采用分治的思想。将大数据拆成小数据,然后单独加载到内存中计算,最后再合并起来去排序。
但我们也可以开阔一下思路,比如说用sql分桶方式去排序,划分区间去排序。
又或者,我们可以把数据存放在GFS等这种大数据系统上,分布式计算,加快效率。
那么为什么MapReduce就是分治思想
首先,当我们的数据大到了上百T的情况下,一台机器无论如何也无法提升效率了,所以使用集群就是大势所趋。但是MapReduce终究是一个框架,用Yarn去配合资源调度,同时自己也用JobTracker当做监控器,再通过Zookeeper做风险处理,在一台机器宕机的时候还能快速切换机器。
但更希望将MapReduce理解成一种思维,也就是分而治之的思维。