分治法

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 说明:该系列博客整理自《算法导论(原书第二版)》,但更偏重于实用,所以晦涩偏理论的内容未整理,请见谅。另外本人能力...
    黑夜0411阅读 3,538评论 0 3
  • 将原问题分解为一组子问题,每个子问题都与原问题类型相同,但是比原问题的规模小 递归求解这些子问题 将子问题的求解结...
    芥丶未央阅读 5,353评论 2 1
  • 版本记录 前言 将数据结构和算法比作计算机的基石毫不为过,追求程序的高效是每一个软件工程师的梦想。下面就是我对算法...
    刀客传奇阅读 8,097评论 0 0
  • 一、基本概念 在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或...
    扎Zn了老Fe阅读 4,101评论 0 1
  • 目录 零、主定理 零-零、利用数学方法求解递归式 一、归并排序*二、最大子数组问题2.1 问题描述2.2 使用分治...
    王侦阅读 5,940评论 0 3

友情链接更多精彩内容