最大连续子数列和问题

问题描述:给定一串整数,找出其中和最大的连续子数列,包括子数列的位置和最大和。

给定整数序列:{0, -3, 6, 8, -20, 21, 8, -9, 10, -1, 3, 6, 5}

其中和最大的连续子数列为:{21, 8, -9, 10, -1, 3, 6, 5}

【PS】

如果序列里都是负数的话,本文的算法返回最大的负数。有一种思想是,如果所有输入数据都是负数,则最大连续子数列为空,值为0,类似空集是任意集合的子集。

【暴力搜索】

依次以序列的每一个数为子数列的开头,遍历所有的情况,时间复杂度为o(n*n)。

  1. //o(n*n)
  2. void MaxSubSet_1(int nums[],int length)
  3. {
  4. if (length == 0)
  5. return;
  6. int start = 0;
  7. int end = 0;
  8. int maxSum = nums[0];
  9. int sum;
  10. for (int i = 0; i < length; i++)
  11. {
  12. sum = nums[i];
  13. for (int j = i; j < length; j++)
  14. {
  15. sum +=nums[j];
  16. if (sum > maxSum)
  17. {
  18. start = i;
  19. end = j;
  20. maxSum = sum;
  21. }
  22. }
  23. }
  24. cout << "Max:" << maxSum <<" ( " << start <<", " << end <<" )" << endl;
  25. }

【线性算法】

此算法基于以下两个定理:

1)如果S(i,j)<0q>j,则S(i,q)不是最大连续子数列和。

2)对于任意i,设A(i,j)为第一个数列,且S(i,j)<0。则对于任意i<=p<=jp<=qA(p,q)要么不是最大连续子数列,要么等于一个已经出现过的最大连续子数列。

故如果出现S(i,j)<0,应当跳到j+1,和置为0

时间复杂度为o(n)。

这个算法是基于两个结论得来的。

1. 如果数列A的某个子列A(i, j) 的和S(i, j) < 0, 那么A(i, q) (q > j) 肯定不是数列A的最大递增子列。

S(i, q) = S(i, j) + S(j+1, q) < 0 + S(j+1, q) = S(j+1, q)

这个结论说明,从位置 i 开始的子列,一旦遇到和为0的子列,后面可以不搜索了,直接从 j+1 开始重新计算。

2. 如果A(i, j) 是数列A以 i 起始的子列中第一个和S(i, j) < 0 的,则对任意 i <= p <= j, p<= q,A(p, q) 的和 S(p, q) 要么小于最大连续子列和,要么与现存的最大连续子列和相等。所以A(p, q)序列可以跳过。

因为S(i ,j) 是第一个<0 的,所以S(i, p-1)必然>=0,则 S(p, q) = S(i, q) - S(i, p-1) <= S(i, q)

i p j q

---|-----|-------|------|---------------------

- 当 q > j 时,由结论1得知, S(i, q) < S(j+1, q),则 S(p, q) < S(j+1, q)

i p q j

---|-----|-------|------|---------------------

- 当q <= j 时,(这个暂时不会证明。。。)

  1. //o(n)
  2. void MaxSubSet_2(int nums[],int length)
  3. {
  4. if (length == 0)
  5. return;
  6. int start = 0;
  7. int sum = nums[0];
  8. int maxStart = 0;
  9. int maxEnd = 0;
  10. int maxSum = nums[0];
  11. for (int i = 1; i < length; i++)
  12. {
  13. sum += nums[i];
  14. if (sum > maxSum)
  15. {
  16. maxStart = start;
  17. maxEnd = i;
  18. maxSum = sum;
  19. }
  20. if (sum < 0)
  21. {
  22. start = i + 1;
  23. sum = 0;
  24. }
  25. }
  26. cout << "Max: " << maxSum << " ( " << maxStart <<", " << maxEnd <<" )" << endl;
  27. }

【分治算法】

分治算法将一个复杂的问题分解成一个个相似的简单的问题,先解决规模较小的问题,然后通过适当的组合来解决复杂的问题。特别适用于目前日渐流行的多核处理器,每个核分别同时计算小问题,再汇总结果,实现算法的并行化。

1. 规模分解

把问题的操作数集合分为2个部分,对两个子数据集合分别操作,最后进行归并。而子数据集合又可以细分成更小的数据集,用递归来实现最合适啦。

把序列 A 均分为前后两个部分 A1 和 A2, 假设我们已经找到了A1和A2的最大连续数列,那么 A 的最大连续数列:

- 全部在A1中

- 全部在A2中

- A1的后半部+A2的前半部

时间复杂度o(nlogn)

  1. //o(nlogn)
  2. void MaxSubSet_3(int nums[],int begin,int length,int& start,int& end,int& maxSum)
  3. {
  4. if (length == 0)
  5. {
  6. start = 0;
  7. end = 0;
  8. maxSum = 0;
  9. return;
  10. }
  11. if (length == 1)
  12. {
  13. start = begin;
  14. end = begin;
  15. maxSum = nums[begin];
  16. return;
  17. }
  18. int start_1, end_1, maxSum_1;
  19. int start_2, end_2, maxSum_2;
  20. int start_3, end_3, maxSum_3;
  21. int headSum, tailSum, sum;
  22. int len_1 = length / 2;
  23. MaxSubSet_3(nums, begin, len_1, start_1, end_1, maxSum_1);
  24. MaxSubSet_3(nums, begin+len_1, length - len_1, start_2, end_2, maxSum_2);
  25. //Compute the max sum of the first half of array
  26. sum = nums[len_1-1];
  27. headSum = nums[len_1-1];
  28. start_3 = len_1-1;
  29. for(int i = len_1-2; i>=begin; i--)
  30. {
  31. sum += nums[i];
  32. if (sum > headSum)
  33. {
  34. headSum = sum;
  35. start_3 = i;
  36. }
  37. }
  38. //Compute the max sum of the second half of array
  39. sum = nums[len_1];
  40. tailSum = nums[len_1];
  41. end_3 = len_1;
  42. for (int i = len_1+1; i < length;i++)
  43. {
  44. sum += nums[i];
  45. if (sum > tailSum)
  46. {
  47. tailSum = sum;
  48. end_3 = i;
  49. }
  50. }
  51. maxSum_3 = headSum + tailSum;
  52. if (maxSum_1 > maxSum_2)
  53. {
  54. start = start_1;
  55. end = end_1;
  56. maxSum = maxSum_1;
  57. }
  58. else
  59. {
  60. start = start_2;
  61. end = end_2;
  62. maxSum = maxSum_2;
  63. }
  64. if (maxSum < maxSum_3)
  65. {
  66. start = start_3;
  67. end = end_3;
  68. maxSum = maxSum_3;
  69. }
  70. }

2. 递推式分解

实际上是数学归纳法。如果知道了规模为 n-1 的最大子数列,现在在这个子列后面多加一个蒜素,如何根据现有的结果得到规模为 n 的最大子数列。在这里,递推式分解是规模分解的一个特例,把问题分为了n-1 和 1 两个部分。也有3种情况:

- 全部在n-1的数列中

- 就是最后一个数

- n-1的数列的后缀+最后一个数

时间复杂度o(n)。

 

转载:http://blog.csdn.net/lilypp/article/details/6124871

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值