算法复杂度:最大子列和问题四种解法(C语言)

算法复杂度:最大子列和问题四种解法(C语言描述)

涉及英文

subcolumn:子列

问题描述

百度百科:

在计算机科学中,最大子数列问题的目标是在数列的一维方向找到一个连续的子数列,使该子数列的和最大。
例如,对一个数列 −2, 1, −3, 4, −1, 2, 1, −5, 4,其连续子数列中和最大的是 4, −1, 2, 1, 其和为6。

算法

算法一:穷举法

  1. 算法描述

    遍历整个数列的所有数值,在每一个扫描点计算以该点数值为开始点的子数列的和。计算所有子列和,并找出最大值。

  2. 代码示例

    int MaxSubseqSum1(int *List,int length)
    {
        int i = 0,j = 0,k=0;
        int sum = 0, max_sum = 0;
    
        //算出所以子列的和并选出最大一个子列和
        //i表示子列的左端
        for (i = 0; i < length; i++)
        {
            //j表示子列的右端,j>=i
            for (j = i; j < length; j++)
            {
                //算出以i为左端,j为右端的子列的子列和
                sum = 0;
                for (k = i; k <= j; k++)
                {
                    sum += List[k];
                }
                //判断当前子列和与当前最大子列和大小
                if (sum > max_sum)
                {
                    max_sum = sum;
                }
            }
        }
        return max_sum;
    }
    
  3. 时间复杂度:T(N)=O( N 3 N^3 N3)

算法二:穷举法的优化

  1. 算法描述

    依旧需要算出所有的子列和,但是在算以List[i]为起点,List[j]为终点的子列和的时候,可以依据计算的上一个子列(以List[i]为起点,List[j-1]为终点)的和来计算,即:当前子列和 = 上一个子列和 + List[j]

  2. 代码示例

    int MaxSubseqSum2(int *List, int length)
    {
        int i = 0, j = 0, k = 0;
        int sum = 0, max_sum = 0;
    
        //算出所以子列的和并选出最大一个子列和
        //i表示子列的左端
        for (i = 0; i < length; i++)
        {
            //j表示子列的右端,j>=i
            sum = 0;
            for (j = i; j < length; j++)
            {
                //以i为左端,j为右端的子列的子列和 为 以i为左端,j-1为右端的子列的子列和加上List[j]
                sum += List[j];
                //判断当前子列和与当前最大子列和大小
                if (sum > max_sum)
                {
                    max_sum = sum;
                }
            }
        }
        return max_sum;
    }
    
  3. 时间复杂度:T(N)=O( N 2 N^2 N2)

算法三:递归算法

  1. 算法描述

    把数列分成左右两部分。最大子序列和的位置存在三种情况:

    1. 完全在左半部分;
    2. 完全在右半部分;
    3. 跨越左右两部分。

    分别求出左半部分的最大子序列和、右半部分的最大子序列和、以及跨越左右两部分的最大子序列和,三者中的最大者就是该数列的最大子列和。

    • 求左、右半部分最大子列和求法:把左、右半部分分别作为新的输入序列通过该算法递归求出。

    • 跨越左右两部分的最大子序列和求法:求出左半数列中 包含左半数列最右元素的 所有子列中 和最大的一个,并求出右半数列中 包含右半数列最左元素的 所有子列中 和最大的一个,两者和相加,即是跨越左右两部分的最大子序列和。

  2. 代码示例

    int MaxSubseqSum3(int *List, int length)
    {
        int left_sum = 0, right_sum = 0, mid_sum = 0;
        int max_sum = 0;
    
        if (length != 1)
        {
            //求左边子列和最大值、右边子列最大值和跨过中间位置的子列和的最大值
            //当长度为2的倍数时,左边长度和右边长度一样
            if (length % 2 == 0)
            {
                left_sum=MaxSubseqSum3(List, length / 2);
                right_sum=MaxSubseqSum3(List+length/2, length / 2);
                mid_sum=FindMidSum(List, length / 2, length / 2);
            }
            //当长度不为2的倍数时,右边长度比左边长度大1
            else
            {
                left_sum = MaxSubseqSum3(List, length / 2);
                right_sum = MaxSubseqSum3(List + length / 2, length / 2+1);
                mid_sum=FindMidSum(List, length / 2, length / 2 + 1);
            }
    
            //比较left_sum、right_sum、mid_sum三个数最大值,将最大值赋值给max_sum,这就是最大子列和
            max_sum = left_sum;
            if (right_sum > max_sum)
            {
                max_sum = right_sum;
            }
            if (mid_sum > max_sum)
            {
                max_sum = mid_sum;
            }
    
            //返回最大子列和
            return max_sum;
        }
        else
        {
            return List[0];
        }
    }
    

    其中找跨越左右两部分的最大子序列和的函数FindMidSum()为:

    int FindMidSum(int *List, int length1, int length2)
    {
        int mid_left_max_sum = 0,mid_right_max_sum = 0,sum=0;
        int i = 0;
    
        //求出左半数列中 包含左半数列最右元素的 所有子列中 和最大的一个
        mid_left_max_sum = List[length1 - 1];
        for (i = length1 - 1; i >= 0; i--)
        {
            sum += List[i];
            if (sum > mid_left_max_sum)
            {
                mid_left_max_sum = sum;
            }
        }
        //求出右半数列中 包含右半数列最左元素的 所有子列中 和最大的一个
        sum = 0;
        mid_right_max_sum = List[length1];
        for (i = length1; i < length2+length1; i++)
        {
            sum += List[i];
            if (sum > mid_right_max_sum)
            {
                mid_right_max_sum = sum;
            }
        }
        //返回两个最大值相加的值,即跨越左右两部分的最大子序列和
        return mid_left_max_sum + mid_right_max_sum;
    }
    
  3. 时间复杂度:T(N)=N*log(N)

