• 数据结构与算法之分治法


    前言

    分治法首先需要明白递归的概念:

    递归: 是指子程序直接调用自己或者通过一系列调用语句间接调用自己,是一种描述问题和解决问题的常用方法。递归的两个基本要素:
    1.边界条件:也就是递归终止调用的条件。
    2.递归出口:递归表达式,大问题分解为小问题。

    分治算法的一般有几个步骤:

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

    以下将分析几个典型例子。

    1.递归解决阶乘函数

    阶乘函数的定义大家都知道。
    1)边界条件 n=0,n!=1
    2) 递归体 n>0,n!=n*(n-1)

    C语言代码:

    /**
     1. 阶乘的算法
     2. @param n
     3. @return
     */
    int Fac(int n){
        if(n==0)
            return 1;
        else
            return n* Fac(n-1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    算法

    2.归并排序算法

    2.1 归并排序的概念

    归并排序是将待排序的元素分成两个大致相同的两个子序列,分别对子序列进行排序,最终将排好序的子序列合并为排序的序列。

    2.2 分治法的三步曲

    大致分为以下几步:

    1. 分解。将n个元素分成n/2个元素的子序列。
    2. 求解。用归并排序对两个子序列递归排序。
    3. 合并。合并两个排序好的子序列得到排序结果。

    2.3 归并排序的动画

    归并排序算法

    2.4 归并排序算法(C语言代码)

    /**
     * 归并排序
     * @param a 待排序的数组
     * @param l 左边的
     * @param r 右端
     */
    void MergeSort(int a[],int l, int r){
        //计算分组的中间位置
        int m;
        if (l < r){
            //计算中间的位置
            m = (l+r)/2;
            //递归左边
            MergeSort(a,l,m);
            //递归右边
            MergeSort(a,m+1,r);
            //合并结果
            Merge(a,l,m,r);
        }
    }
    /**
     * 合并排序的结果
     * @param a
     * @param l
     * @param m
     * @param r
     */
    void Merge(int a[], int l, int m, int r) {
        //左边的长度,定义右边的长度
        int lLen=m-l+1 ,rLen=r-m;
        //定义临时数组存放左边的排序结果以及右边的排序结果
        int L[50],R[50];
        //取出左边的元素
        for(int i=0 ; i < lLen ; i++){
           L[i]=a[l+i];
        }
        //取出右边的元素
        for(int j = 0 ; j < rLen; j++){
            R[j]=a[m+j+1];
        }
        //很关键,这个值一定要设置为最大
        L[lLen] = INT_MAX;
        R[rLen] = INT_MAX;
        //开始比较大小
        int i=0,j=0;
        for(int k = l; k < r+1 ; k++){
            if(L[i] < R[j]){
                a[k]=L[i];
                i++;
            }else{
                a[k]=R[j];
                j++;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    测试代码:
    测试

    3.最大子序列和问题

    3.1 问题的定义

    给定n个整型数组组成的蓄力A1,A2,…An,求该序列中子序列的字段和的最大值,当所有序列所有的整型均为负数整数时,其最大字段和为0。
    example:
    当序号为[-2,6,-4,8,-5,3]时,最大子序列的和为10=6+(-4)+8。

    3.2 分治的思路

    普通的计算,只有循环遍历所有的子段求和进行比较求解,这样的时间复杂度比较高,这种也就是暴力算法解题的思路,第一遍单个元素作为和比较,得出最大和为8,第二次遍历两个连续元素的组合,第三次遍历三个元素的组合,第四次组合…比较简单,这里不在代码。

    1. 分解。将序列拆分为n个子序列,最大值分为三种情况,所有序列的和分为三种情况。
      (1) 所有序列的的最大字段和在序列的1/2序列左边的和相同。
      (2) 所有序列的的最大字段和在序列的1/2序列右边的和相同。
      (3) 所有序列的的最大字段和在序列的1/2序列左和右边的和相同

    2. 求解。
      递归分段求解1/2字段和。由于序列是连续的,此问题的关键在于求和的时候从1/2向两边扩展求和,这样就能包含前两种情况,第三种情况是从中间1/2处左右两边子序列和的最大值。

    3. 合并。最终的结果就是三个情况的和最大结果。

    3.3 简单的分解下代码的结果

    以序列[-2,6,-4,8,-5,3]为例。
    在这里插入图片描述
    此处写比较费劲,看代码输出。

    3.4 算法代码

    /**
     * 打印数组
     * @param a
     * @param left
     * @param right
     */
    void printArr(int *a,int left,int right){
        for (int i = left; i <= right ; ++i) {
            printf("%d,",a[i]);
        }
        printf("\n");
    }
    /**
     * 求解最大的字段序列和
     * @param a
     * @param left
     * @param right
     * @return
     */
    int MaxSubSeqSum(int * a,int left,int right){
        int sum = 0 ;
        int i;
        //遍历到单个元素
        if(left == right){
            if(a[left]>0)  sum=a[left];
            else sum=0;
        }else{
            //循环遍历
            int med=(left+right)/2;
            int leftSum= MaxSubSeqSum(a,left,med);
            int rightSum= MaxSubSeqSum(a,med+1,right);
            //计算左边的序列的字段和
            int subLeftSum=0,s1=0;
            printArr(a,left,med);
    
            for ( i = med; i >=left ; i--) {
                subLeftSum+=a[i];
                if (subLeftSum > s1) s1=subLeftSum;
            }
            printf("the left sum is %d",subLeftSum);
            printf("\n");
            //计算右边的序列的字段和
            printArr(a,med,right);
            int subRightSum=0,s2=0;
            for (i = med+1; i <= right  ; i++) {
                subRightSum+=a[i];
                if (subRightSum > s2) s2=subRightSum;
            }
            printf("the right sum is %d",subRightSum);
            printf("\n");
            //左右两边之和
            sum=s1+s2;
            if(sum < leftSum) sum=leftSum;
            if(sum < rightSum) sum=rightSum;
        }
        return sum;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    3.5 测试结果

    int main(){
        int a[] = {-2,6,-4,8,-5,3};
        int result= MaxSubSeqSum(a,0,5);
        printf("the final result is :%d",result);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    代码执行递归过程。

    -2,
    the left sum is -2 
    -2,6,
    the right sum is 6 
    -2,6,
    the left sum is 4  
    6,-4,
    the right sum is -4
    8,
    the left sum is 8  
    8,-5,
    the right sum is -5
    8,-5,
    the left sum is 3  
    -5,3,
    the right sum is 3 
    -2,6,-4,
    the left sum is 0 
    -4,8,-5,3,        
    the right sum is 6
    the final result is :10
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
  • 相关阅读:
    JAVA学习-练习试用Java实现“串联所有单词的子串”
    Java编程技巧:分类
    字符串去掉()以及()中的文字
    vue项目axios的使用实例详解
    LeetCode-94. Binary Tree Inorder Traversal [C++][Java]
    RK3588 开启HDCP
    【Mysql专题】一条SQL在Mysql中是如何执行的
    【蓝桥杯】赢球票(模拟、枚举、搜索)
    HarmonyOS应用开发入门(五)
    奖品定制经营商城小程序的作用是什么
  • 原文地址:https://blog.csdn.net/qq_37400096/article/details/132788368