分治法

分治法是一种算法思想,顾名思义就是分而治之的意思。把一个很难解决的问题划分成许多小问题进行解决然后合并。在计算机算法设计与分析中,分治法的应用离不开递归技术。
递归,是指子程序(或函数)直接调用自己或通过一系列调用语句间接调用自己,是一种描述问题和解决问题的常用方法。递归有两个基本要素:
①边界条件,即确定递归到何时终止,也称为递归出口。
②递归模式,即大问题是如何分解为小问题的,也称为递归体。
举个例子,斐波那契数列:f(n)=f(n-1)+f(n-2)(n>2) f(0)=1;f(1)=1;
java代码实现如下:

    static int Fibonacci(int sequence){
        if(sequence <= 0){
            return 0;
        }else if(sequence == 1){
            return 1;
        }else if(sequence == 2){
            return 2;
        }else{
            return Fibonacci(sequence-2)+Fibonacci(sequence-1);
        }
    }

解释完递归再回来讲分治法,分治法在每一层递归上都有3个步骤:
⑴分解。将原问题分解成一系列子问题。
⑵求解。递归地求解各子问题。若子问题足够小,则直接求解。
⑶合并。将子问题的解合并成原问题的解。
那么用分治法来实现两个具体的问题案例。

⒈用分治法实现排序,即归并排序。
首先,分解。将待排序的数组一份为二。
接着,求解。对子数组进行递归排序。
最后,合并。合并两个有序的子数组为一个有序的数组。
java代码:

        int[] numMerge = {-101, -12, -13, 45, -23, 33, -41, -1, 13};
        mergeSort(numMerge,0,numMerge.length-1);
    /**
     * 分治法实现排序,即归并排序
     * @param Array
     */
    static void mergeSort(int[] Array, int left, int right){
        if(left < right){
            int mid = (left + right)/2;
            mergeSort(Array, left, mid);
            mergeSort(Array, mid+1, right);
            mergeByAsc(Array, left, right);
        }
    }

      /**
     * 合并,默认左边右边都是升序的
     * @param Array
     * @param left
     * @param right
     */
    static void mergeByAsc(int[] Array, int left, int right){
        int mid = (left + right)/2;
        //左边的升序数组
        int leftTemp[] = new int[mid - left + 1];
        for(int i = 0, j = left; i < leftTemp.length; i++,j++){
            leftTemp[i] = Array[j];
        }
        //右边的升序数组
        int rightTemp[] = new int[right - mid];
        for(int i = 0, j = mid+1; i < rightTemp.length; i++,j++){
            rightTemp[i] = Array[j];
        }
        int i=0,j=0;
        //按照升序合并左右两边数组为一个升序数组
        for(int k=left; k<=right; k++){
            if(i == leftTemp.length){
                Array[k] = rightTemp[j];
                j++;
            }else if(j == rightTemp.length){
                Array[k] = leftTemp[i];
                i++;
            }else if(leftTemp[i] > rightTemp[j]){
                Array[k] = rightTemp[j];
                j++;
            }else{
                Array[k] = leftTemp[i];
                i++;
            }
        }
    }

其实,在合并的过程就是对其排序的过程。当两个子数组仅有一个元素的时候,就默认是有序的,因为就一个嘛,升序或降序都行。接着将两个子数组合并成一个的时候,就按照升序或降序来合并了。在仿照褚华、霍秋艳主编的《软件设计师教程》第5版中C语言版本来写的时候,发现一个bug,就是左边或右边数组的指针到最后一个元素并赋值后,后面指针变量还是加了1,循环到下一次判断的时候就会引起指针越界错误,所以我在前面就判断指针是否到达最后,以防这种错误发生。

⒉用分治法求解最大子段问题。
问题描述:给定n个整数组成的序列,求其中某连续序列相加的和的最大值。比如1,-1,4,5,6,-3,2,最大子段的和就是15.(4+5+6)
这个问题乍看起来有点棘手,比排序难了一点,因为想复杂了,第1个元素开始加后面的元素,然后第二个元素加后面的元素,循环再比较,啊,越来越复杂了。排序至少可以用最简单的冒泡来代替归并什么的算法。这个就没办法了啊。这时应该谨记分治法思想的核心,就是划分问题再解决、合并。
首先,分解。整数组成的序列分成两个子序列。
接着,求解。求解子序列的最大子段和(聪明的你肯定想到了递归)。
最后,合并。这里的合并与前面的归并排序的合并有稍稍不同,归并排序的合并只要把两个子数组直接合并处理就行,而这里是求最大子段和,最大子段和的情况有3种:
①左子段的和。比如1,2,3,-1,-2,-3的左子段1,2,3的和就是最大子段和。
②右子段的和。比如-1,-2,-3,1,2,3的右子段1,2,3的和就是最大子段和。
③左右中间连接处的和。比如-1,-2,3,4,-2,-1左子段-1,-2,3的3加上右子段的4,-2,-1的4就是最大子段和。
java代码:

        int[] nums = {-101, -12, -13, -45, -23, -33, -41, -1};
        System.out.println(maxSubSum(nums,0,nums.length-1));

    /**
     * 用分治法求解最大字段和问题。
     * 对一个整型数组,求解数组中子段和最大值
     * @param Array
     * @param left
     * @param right
     * @return
     */
    static int maxSubSum(int[] Array, int left, int right){
        int sum = 0;
        int i;
        if(left == right){
            sum = Array[left];
        }else{
            /* 从left和right的中间分解数组
             * 求得左子段的最大子段和
             * 求得右子段的最大子段和
             * */
            int center = (left + right)/2;
            int leftSum = maxSubSum(Array, left, center);
            int rightSum = maxSubSum(Array, center + 1, right);
            /*求得左右子段中间连接部分的最大字段和*/
            int s1 = Integer.MIN_VALUE;
            int lefts = 0;
            for(i = center; i >= left; i--){
                lefts += Array[i];
                if(lefts > s1){
                    s1 = lefts;
                }
            }
            int s2 = Integer.MIN_VALUE;
            int rights = 0;
            for(i = center + 1; i <= right; i++){
                rights += Array[i];
                if(rights > s2){
                    s2 = rights;
                }
            }
            sum = s1 + s2;
            if(sum < leftSum){
                sum = leftSum;
            }
            if(sum < rightSum){
                sum = rightSum;
            }
        }
        return sum;
    }

引用:褚华、霍秋艳主编的《软件设计师教程》第5版

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容