算法四:在线处理算法

  1. 算法描述

    遍历整个数组,扫描时将扫描点累加,并将累加值与最大子列和比较,如果大于最大子列和,这更新最大子列和。如果累加之后结果为负数,将累加和置零,并从下一扫描点开始累加,因为负数与后面的扫描点的值相加,并不能使累加和变得更大。

    在线 的意思是指每输入一个数据就进行 即是处理 ,在任何一个地方终止输入,算法都能能正确给出当前解。

  2. 代码示例

    int MaxSubseqSum4(int *List, int length)
    {
        int max_sum = 0,sum=0;
        int i = 0;
    
        for (i = 0; i < length; i++)
        {
            //向右累加
            sum += List[i];
    
            //发现更大的和则更新当前结果
            if (sum > max_sum)
            {
                max_sum = sum;
            }
            //如果当前子列和为负则不可能使后面的部分和增大,抛弃之
            if (sum < 0)
            {
                sum = 0;
            }
        }
    
        return max_sum;
    }
    
  3. 时间复杂度:T(N)=O(N)

测试代码

   #include<stdio.h>
   #include<stdlib.h>
   #include<time.h>
   #include"MaxSubseqSum.h"


   clock_t start = 0, stop = 0;
   double duration = 0;

   int main(void)
   {
       int i = 0;
       int max_sum = 0;
       int length = 5000;
       int *List = NULL;

       //随机生成长度为length的数组
       List = (int *)malloc(sizeof(int)*length);
       srand(time(NULL));
       for (i = 0; i < length; i++)
       {
           List[i] =rand() % length-length/2;
           /*printf("%d ", List[i]);*/
       }

       //算法一所用的时间
       start = clock();
       for (i = 0; i < 10; i++)
       {
           max_sum = MaxSubseqSum1(List, length);
       }
       stop = clock();
       duration = (double)(stop - start) / CLK_TCK/10;
       printf("\n算法一所用时间:%lf\n最大值为:%d\n\n", duration,max_sum);

       //算法二所用的时间
       start = clock();
       for (i = 0; i < 10; i++)
       {
           max_sum = MaxSubseqSum2(List, length);
       }
       stop = clock();
       duration = (double)(stop - start) / CLK_TCK / 10;
       printf("算法二所用时间:%lf\n最大值为:%d\n\n", duration, max_sum);

       //算法三所用的时间
       start = clock();
       for (i = 0; i < 10; i++)
       {
           max_sum = MaxSubseqSum3(List, length);
       }
       stop = clock();
       duration = (double)(stop - start) / CLK_TCK / 10;
       printf("算法三所用时间:%lf\n最大值为:%d\n\n", duration, max_sum);

       //算法四所用的时间
       start = clock();
       for (i = 0; i < 10; i++)
       {
           max_sum = MaxSubseqSum4(List, length);
       }
       stop = clock();
       duration = (double)(stop - start) / CLK_TCK / 10;
       printf("算法四所用时间:%lf\n最大值为:%d\n\n", duration, max_sum);

       free(List);
       return 0;
   }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值