最大子序列问题
2016-07-29 00:00
211 查看
问题1:输入是具有n个浮点数的向量x,输出这个向量的任何连续子向量中的最大和。当所有元素为负数时定义最大和为0。
(1)穷举算法:对所有0<=i<=j<n的整数对进行迭代。对每个整数对(i,j),程序都要计算x[i...j]的总和,并判断其是否大于当前的最大总和。
可见这个算法运行时间为O(n**3)。
(2)平方算法1:从(1)中可以看出,x[i...j]的总和与前面已计算出的总和x[i...j-1]密切相关。利用这一点进行改进。
这个算法的效率为O(n**2)。
(3)平方算法2:通过访问在外循环执行之前就已经构建的数据结构,在内循环中计算总和。cumarr[i]表示x[0...j]的总和,因此x[i...j]的总和可通过cumarr[j]-cumarr[i-1]获得。
(4)分治算法:要解决规模为n的问题,可递归地解决两个规模近似为n/2的子问题,然后对两个结果进行合并以得到整个问题的答案。将x划分为两个近似相等的子向量ab,在a和b中分别找出总和最大的子向量ma和mb,然后找到跨越a和b边界的最大子向量mc,返回三个总和中的最大者。通过观察发现mc在a中的部分是a中包含右边界的最大子向量,mc在b中的部分是b中包含左边界的最大子向量。伪代码如下:
(5)扫描算法:我们采用从x[0]开始扫描,一起到最右端x[n-1],并记下所遇到的最大子向量总和(初始值设为0)。假设我们已解决了x[0,i-1]的问题,如何将其扩展到x[0...i]呢?前i个元素中,最大总和子数组要么在前i-1个元素中(用maxsofar存储),要么其结束位置为i(用maxendinghere存储)。
该算法十分简短,运行时间为O(n)。
两个平方算法和扫描算法都使用了简单的动态规划形式,通过使用一些空间来保存蹭计算结果,避免了花时间对其重复计算。平方算法2使用了一个累积表来存放x[0...i]的总和。与数组与数组相关的问题经常可以通过思考“如何将x[0...i-1]的解扩展为x[0...i]的解?"来解决。
问题2:假设我们想要查找的是总和最接近0的子向量,而不是具有最大总和的子向量,该如何设计算法?
可初始化累加数组cum,使得cum[i]=x[0]+...+x[i]。如果cum[l-1]=cum[u],那么子向量x[l...u]之和就为0.因此可以通过定位cum中最接近的两个元素来找出和最接近0的子向量。这可以通过排序数组,在O(nlogn)时间内完成。
关键算法设计思想:穷举策略、动态规划(保存状态,避免重复计算)、分治策略、扫描策略(如何将x[0...i-1]的解扩展到x[0...i]的解)、累积表。
(1)穷举算法:对所有0<=i<=j<n的整数对进行迭代。对每个整数对(i,j),程序都要计算x[i...j]的总和,并判断其是否大于当前的最大总和。
maxsofar=0 for i=[0,n) for j=[i,n) sum=0 for k=[i,j] /* sum is sum of x[i...j] */ sum+=x[k] maxsofar=max(maxsofar,sum)
可见这个算法运行时间为O(n**3)。
(2)平方算法1:从(1)中可以看出,x[i...j]的总和与前面已计算出的总和x[i...j-1]密切相关。利用这一点进行改进。
maxsofar=0 for i=[0,n) sum=0; for j=[i,n) sum+=x[j] /* sum is sum of x[i...j] */ maxsofar=max(maxsofar,sum)
这个算法的效率为O(n**2)。
(3)平方算法2:通过访问在外循环执行之前就已经构建的数据结构,在内循环中计算总和。cumarr[i]表示x[0...j]的总和,因此x[i...j]的总和可通过cumarr[j]-cumarr[i-1]获得。
cumarr[-1]=0 for i=[0,n) cumarr[i]=cumarr[i-1]+x[i] maxsofar=0 for i=[0,n) for j=[i,n) sum=cumarr[j]-cumarr[i-1] /* sum is sum of x[i...j] */ maxsofar=max(maxsofar,sum)
(4)分治算法:要解决规模为n的问题,可递归地解决两个规模近似为n/2的子问题,然后对两个结果进行合并以得到整个问题的答案。将x划分为两个近似相等的子向量ab,在a和b中分别找出总和最大的子向量ma和mb,然后找到跨越a和b边界的最大子向量mc,返回三个总和中的最大者。通过观察发现mc在a中的部分是a中包含右边界的最大子向量,mc在b中的部分是b中包含左边界的最大子向量。伪代码如下:
float maxsum(l,u) if(l>u) return 0 /* zero elements */ if(l==u) return max(0,x[1]) /* one element */ m=(l+u)/2 lmax=sum=0; for(i=m;i>=l;i--) /* find max crossing to left */ sum+=x[i] lmax=max(lmax,sum) rmax=sum=0 for i=(m,u] /* find max crossing to right */ sum+=x[i] rmax=max(rmax,sum) return max(lmax+rmax,maxsum(l,m),maxsum(m+1,u))
(5)扫描算法:我们采用从x[0]开始扫描,一起到最右端x[n-1],并记下所遇到的最大子向量总和(初始值设为0)。假设我们已解决了x[0,i-1]的问题,如何将其扩展到x[0...i]呢?前i个元素中,最大总和子数组要么在前i-1个元素中(用maxsofar存储),要么其结束位置为i(用maxendinghere存储)。
maxsofar=0 maxendinghere=0 for i=[0,n) maxendinghere=max(maxendinghere+x[i],0) /* 计算前maxendinghere是结束位置为i-1的最大子向量的和 */ maxsofar=max(maxsofar,maxendinghere)
该算法十分简短,运行时间为O(n)。
两个平方算法和扫描算法都使用了简单的动态规划形式,通过使用一些空间来保存蹭计算结果,避免了花时间对其重复计算。平方算法2使用了一个累积表来存放x[0...i]的总和。与数组与数组相关的问题经常可以通过思考“如何将x[0...i-1]的解扩展为x[0...i]的解?"来解决。
问题2:假设我们想要查找的是总和最接近0的子向量,而不是具有最大总和的子向量,该如何设计算法?
可初始化累加数组cum,使得cum[i]=x[0]+...+x[i]。如果cum[l-1]=cum[u],那么子向量x[l...u]之和就为0.因此可以通过定位cum中最接近的两个元素来找出和最接近0的子向量。这可以通过排序数组,在O(nlogn)时间内完成。
关键算法设计思想:穷举策略、动态规划(保存状态,避免重复计算)、分治策略、扫描策略(如何将x[0...i-1]的解扩展到x[0...i]的解)、累积表。
相关文章推荐
- C10K问题
- Java集合框架官方教程(6):算法
- Linux系统管理实践(7):网络配置
- 深入理解Java国际化
- Hadoop十大应用领域--从互联网行业到传统行业
- Linux内核启动过程分析
- 我的C++实践(7):模板元编程实战
- VMWare组网攻略
- 搭建thrift服务
- 从ping和ping6说起
- 浏览器的渲染原理简介
- 第4部分:资源
- SQLite剖析(4):数据类型
- SQLite剖析(3):C/C++接口介绍
- 千万级并发实现的秘密:内核不是解决方案,而是问题所在!
- 搭建Hadoop集群
- dbm数据库源代码分析(13):ndbm部分
- Hadoop在Last.fm的应用--音乐排行榜
- C标准库源码解剖(13):输入输出函数stdio.h
- 我的C实践(9):位和字节的重排