最大子列和的四种算法及其时间复杂度分析

简介: 最大子列和的四种算法及其时间复杂度分析暴力破解法O(n^3)、暴力破解法改进版O(n^2);分而治之法O(nlogn)、在线处理算法O(n)。

最大子列和的四种算法

题目描述:
给定n个整数序列{A1,A2,...,An},求函数 f(i,j)=max{ 0,sum(ai,a(i+1),...,aj)}的最大值。
算法一:暴力破解法

​ 算出所有的子列和,找出最大的,即为所求。

​ 代码如下:

int maxSubseqSum1(int a[]) {
    int preSum = -1, maxSum = -1;                    //presentSum记录当前Sum,MaxSum记录最大子列和。
    int left = -1, right = -1;                       //left、right记录最大子列的左右端点值。
    for (int i = 0; i < n; i++) {                    //i记录左端点
        for (int j = i; j < n; j++) {                //j记录右端点
            preSum = 0;
            for (int k = i; k <= j; k++) {
                preSum += a[k];                     //累加计算a[i]->a[j]的子列和
            }
            if (preSum > maxSum) {
                maxSum = preSum;                    //更新最大值及其对应的左右边界
                left = i;
                right = j;
            }
        }
    }
    printf("in maxSubseqSum1() left=%d,right=%d,maxSum=%d\t暴力破解法\n", left, right, maxSum);
    return maxSum;
}

​ 时间复杂度较容易由3层嵌套for循环得出为

​ 可改进的主要点在于,如已知当前从i到j的子列和时,再计算i到j+1的子列和时,i到j的子列和又重新算一遍,显然是冗余的,是完全没有必要的,只需加一个j+1元素即可。

​ 那么可以改进之后可以得出算法2。

image.png

算法二:暴力破解法改进版

​ 同样为,算出所有的子列和,找出最大的,即为所求。但在具体是线上进行改进。

​ 代码如下:

int maxSubseqSum2(int a[]) {
    int preSum = -1, maxSum = -1;          //presentSum记录当前Sum,MaxSum记录最大子列和。
    int left = -1, right = -1;             //left、right记录最大子列的左右端点值。
    for (int i = 0; i < n; i++) {          //i记录左端点
        preSum = 0;
        for (int j = i; j < n; j++) {      //j记录右端点
            preSum += a[j];                //对于相同的i不同的j,只需在j-1层循环的基础上加一项a[j]即可。
            if (preSum > maxSum) {
                maxSum = preSum;           //更新最大值及其对应的左右边界
                left = i;
                right = j;
            }
        }
    }
    printf("in maxSubseqSum2() left=%d,right=%d,maxSum=%d\t暴力破解改进版算法\n", left, right, maxSum);
    return maxSum;
}

​ 改进版在对于相同的i不同的j的子列和计算时,在j-1层循环的基础上加一项a[j]的即可得出i到j的子列和。

​ 不难看出该算法的时间复杂度为T(n)=O(n^2)。较法一已经有较大提升。

​ 但是,当n到达10000、100000 、1000000时仍为一个较恐怖的的时间复杂度,因此借用分而治之的思想提出更好的算法三。

算法三:分而治之(分治法)

分治法是基于多项分支递归的一种很重要的算法。“分而治之”就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即为子问题的解的合并。诸如快速排序、归并排序、傅里叶变换、快速傅里叶变换等算法都极大程度上采用了分治法的基本思想。

​ 分治法通常以数学归纳法来验证。而其复杂度多数情况下是以解递推关系式来判定。

​ 运用到最大子列和问题中就可简述为:先“分”,即将数列分为两半,最大子列必只有左半边、右半边、或者两者中间这三种情况,依次求出三种情况的解;再“治”,求三个最大子列和的最大解即可得到整个数列的最大子列和。

​ 依据上述思想可得出该题的分治算法简要的四步:

  1. 将数列从中间分为两个等长或者数量差值为1的两个子数列。
  2. 递归使用该算法分别求出左半边的最大子列和和右半边的最大子列和。
  3. 计算跨越中线的最大子列和,即从中线开始向左右两边分别遍历,得出跨越中线的最大子列和。
  4. 求三者的最大值即可。

其代码具体描述如下:

