最长子串和的三种算法

1.实验目的

比较同一问题,采用不同策略设计不同算法,分析和比较算法的性能。

2.实验内容

自学最长子串和的三种算法,总结分析编程实现简单算法、分治法和动态规划算法的理论复杂度,编程实现这些算法。

3.实验要求

并对一组数据测试实际运行时间,算法实际运行时间检测实际运行时间与理论分析是否一致。

□ \square 基础性实验 □ \square 综合性实验 ⊠ \boxtimes 设计性实验


实验报告正文

一、问题分析(模型、算法设计和正确性证明等)

1、简单算法:

模型:用 a [ n ] a[n] a[n]存储序列。用 s u m sum sum t e m p temp temp存所有可能的值即可。

算法设计:对 ∀ i ∈ [ 1 , n ] , j ∈ ( i , n ] \forall i \in [1,n], j \in (i,n] i[1,n],j(i,n],计算所有的 s u m = ∑ k = i k = j ( a [ k ] ) sum = \sum_{k=i}^{k=j}(a[k]) sum=k=ik=j(a[k]),再求出最小值即可。

正确性证明:由于遍历了所有情况求出最值,因此必然正确。

2、分治法:

模型:采用递归的方式对问题进行求解,分别用三个变量存储三种情况下的结果即可。

算法设计:规模为 n n n的问题求解 M a x S u b S u m ( 1 , n ) MaxSubSum(1,n) MaxSubSum(1,n)时,将该串分为左右两个子串,则最长子串和一定满足下列三种情况中的一个:

  • 最长子串全部位于左子串,即 M a x S u b S u m ( 1 , n ) = M a x S u b S u m ( 1 , n 2 ) MaxSubSum(1,n) = MaxSubSum(1, \frac{n}{2}) MaxSubSum(1,n)=MaxSubSum(1,2n)
  • 最长子串全部位于右子串,即 M a x S u b S u m ( 1 , n ) = M a x S u b S u m ( n 2 + 1 , n ) MaxSubSum(1,n)=MaxSubSum(\frac{n}{2}+1, n) MaxSubSum(1,n)=MaxSubSum(2n+1,n)
  • 最长子串在左右子串中均有分布。在这种情况下,显然 a [ n 2 ] a[\frac{n}{2}] a[2n] a [ n 2 + 1 ] a[\frac{n}{2}+1] a[2n+1]均属于最长子串,也就是说问题变成了确定 i , j i,j i,j其中 i ∈ [ 1 , n 2 ] , j ∈ [ n 2 + 1 , n ] i \in[1,\frac{n}{2}], j \in [\frac{n}{2}+1,n] i[1,2n],j[2n+1,n],使得 i , j i,j i,j分别是最长子串的开始和结束。具体确定方法采用遍历法。

其中,递归出口为序列长为 1 1 1,此时最长子串和为 m a x ( a [ i ] , 0 ) max(a[i],0) max(a[i],0)

正确性证明:由于每一个主要问题有仅有三种情况,且每个子问题的最小规模必然为0或者当前值,因此正确。

3、动态规划法:

模型:使用 d p [ i ] dp[i] dp[i]表示从初始位置开始一直到 a [ i ] a[i] a[i]结束(包含 a [ i ] a[i] a[i])的最长字段和。

算法设计:按照分治法的情况,若 [ 1 , i ] [1,i] [1,i]分为 [ 1 , i − 1 ] [1,i-1] [1,i1] [ i , i ] [i,i] [i,i]。那么情况一下 d p [ i ] = d p [ i − 1 ] dp[i]=dp[i-1] dp[i]=dp[i1],情况二下 d p [ i ] = a [ i ] dp[i] = a[i] dp[i]=a[i],情况三下 d p [ i ] = d p [ i − 1 ] + a [ i ] dp[i] = dp[i-1]+a[i] dp[i]=dp[i1]+a[i]。因此 d p [ i ] = m a x { d p [ i − 1 ] , a [ i ] , d p [ i − 1 ] + a [ i ] } = m a x { d p [ i − 1 ] , 0 } + a [ i ] dp[i] = max\{dp[i-1], a[i], dp[i-1]+a[i] \}=max\{dp[i-1],0 \}+a[i] dp[i]=max{dp[i1],a[i],dp[i1]+a[i]}=max{dp[i1],0}+a[i]。dp表更新完成后对每一个 d p [ i ] dp[i] dp[i]取最大,便是 s u m sum sum

正确性证明:按照分治法的分析,我们不难得出 d p [ i ] dp[i] dp[i]确实代表从初始位置到到 a [ i ] a[i] a[i]结束(包含 a [ i ] a[i] a[i])的最长字段和。由于最长子序列必然是以 [ 1 , n ] [1,n] [1,n]中的某一个值作为结尾的子段,我们可以确定最终的 s u m sum sum值必然属于我们给出的 n n n d p [ i ] dp[i] dp[i]的值,因此对这 n n n个值取最大即可得到最终结果。

