前面已经介绍过MapReduce框架,MapReduce就是用的分治算法
MapReduce如何让数据完成一次旅行
如何理解分治算法
分治算法核心是:分而治之,就是将原问题划分成n个规模较小,并且和原问题相似的子问题,递归的去解决这些问题,然后将结果合并,最后得到原问题的答案。
当然,分治算法和昨天说到的贪心算法本质是差不多的,都是一种处理问题的思想,并非编程的框架。从实际上来说,分治算法一般都适合用递归来实现。
分治算法的递归实现中,每一层递归都包含了这样三个操作:
- 分解:将原问题分解成一系列子问题。
- 解决:递归地求解各个子问题,若子问题足够小,则直接求解。
- 合并:将子问题的结果合并成原问题。
分治算法原则如下:
- 原问题和子问题使用相同的计算模式
- 子问题之间相互独立,不会相互影响
- 当子问题足够小,可以直接求解出答案
- 子问题解决后,还可以合并为原问题期望的结果,而且合并的时间复杂度要低于原问题直接解决的时间复杂度
分治算法应用举例
首先先引入一下有序度和逆序度的概念:
有序度: 有序度是数组中具有有序关系的元素对的个数。有序元素对用数学表达式表示就是这样:
有序元素对a[i]<=a[j],如果i<j
当拥有一个单调递增序列的时候,那么有序度就是最大化的,为n*(n-1)/2,这种情况可以达到满有序度。
逆序度: 概念和有序度相反。。。
用冒泡排序举个例子:
回到开始的问题:
- 假设有n个int类型的数据,如何用代码求出一组数据的有序对个数或者逆序对个数呢???
首先说一下大家普遍都知道的解决办法:来个n²复杂度,满足条件就存入map,不满足就不存。最后print一下就好了。但是还有没有更好的办法呢????
使用分治算法,思路如下:
- 将这些数据放入数组,将数组拆成前后两半A1、A2
- 计算A1和A2的逆序对个数K1和K2
- 计算A1和A2之间的逆序对个数K3
- 最后答案就是K1+K2+K3
但是分治算法原则上要求合并的代价不能太大,因此如何计算出子问题A1和A2之间的逆序对个数呢????
要使用归并排序,因为归并排序本质就是将原有有序的小数组合并成大数组,每次调换位置就用一个局部变量进行统计,然后就能求出K3。
现在问题的难点就在归并排序上了,因为不是很多人都能想到这一点,因此方法不能局限于此,要去思考类似归并排序的排序,最好的是可以自己编写一个算法。。。
分治算法还有很多典型案例,建议大家多看一点。。。
分治算法在海量数据中的应用
当给出100GB的文件按照某种字段排序,首先想到的应该就是性能的问题,而且硬件本身也有一定的限制,因此无法单纯的靠简单的排序算法去解决。。。
要解决排序这种数据量很大以至于硬件无法支撑的情况时,就要采用分治算法的思想。将大数据拆成小的数据,然后单独加载到内存中计算,最后合并起来去排序。
但是还可以考虑一些其他的方法:用SQL分桶方式去排序,划分区间去排序。
再或者可以把数据放在HDFS等这种大数据系统上,分布式计算,加快效率。。。
为什么MapReduce是分治算法思想
首先当数据很大已经到了上百T的情况下,一台机器再怎么样也无法提升效率,因此使用集群就是大势所趋。。但是MapReduce终究只是一个框架,用Yarn去配合资源调度,同时用JobTracker当做监控器,再通过Zookeeper去做风险处理,在一台机器宕机时候还能快速的切换机器。。。
但是我更希望的是大家将MapReduce理解成一种思维就是分而治之的思想。。。。
总结
最后附上两个运用分治算法实现的代码案例:
1、实现棋盘的填充
package sufang;
/**
* 分治算法:棋盘
*/
public class ChessBroadProblem {
private int[][] braod; //棋盘
private int specialRow; //特殊点的行下标
private int specialCol; //特殊点的列下标
private int size;
private int type = 0;
public ChessBroadProblem(int specialRow,int specialCol,int size){
super();
this.specialRow = specialRow;
this.specialCol = specialCol;
this.size = size;
braod = new int[size][size];
}
/**
*
* @param specialRow 特殊点行下标
* @param specialCol 特殊点列下标
* @param leftRow 矩阵左边的起点行下标
* @param leftCol 矩阵左边起点列下标
* @param size 矩阵的宽或高
*/
private void ChessBroad(int specialRow,int specialCol,int leftRow,int leftCol,int size){
if (size == 1){
return;
}
int subSize = size/2;
type = type%4 + 1;
int n = type;
//假设特殊点在左上角区
if (specialRow<leftRow+subSize && specialCol<leftCol+subSize){
ChessBroad(specialRow,specialCol,leftRow,leftCol,subSize);
}else {
//不在左上角,左下角矩阵的右下角就是特殊点
braod[leftRow+subSize-1][leftCol+subSize-1] = n;
ChessBroad(leftRow+subSize-1,leftCol+subSize-1,leftRow,leftCol,subSize);
}
//特殊点在右上方
if (specialRow<leftRow+subSize && specialCol>=leftCol+subSize){
ChessBroad(specialRow,specialCol,leftRow,leftCol+subSize,subSize);
}else {
//特殊点不在右上方
braod[leftRow+subSize-1][leftCol+subSize] = n;
ChessBroad(leftRow+subSize-1,leftCol+subSize,leftRow,leftCol+subSize,subSize);
}
//特殊点在左下方
if (specialRow>=leftRow+subSize && specialCol<leftCol+subSize){
ChessBroad(specialRow,specialCol,leftRow+subSize,leftCol,subSize);
}else {
braod[leftRow+subSize][leftCol+subSize-1] = n;
ChessBroad(leftRow+subSize,leftCol+subSize-1,leftRow+subSize,leftCol,subSize);
}
//特殊点在右下角
if (specialRow>=leftRow+subSize && specialCol>=leftCol+subSize){
ChessBroad(specialRow,specialCol,leftRow+subSize,leftCol+subSize,subSize);
}else {
braod[leftRow+subSize][leftCol+subSize] = n;
ChessBroad(leftRow+subSize,leftCol+subSize,leftRow+subSize,leftCol+subSize,subSize);
}
}
//打印棋盘
public void printBoard(int specialRow,int specialCol,int size){
ChessBroad(specialRow,specialCol,0,0,size);
printResult();
}
//打印结果
private void printResult() {
for (int i=0;i<size;i++){
for (int j=0;j<size;j++){
System.out.print(braod[i][j]+"\t");
}
System.out.println();
}
}
public static void main(String[] args) {
int N = 16;
int specialRow = 0;
int specialCol = 1;
ChessBroadProblem chessBroadProblem = new ChessBroadProblem(specialRow,specialCol,N);
chessBroadProblem.printBoard(specialRow,specialCol,N);
}
}
2、实现体育赛程的填充
package sufang;
/**
* 分治算法
*/
public class SportsSchedule {
public void scheduleTable(int[][] table, int n){
if (n == 1){
table[0][0] = 1;
}else {
//填充左上区域
int m = n/2;
scheduleTable(table,m);
//填充右上区域
for (int i=0;i<m;i++){
for (int j =m;j<n;j++){
table[i][j] = table[i][j-m] + m;
}
}
//填充左下区域
for (int i = m;i<n;i++){
for (int j = 0;j<m;j++){
table[i][j] = table[i-m][j] + m;
}
}
//填充右下区域
for (int i = m;i<n;i++){
for (int j = m;j<n;j++){
table[i][j] = table[i-m][j-m];
}
}
}
}
public static void main(String[] args) {
int[][] table = new int[8][8];
int n = 8;
SportsSchedule sportsSchedule = new SportsSchedule();
sportsSchedule.scheduleTable(table,n);
int c = 0;
for (int i =0;i<n;i++){
for (int j=0;j<n;j++){
System.out.print(table[i][j]+"\t");
c++;
if(c%n==0){
System.out.println();
}
}
}
}
}