算法导论例程——最大子数组问题
2016-02-03 01:42
323 查看
最大子数组描述的是这样的一个问题:给定一个整型数组a[],规模为n,求其中连续的m个元素之和使其最大。其中这m个元素被称为是数组a的子数组。
最大子数组问题的求解过程可以分成三种情况,对给定数组,找到中点一分为二,它的最大子数组有可能在左边的部分,有可能在右边的部分,还有可能跨过(cross)左边和右边,那么对于最大子数组在左边或右边的情况,我们又可以把他们看作原问题的一个较小规模的子问题,即对左数组或右数组再次求最大子数组,那么这是递归问题的部分,它的基本问题是当待考察数组规模为1时,那么最大子数组就是它本身,这是递归的终止条件。接下来我们需要考虑的问题就是跨中点的子数组的问题。
对于跨中点的最大子数组,我们可以设定max值,从中点起不断向两侧进行累加并与flag值比较,若大于flag值则替换,而flag值最初始时为一个广义的无穷小值,保证第一次循环时可以替换。
如果想要更精确的知道最大子数组的下标以及sum值,可以声明结构体变量,让两个函数的返回值为该结构体即可。
接下来看dp的解法,非常巧妙。这里考察两个属性,一个是我们要求的最大子数组ms,一个是尾数组tms,其中tms[k,...i] = a[k] + ... + a[i]。我们不考虑整个数组中全是负数元素的情况,那么容易证明,ms和tms的最小值是0。对于数组规模为1的时候,ms = a[0],tms = a[0],而当数组规模大于1时,我们得到这样的关系:
tms[i] = max{0,tms[i-1] + a[i]}
ms[i] = max{tms[0],tms[1],...,tms[i]}
也就是说,当尾数组不大于零时,他对于最大子数组是没有贡献的,而最大子数组其实就是尾数组中最大的。
改进了之后的,支持全负数,可以输出起始位置。
最大子数组问题的求解过程可以分成三种情况,对给定数组,找到中点一分为二,它的最大子数组有可能在左边的部分,有可能在右边的部分,还有可能跨过(cross)左边和右边,那么对于最大子数组在左边或右边的情况,我们又可以把他们看作原问题的一个较小规模的子问题,即对左数组或右数组再次求最大子数组,那么这是递归问题的部分,它的基本问题是当待考察数组规模为1时,那么最大子数组就是它本身,这是递归的终止条件。接下来我们需要考虑的问题就是跨中点的子数组的问题。
对于跨中点的最大子数组,我们可以设定max值,从中点起不断向两侧进行累加并与flag值比较,若大于flag值则替换,而flag值最初始时为一个广义的无穷小值,保证第一次循环时可以替换。
#include <stdio.h> int max_subarray(int a[], int start, int end); int max_crossing_subarray(int a[], int start, int mid, int end); int main() { int a[1000] = { 0 }, n = 0, i = 0, length = 0; printf("请输入待查找数组的规模:"); scanf("%d", &n); length = n; while (n--) scanf("%d", &a[i++]); printf("%d\n", max_subarray(a, 0, length - 1)); return 0; } int max_subarray(int a[], int start, int end) { int mid, left_max, right_max, crossing_max; if (start == end) return a[start]; else { mid = (start + end) / 2; left_max = max_subarray(a, start, mid); right_max = max_subarray(a, mid + 1, end); crossing_max = max_crossing_subarray(a, start, mid, end); if (left_max > right_max&&left_max > crossing_max) return left_max; else if (right_max > left_max&&right_max > crossing_max) return right_max; else return crossing_max; } } int max_crossing_subarray(int a[], int start, int mid, int end) { int i = mid, j = mid + 1, sum = 0, left_sum = -10000, right_sum = -10000; for (i; i > start-1; i--) { sum += a[i]; if (sum > left_sum) left_sum = sum; } sum = 0; //sum在进行新的计算的时候一定要初始化 for (j; j < end + 1; j++) { sum += a[j]; if (sum > right_sum) right_sum = sum; } return left_sum + right_sum; }
如果想要更精确的知道最大子数组的下标以及sum值,可以声明结构体变量,让两个函数的返回值为该结构体即可。
接下来看dp的解法,非常巧妙。这里考察两个属性,一个是我们要求的最大子数组ms,一个是尾数组tms,其中tms[k,...i] = a[k] + ... + a[i]。我们不考虑整个数组中全是负数元素的情况,那么容易证明,ms和tms的最小值是0。对于数组规模为1的时候,ms = a[0],tms = a[0],而当数组规模大于1时,我们得到这样的关系:
tms[i] = max{0,tms[i-1] + a[i]}
ms[i] = max{tms[0],tms[1],...,tms[i]}
也就是说,当尾数组不大于零时,他对于最大子数组是没有贡献的,而最大子数组其实就是尾数组中最大的。
#include <stdio.h> int main() { int a[1000] = { 0 }, i = 0, n = 0, length = 0, tail_max_subarray = 0, max_subarray = 0; printf("请输入待考察数组的规模:"); scanf("%d", &n); length = n; while (n--) scanf("%d", &a[i++]); for (i = 0; i < length; i++) { tail_max_subarray += a[i]; if (tail_max_subarray < 0) tail_max_subarray = 0; if (max_subarray < tail_max_subarray) max_subarray = tail_max_subarray; } printf("最大子数组sum值:%d\n", max_subarray); return 0; }这样写完之后,复杂度一下子就降到o(n)。dp大法好。
改进了之后的,支持全负数,可以输出起始位置。
#include <iostream> using namespace std; int main() { int start = 0, end = 0, i = 0, tms = 0, ms = 0, T = 0, size = 0, a[100010] = { 0 }, num = 1, temp1 = 0; cin >> T; int temp = T; while (T--) { cin >> size; for (i = 1; i <= size; i++) cin >> a[i]; tms = 0; ms = a[1]; start = 1; end = 1; temp1 = 1; for (i = 1; i <= size; i++) { tms += a[i]; if (tms > ms) { ms = tms; end = i; start = temp1; } if (tms <= 0) { tms = 0; temp1 = i + 1; } } cout << "Case " << num++ << ":" << endl << ms << " " << start << " " << end << endl; if (num <= temp) cout << endl; } }
相关文章推荐
- Android客户端与服务器端交互数据之json解析
- 猴年回家前的思绪
- UVALive 3634 熟悉一下STL
- JAVA中的包装类
- Leetcode(1) Two Sum
- 起点——2015年终总结
- addr2line 工具
- java面向对象的基础扫描
- 学习新技术方法感想
- ICPCCamp 2016 Day1 ftiasch's Contest #4(Around the world-树,构造)
- 【HDOJ】2890 Longest Repeated subsequence
- linux文件I/O
- DNS 主从实验
- JAVA中的匿名内部类
- python 多线程笔记(5)-- 生产者/消费者模式
- js获取浏览器高度和宽度值(多浏览器)
- DNS and BIND 笔记总结
- 计算A+B及其结果的标准形式输出
- 个人笔记 html 08 浏览器返回页面时刷新
- JAVA中的Object类