算法笔记四:使用分治策略求最大子数组算法

本文介绍了一种使用分治策略解决最大子数组问题的高效算法,该算法通过递归地将数组分为两部分,并确定最大子数组是否位于左侧、右侧或跨越中间点。相比传统方法,此算法将复杂度从O(n^2)降低到了O(n*log2n),显著提升了大规模数据处理能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

算法思想

//求最大子数组问题:给定一个数组,求出值相加起来的最大的子数组

//当数组中全部是正数时,数组本身就是其最大子数组

//采取分治策略来解答,将数组一分为二,那么,答案要么位于左侧,要么位于右侧,要么横跨分割点。

//横跨分割点时,其解答的代价是线性的

//左侧和右侧又可以采用递归方式继续求解

//不难得出,代价为O(NlgN)

//跟传统的依次求每种可能的代价O(n^2)有非常明显的性能改进

分解—>解决—>合并

在解决的步骤中,什么条件下是阻止其继续分解的终止阀呢?:

分解的只有一个元素了

解决:

限制到最大数组必须横跨中间点,这是一个线性的过程,所以,就将原问题,分解成了N个子规模的线性问题

合并:

这里的合并,代价可以忽略,只需要比较下左区间和右区间以及横跨点区间的最大值即可


最好情况

与最坏情况相同,都是O(n*log2n)


最坏情况

与最好情况相同,都是

O(n*log2n)


下面我们来分析下为什么最好情况和最坏情况都是一样的,都为O(n*log2n)


分解:

对规模为N的数据,其分解过程,就是找到一个分解点X,所以其分解代价为常量O(1)

合并:

合并的过程,就是几个比较,O(1)

解决:

n个元素的顺序遍历求和过程,线性代价,O(n)


一样的使用递归树方法,不难得出最终的代价为n*log2n

算法实现

#ifndef __p1__MaxSubArray__
#define __p1__MaxSubArray__

#include <stdio.h>


//求最大子数组问题:给定一个数组,求出值相加起来的最大的子数组
//当数组中全部是正数时,数组本身就是其最大子数组
//采取分治策略来解答,将数组一分为二,那么,答案要么位于左侧,要么位于右侧,要么横跨分割点。
//横跨分割点时,其解答的代价是线性的
//左侧和右侧又可以采用递归方式继续求解
//不难得出,代价为O(NlgN)
//跟传统的依次求每种可能的代价O(n^2)有非常明显的性能改进
class MaxSubArray {
    
public:
    //分解
    void split(int * data,int left,int right,int * rLeft,int * rRight,int * rSum){
        //分解的终点,就是数组只有一个元素,那么,最大数组就是数组本身
        if(left == right){
            *rLeft = left;
            *rRight = left;
            *rSum = *(data + left);
            return;
        }
        //求出中间分割点
        int middle = (left + right) / 2;
        
        //解决无限递归问题,区间只有两个元素
        int p_start1 = middle;
        int p_start2 = middle;
        if(middle == left || middle == right){
            p_start1 = left;
            p_start2 = right;
        }

        int rleft1,rright1,rsum1,rleft2,rright2,rsum2,rleft3,rright3,rsum3;
        //求左边的最大子数组
        split(data, left, p_start1, &rleft1, &rright1, &rsum1);
        //求跨越点的最大子
        subArrayAcrossingPoint(data,left,right,middle,&rleft2,&rright2,&rsum2);
        //求右边的最大子数组
        split(data, p_start2, right, &rleft3, &rright3, &rsum3);
        
        if(rsum1 >= rsum2 && rsum1 >= rsum3){
            //左侧最大
            *rLeft = rleft1;
            *rRight = rright1;
            *rSum = rsum1;
        }else if (rsum2 >= rsum1 && rsum2 >= rsum3){
            //横跨点大
            *rLeft = rleft2;
            *rRight = rright2;
            *rSum = rsum2;
        }else{
            //右侧大
            *rLeft = rleft3;
            *rRight = rright3;
            *rSum = rsum3;
        }
        
    }
    
    //解决
    void subArrayAcrossingPoint(int * data,int left,int right,int accrossingPoint,int * rLeft,int * rRight,int * rSum){
        //这里理论上来说,应该用负无穷大来替代
        int max_sum_left = -999999999, max_sum_right = -999999999;
        int tmp_sum = 0;
        for(int i = accrossingPoint;i >= left;i--){
            tmp_sum = tmp_sum + *(data + i);
            if(tmp_sum > max_sum_left){
                *rLeft = i;
                max_sum_left = tmp_sum;
            }
        }
        tmp_sum = 0;
        for(int i = accrossingPoint + 1;i <= right;i++){
            tmp_sum = tmp_sum + *(data + i);
            if(tmp_sum > max_sum_right){
                *rRight = i;
                max_sum_right = tmp_sum;
            }
        }
        *rSum =max_sum_left +max_sum_right;
    }
    
    
    
    //排序入口
    void doing(int * data,int size,int * rLeft,int * rRight,int * rSum){
        split(data,0,size - 1,rLeft,rRight,rSum);
    }
};

#endif /* defined(__p1__MaxSubArray__) */

算法总结

该算法在应对数据规模增长的表现上,logn明显好于传统的每个组合都求值的n^2,

空间代价上,无论是分解还是合并,都不需要另辟空间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值