//分治法递归求最大子列和
int divCon(int list[], int left, int right) {
    int maxLeftSum, maxRightSum;              //记录左右两边数列的最大子列和
    int maxLBorderSum = 0, maxRBorderSum = 0; //记录跨越中线的最大子列和

    if (left == right) {                      //当left=right时,表示当前递归到只有一个数字的子列
        if (list[left] > 0) {
            return list[left];                //若该值>0则表示可以使最大子列和增加,故返回改值。
        } else
            return 0;                         //若改值<=0则表示不能增大最大子列和返回0。
    }

    //“分”
    int center = (left + right) / 2;                   //计算中间点
    maxLeftSum = divCon(list, left, center);           //递归解决左半边最大子列和
    maxRightSum = divCon(list, center + 1, right);     //递归解决左半边最大子列和

    //求跨越边界的最大子列和。
    int leftBorderSum = 0, rightBorderSum = 0; //记录向左、向右的边界上当前扫描的和
    //求中线以左的最大值
    for (int i = center; i >= left; i--) {
        leftBorderSum += list[i];
        if (leftBorderSum > maxLBorderSum)     //如果当前扫描的左半边的和大于左半边整体的和则更新最大值
            maxLBorderSum = leftBorderSum;
    }
    //求中线以右的最大值
    for (int i = center + 1; i < right; i++) {
        rightBorderSum += list[i];
        if (rightBorderSum > maxRBorderSum)    //如果当前扫描的左半边的和大于左半边整体的和则更新最大值
            maxRBorderSum = rightBorderSum;
    }

    //“治”   求左数列、右数列、跨越边界的数列,三个子问题的最大解。
    return maxOfThNum(maxLeftSum, maxRightSum, maxLBorderSum + maxRBorderSum);
}

//求三个数的最大值函数
int maxOfThNum(int a, int b, int c) {
    return a > b ? a > c ? a : c : b > c ? b : c;
}

int maxSubseqSum3(int list[]) {
    int maxSum = divCon(list, 0, n - 1);
    printf("in maxSubseqSum3() maxSum=%d\t分而治之算法\n", maxSum);
    return maxSum;
}

​ 分治法主要分为三个部分进行求解,因此时间复杂度的计算也应该分为三部分,其中在“分”的过程中每一半都为$T(n/2)$,故左右两部分的时间复杂度为$2T(n/2)$;对于跨越中线的子列和是通过两个for循环分别求解中线以左、以右的两部分的子列和,因此最差时间复杂度为遍历整个数组,故而时间复杂度为$T(n)$。

​ 综上所述,对于时间复杂度可得如下递推式:
image.png

​ 通过对数列的数次分割,直至被分为1个数组元素时满足$n/2^k=1$,即为$2^k=n$,因此可以得到时间复杂度为:
image.png

​ 对比于暴力破解法,时间复杂已经有一个不错的提升,比如当n的数量级来到$10^4$时,已经有较为优异的表现。

​ 但是,在该算法的第6~11行及21~31行我们不难发现这样子一个规律:当某子列为正数时可以增大最大子列和,若为负数则不可能使得最大子列和增加。

​ 因此进一步分析可以得出方法四:在线处理算法。

算法四:在线处理算法

​ 通过“在线的”思想,即无须获取全部数组元素就可以进行先行处理,可以对输入的每一个数据进行立即处理,使得算法的结果是对于当前已经读入的数据都成立的解。也就是说当某个地方的输入发生中断时,程序可以返回当前已经输入的数据的正确当前解。因此在线思想是完全区别于上述所提的三种算法的。

​ 在线处理算法的核心思想为:

​ 如果整数序列{a1,a2,...,an}的最大子列和是{ai,a(i+1),...,aj},那么必有(ai+a(i+1)+...+al>=0)对任意的i<=l<=j成立。因此,当某当前子列为负时,则应考察新的以i+1为左边界的子列进行计算。

​ 其代码具体描述如下:

int maxSubseqSum4(int list[]) {
    int preSum = 0, maxSum = 0;     //preSum记录当前子列和,maxSum记录最大子列和
    int left = -1, right = -1;      //标记最大子列的左右边界
    for (int i = 0; i < n; i++) {
        preSum += list[i];          //累加下一个元素
        if (preSum > maxSum) {      //若累加后当前子列和大于最大子列和则更新
            maxSum = preSum;
            right = i;              //修改右边界指针
        } else if (preSum < 0) {    //若累加使得当前子列和变为负数,则应当丢弃该子列。
            preSum = 0;             //因为其不可能使最大子列和增大。
            left = i+1;             //修改左边界指针
        }
    }
    printf("in maxSubseqSum4() left=%d,right=%d,maxSum=%d\t在线处理算法\n", left, right, maxSum);
    return maxSum;
}