二、复杂度分析

1、简单算法:

时间复杂度:

对所有的 i , j i,j i,j共有 n ( n + 1 ) 2 \frac{n(n+1)}{2} 2n(n+1)种可能,对于每种可能计算从 0 0 0 n n n长度的序列和,因此时间复杂度为 O ( n 3 ) O(n^3) O(n3)

空间复杂度:

由于只使用了两个变量和一个序列,空间复杂度为 O ( n ) O(n) O(n)

2、分治法:

时间复杂度:

对原本规模为 n n n的问题,将其分为三个子问题,前两个子问题也就是规模减小一半的同等问题,第三种情况下的求解由遍历的方式给出,由于 i , j i,j i,j只有可能出现在 n n n个位置,因此复杂度为 O ( n ) O(n) O(n)。因此得到递推关系式: T ( n ) = 2 T ( n 2 ) + O ( n ) T(n) = 2T(\frac{n}{2})+O(n) T(n)=2T(2n)+O(n),故时间复杂度为 O ( n log ⁡ ( n ) ) O(n \log(n)) O(nlog(n))

空间复杂度:

与简单算法一致,为 O ( n ) O(n) O(n)

3、动态规划法:

时间复杂度:

实验只需要对dp表进行一次线性更新,再从dp表中线性取最值,因此时间复杂度为 O ( n ) O(n) O(n)

空间复杂度:

实验采用了长为 n n n的两个数组分别存储数组序列和动态规划表,因此空间复杂度为 O ( n ) O(n) O(n)

三、程序实现和测试过程和结果(主要描述出现的问题和解决方法)

  • 按照文件读取:

    ​ 由于给出的测试数据体量大、数据多。因此采用文件流的格式读取数据并赋值。考虑到每次仅读取一个文件需要频繁打开可执行程序,因此直接对于每一种方法读取10个文件。实现代码如下:

    char filename[][50] = {"test_data/0.in","test_data/1.in","test_data/2.in","test_data/3.in","test_data/4.in"\
          ,"test_data/5.in","test_data/6.in","test_data/7.in","test_data/8.in","test_data/9.in"};
        fstream fin;
        for(int in=0; in<10; in++)
        {
            fin.open(filename[in]);
            fin>>n;
            for(int i=0; i<n; i++) fin>>a[i+1];
            ······
            fin.close();
        }
    
  • 输出运行时间:

    ​ 直接采用cpp自带函数库实现:

    clock_t start = clock();
    	······
    clock_t stop = clock();
    cout<<"time is "<<(stop - start)<<"ms";
    
  • 按照5.in给出的运行时间比较:

    5.in的规模:10000

    简单算法耗时:337014ms。分治算法耗时:104ms。动态规划耗时:1ms

    O ( 10000 ) = 1 O(10000)=1 O(10000)=1 O ( 10000 ∗ l o g ( 10000 ) ) = O ( 14 ∗ 10000 ) = 104 O(10000*log(10000))=O(14*10000)=104 O(10000log(10000))=O(1410000)=104 O ( 1000 0 3 ) = 337014 O(10000^3)=337014 O(100003)=337014

    很明显可以看出理论分析与实际运行较为一致

1、简单算法:

实现很简单,直接对所有 i , j i,j i,j进行遍历即可:

for(int i=1; i<=n; i++)
    for(int j=i; j<=n; j++)
    {
        temp = 0;
        for(int k=i; k<=j; k++) temp+=a[k];
        sum = max(sum, temp);
    }

2、分治算法

int MaxSubSum(int low, int high)
{
    if(low == high) return max(a[low], 0);	//递归出口
    int mid = (low + high)/2, temp, s1, s2, s3, s3_l=0, s3_r=0;
    s1 = MaxSubSum(low, mid);				//情况一
    s2 = MaxSubSum(mid+1, high);			//情况二
    for(int i=low; i<=mid; i++)				//情况三
    {
        temp = 0;
        for(int j=i; j<=mid; j++) temp+=a[j];
        s3_l = max(temp, s3_l);
    }
    for(int i=mid+1; i<=high; i++)
    {
        temp = 0;
        for(int j=mid+1; j<=i; j++) temp+=a[j];
        s3_r = max(temp, s3_r);
    }
    s3 = s3_l + s3_r;
    return max(max(s1, s2),s3);				//比较哪一个情况下给出的答案更大
}

3、动态规划

        dp[1] = a[1];	//边界赋值
        for(int i=2; i<=n; i++) dp[i] = max(dp[i-1], 0)+a[i];	//动态规划
        for(int i=1; i<=n; i++) sum = max(sum, dp[i]);			//取最大值

实现结果:
在这里插入图片描述
测试数据和源码下载链接:https://download.youkuaiyun.com/download/weixin_44609261/12408063

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值