分治算法详解

前面已经介绍过MapReduce框架,MapReduce就是用的分治算法
MapReduce如何让数据完成一次旅行

如何理解分治算法

分治算法核心是:分而治之,就是将原问题划分成n个规模较小,并且和原问题相似的子问题,递归的去解决这些问题,然后将结果合并,最后得到原问题的答案。

当然,分治算法和昨天说到的贪心算法本质是差不多的,都是一种处理问题的思想,并非编程的框架。从实际上来说,分治算法一般都适合用递归来实现。

分治算法的递归实现中,每一层递归都包含了这样三个操作:

  1. 分解:将原问题分解成一系列子问题。
  2. 解决:递归地求解各个子问题,若子问题足够小,则直接求解。
  3. 合并:将子问题的结果合并成原问题。

分治算法原则如下:

  1. 原问题和子问题使用相同的计算模式
  2. 子问题之间相互独立,不会相互影响
  3. 当子问题足够小,可以直接求解出答案
  4. 子问题解决后,还可以合并为原问题期望的结果,而且合并的时间复杂度要低于原问题直接解决的时间复杂度

分治算法应用举例

首先先引入一下有序度和逆序度的概念:
有序度: 有序度是数组中具有有序关系的元素对的个数。有序元素对用数学表达式表示就是这样:

有序元素对a[i]<=a[j],如果i<j

在这里插入图片描述
当拥有一个单调递增序列的时候,那么有序度就是最大化的,为n*(n-1)/2,这种情况可以达到满有序度。

逆序度: 概念和有序度相反。。。

用冒泡排序举个例子:
在这里插入图片描述
回到开始的问题:

  • 假设有n个int类型的数据,如何用代码求出一组数据的有序对个数或者逆序对个数呢???

首先说一下大家普遍都知道的解决办法:来个n²复杂度,满足条件就存入map,不满足就不存。最后print一下就好了。但是还有没有更好的办法呢????

使用分治算法,思路如下:

  1. 将这些数据放入数组,将数组拆成前后两半A1、A2
  2. 计算A1和A2的逆序对个数K1和K2
  3. 计算A1和A2之间的逆序对个数K3
  4. 最后答案就是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();
                }
            }
        }
    }
}
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值