​ 对于该算法的时间复杂度由算法代码较易得出为T(n)=O(n),但是优秀的算法,因其思想与普通算法思想有较大的差异,所以导致算法的正确性表现的不是很直观,需要通过实际例子深入理解。

                                                          by...Ss1Two...2023/01/03
目录
相关文章
|
1月前
|
机器学习/深度学习 算法 搜索推荐
从理论到实践,Python算法复杂度分析一站式教程,助你轻松驾驭大数据挑战!
【10月更文挑战第4天】在大数据时代,算法效率至关重要。本文从理论入手,介绍时间复杂度和空间复杂度两个核心概念,并通过冒泡排序和快速排序的Python实现详细分析其复杂度。冒泡排序的时间复杂度为O(n^2),空间复杂度为O(1);快速排序平均时间复杂度为O(n log n),空间复杂度为O(log n)。文章还介绍了算法选择、分而治之及空间换时间等优化策略,帮助你在大数据挑战中游刃有余。
57 4
|
1月前
|
机器学习/深度学习 缓存 算法
Python算法设计中的时间复杂度与空间复杂度,你真的理解对了吗?
【10月更文挑战第4天】在Python编程中,算法的设计与优化至关重要,尤其在数据处理、科学计算及机器学习领域。本文探讨了评估算法性能的核心指标——时间复杂度和空间复杂度。通过详细解释两者的概念,并提供快速排序和字符串反转的示例代码,帮助读者深入理解这些概念。同时,文章还讨论了如何在实际应用中平衡时间和空间复杂度,以实现最优性能。
59 6
|
28天前
|
搜索推荐 算法
插入排序算法的平均时间复杂度解析
【10月更文挑战第12天】 插入排序是一种简单直观的排序算法,通过不断将未排序元素插入到已排序部分的合适位置来完成排序。其平均时间复杂度为$O(n^2)$,适用于小规模或部分有序的数据。尽管效率不高,但在特定场景下仍具优势。
|
1月前
|
机器学习/深度学习 存储 缓存
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
文章主要介绍了排序算法的分类、时间复杂度的概念和计算方法,以及常见的时间复杂度级别,并简单提及了空间复杂度。
23 1
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
|
19天前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
1月前
|
存储 算法
算法的时间复杂度和空间复杂度
本文详细讨论了算法的时间复杂度和空间复杂度,包括它们的概念、计算方法和常见复杂度的对比,并通过多个实例解释了如何计算算法的时间和空间复杂度。
60 0
算法的时间复杂度和空间复杂度
|
26天前
|
算法
PID算法原理分析
【10月更文挑战第12天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。
|
2月前
|
算法 Python
震惊!Python 算法设计背后,时间复杂度与空间复杂度的惊天秘密大起底!
在 Python 算法设计中,理解并巧妙运用时间复杂度和空间复杂度的知识,是实现高效、优雅代码的必经之路。通过不断地实践和优化,我们能够在这两个因素之间找到最佳的平衡点,创造出性能卓越的程序。
39 4
|
2月前
|
算法 搜索推荐 开发者
别再让复杂度拖你后腿!Python 算法设计与分析实战,教你如何精准评估与优化!
在 Python 编程中,算法的性能至关重要。本文将带您深入了解算法复杂度的概念,包括时间复杂度和空间复杂度。通过具体的例子,如冒泡排序算法 (`O(n^2)` 时间复杂度,`O(1)` 空间复杂度),我们将展示如何评估算法的性能。同时,我们还会介绍如何优化算法,例如使用 Python 的内置函数 `max` 来提高查找最大值的效率,或利用哈希表将查找时间从 `O(n)` 降至 `O(1)`。此外,还将介绍使用 `timeit` 模块等工具来评估算法性能的方法。通过不断实践,您将能更高效地优化 Python 程序。
53 4
|
1月前
|
算法
PID算法原理分析及优化
【10月更文挑战第6天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。