最大连续子向量(分治策略和扫描算法)
来源:互联网 发布:linux ntpserver配置 编辑:程序博客网 时间:2024/05/23 11:35
问题及简单算法
本章引入的问题来自一维的模式识别,问题的输入是具有n个浮点数的向量x,输出是输入向量的任何连续子向量中的最大和。
例如,如果输入向量包含以下 N = 10 个元素:
arr[N] = { 31, -41, 59, 26, -53, 58, 97, -93, -23, 84 }
那么该程序的输出为x[2…6]的总和,即187。
该问题分为以下几种情况:
(1)当所有数都是正数时,此时最大子向量就是整个输入向量;
(2)当所有数都是负数时,此时最大子向量就是空向量,总和为0;
(3)当输入向量正负均有时,此时最大子向量需要特殊处理;
对该问题,本章讨论了4种算法,性能逐步提高。
(1)算法1的时间复杂度为T(n)=O(n^3);
(2)算法2及2b的时间复杂度为T(n)=O(n^2);
(3)算法3的时间复杂度为T(n)=O(nlogn);
(4)算法4的时间复杂度为T(n)=O(n);
扫描算法
我们用 sum(i . . . j) 来表示连续子向量 x[i . . . j] 的总和。我们假设连续子向量 x[m . . . k] 的总和是最大,即
sum(m − 1 . . . k) ≤ sum(m . . . k) ≤ sum(m . . . k + 1)
也就是说,
sum(h . . . k) < sum(m . . . k) ∀ h ∈ [m, k) (1)
sum(l . . . k) ≤ sum(m . . . k) ∀ l ∈ [0, m) (2)
对于表达式 (1),我们得到下面的推导:
0 + sum(h . . . k) < sum(m . . . h) + sum(h . . . k)
表达式两边减去 sum(h . . . k),得到
0 < sum(m . . . h)
于是,我们得到结论 1:如果某个连续子向量的和大于零,则以该子向量为前缀的连续子向量的和可能会更大。
对于表达式 (2),我们得到下面的推导:
sum(l . . . m − 1) + sum(m . . . k) ≤ 0 + sum(m . . . k)
表达式两边减去 sum(m . . . k),得到
sum(l . . . m − 1) ≤ 0
于是,我们得到结论 2:如果某个连续子向量的和小于或等于零,则以该子向量为前缀的连续子向量的和不可能大于去掉该子向量前缀之后的子向量的和。
根据结论 1 和结论 2,我们就可以得到下面这个只需要扫描一遍输入向量 x 的算法。
考虑向量x[0..n],假设sum存放了数组中x[s..i-1]这i-s个元素的和,那么temp←sum+x[i]与x[i]进行比较将存在两种情况:
①若sum<0,则temp<x[i],
②若sum>0,则temp>x[i],
而x[i+1..n]向量中元素的和必然是一个定值,设为fixval,
在情况①中temp+fixval<x[i]+fixval,而在情况②中则有temp+fixval>x[i]+fixval,这说明如果当temp<x[i]时,我们可以完全舍弃前i个元素组成的连续子向量,从某种意义上而言,这构成了当前最大向量和值的下界。
我们接着分析,考虑已经被舍弃的向量x[s..i-1],该子向量中必然存在一个上界k使得x[s..k]中的所有元素的和值(设为maxval)是向量x[s..i-1]中最大的,只是将该值与后续元素累加之后无法再大于maxval从而更新它,因此这相当于当前最大向量和值的上界。
有了当前最大向量和值的上界和下界,那么是否就相当于得出了当前已扫描的所有元素中最大子向量的和值及其边界?从某种程度上,这是可以的,但需要通过精巧的构造。
考虑maxval存放的是第一个当前向量中最大子向量的和值,如果在第二个子向量开始处,我们所得出的子向量的和值不断与maxval进行比较,我们会得出两种结果,即要么更新,要么不更新。若更新,则说明第二个向量中的子向量的和值(但不一定最大)大于第一个向量中的最大子向量。那么我们就需要更新相应的边界,更准确的说是下界,而上界则仍由maxval在当前向量中是否继续更新决定。
/*linear version*/ maxval←currval←x[0] lowbound←highbound←0 tmplowbound←0 /*record current vector's lower boundary, if maxval update, update lowbound*/ for i←1 to n-1 do temp←currval+x[i] if temp>x[i] /*indicate that the current vector shouldn't be update*/ then currval←temp else currval←x[i] /*restart a current vector*/ tmplowbound←i if maxval<currval /*maxval is updated by current vector's sum, update the boundary*/ then maxval←currval highbound←i /*once maxval update,just use the label i to update highbound*/ if lowbound≠tmplowbound /*jump from previous vector to current vector*/ then lowbound←tmplowbound
type linearcomp(const vector<type>::iterator iter,\ vector<type>::size_type length,\ size_t *lowbound,size_t *highbound) { if(length<=0) showerr("Length of Vector must larger than 0"); type temp,maxval,currval=maxval=temp=iter[0]; size_t tmplowbound=0; /*record current vector's lower boundary*/ size_t low,high=low=0; for(vector<type>::size_type i=1; i != length; ++i) { temp=currval+iter[i]; if(temp > iter[i]) /*current vector shouldn't be update*/ currval=temp; else { currval=iter[i]; /*restart a current vector*/ tmplowbound=static_cast<size_t>(i); } if(maxval < currval) /*maxval is updated by current vector's sum*/ { maxval=currval,high=i; if(low != tmplowbound) low=tmplowbound; } } if(lowbound != NULL) *lowbound = low; if(highbound != NULL) *highbound = high; return maxval; }
下面将下面4个算法,详细给出程序实现。
<span style="font-family:Microsoft YaHei;"><span style="font-family:Microsoft YaHei;">/************************************************************************//** 《编程珠玑》第八章 算法设计技术* 问题:输入具有n个浮点数的向量x ,输出向量中任何连续子向量中的最大和**//************************************************************************/#include <iostream>#include <algorithm>using namespace std;const int N = 10;float arr[N] = { 31, -41, 59, 26, -53, 58, 97, -93, -23, 84 };/************************************************************************//** 算法一:T(n) = O(n^3)* 算法说明: * 对所有满足0<=i<=j<n的(i,j)整数对进行迭代* 对每个整数对,程序都要计算x[i...j]的总和,并检验该总和是否大于迄今为止的最大总和*(1)当所有输入都是正数时,最大子向量就是整个输入向量*(2)当所有输入都是负数时,最大子向量是空向量,和为0*(3)当前处理情况是,输入序列有正有负* begin:用来存储最大子向量的开始位置* end:用来存储最大子向量的结束位置*//************************************************************************/float cube_algo(float* array, const int size,int& begin,int& end){float maxsofar = 0;for (int i = 0; i < size; ++i){for (int j = i; j < size; ++j){float sum = 0;//计算x[i...j]的总和,并检验该总和是否大于迄今为止的最大总和for (int k = i; k <= j; ++k)sum += array[k];if (sum > maxsofar){maxsofar = sum;begin = i;end = j;}}}return maxsofar;}/************************************************************************//**对于之前的立方算法性能不尽如人意,接下来的第一个平方算法,*利用x[i…j]的总和与x[i…j-1]的总和之间的关系出发,简化算法使得时间复杂度降为O(n^2)*在该算法中,第一个循环内的语句执行n次,第二个循环内的语句在每次执行外循环时至多执行n次,所以总的运行时间为O(n^2);*算法二:T(n) = O(n^2)* 算法说明:*(1)当所有输入都是正数时,最大子向量就是整个输入向量*(2)当所有输入都是负数时,最大子向量是空向量,和为0*(3)当前处理情况是,输入序列有正有负*//************************************************************************/float square_algo(float* array, const int size, int& begin, int& end){float maxsofar = 0;for (int i = 0; i < size; ++i){float sum = 0;for (int j = i; j < size; ++j){sum += array[j];if (sum > maxsofar){maxsofar = sum;begin = i;end = j;}}}return maxsofar;}/************************************************************************//***利用x[i…j]的总和与x[i…j-1]的总和之间的关系出发,简化算法使得时间复杂度降为O(n^2)*在该算法中,通过访问在外循环执行之前就已构建的数据结构的方式在内循环中计算总和,*cumarr中的第i个元素包含x[0…i]中各个数的累加和,所以x[i…j]中各个数的总和*可以通过计算cumarr[j]-cumarr[i-1]得到*算法三:T(n) = O(n^2)* 算法说明:*(1)当所有输入都是正数时,最大子向量就是整个输入向量*(2)当所有输入都是负数时,最大子向量是空向量,和为0*(3)当前处理情况是,输入序列有正有负*//************************************************************************/float square_algo2(float* array, const int size, int& begin, int& end){/** 下面3行代码解决 cumarr[-1] 的问题*/float realsum[N+ 1] = { 0 };float* curarr = realsum + 1;curarr[-1] = 0;for (int i = 0; i < size; i++){curarr[i] = curarr[i - 1] + array[i];}float maxsofar = 0;for (int i = 0; i < size; ++i){float sum = 0;for (int j = i; j < size; ++j){sum = curarr[j] - curarr[i];if (sum > maxsofar){maxsofar = sum;begin = i;end = j;}}}return maxsofar;}/************************************************************************//*分治算法思想:要解决规模为n的问题,可递归的解决两个规模近似为n/2的子问题,然后对他们的答案进行合并可以得到整个问题的答案。在本例中,初始问题要处理大小为n的向量。所以将它划分为两个子问题的最自然的方法就是创建两个大小近似相等的子向量,分别称为a , b;然后递归的找出a,b元素中总和最大的子向量;此时分为三种情况:最大子向量位于a中;最大子向量位于b中;最大子向量跨越a,b之间的边界,处于整个向量的中间;其时间复杂度满足:T(n) = 2T(n/2) + O(n)解此递归式可得T(n)=O(nlogn);* 算法说明:*(1)当所有输入都是正数时,最大子向量就是整个输入向量*(2)当所有输入都是负数时,最大子向量是空向量,和为0*(3)当前处理情况是,输入序列有正有负*//************************************************************************/float max_float(float a, float b){return a > b ? a : b;}float nlogn_algo(float* array, int left, int right){if (left > right) return 0;if (left == right) return (0>array[left] ? 0 : array[left]);int mid = left + (right - left) / 2;float lmax = 0,sum = 0;for (int i = mid; i >left ; i--){sum += array[i];lmax = lmax > sum ? lmax : sum;}float rmax = 0;sum = 0;for (int i = mid+1; i <=right; i++){sum += array[i];rmax = rmax > sum ? lmax : sum;}return max(max(lmax + rmax, nlogn_algo(array, left, mid)), nlogn_algo(array, mid + 1, right));}/************************************************************************//*扫描算法是操作数据的最简单算法:从数组最左端(元素x[0])开始扫描,到元素最右端(元素x[n-1])为止,并记下所遇到的总和最大的子向量。* 算法说明:*(1)当所有输入都是正数时,最大子向量就是整个输入向量*(2)当所有输入都是负数时,最大子向量是空向量,和为0*(3)当前处理情况是,输入序列有正有负*//************************************************************************/float scan_algo(float *arr, const int n){float maxSoFar = 0, maxEndInHere = 0;//该问题的关键在于maxEndInHere变量,它是结束位置i-1之前的最大子向量之和for (int i = 0; i < n; i++){maxEndInHere = max_float(maxEndInHere + arr[i], 0);maxSoFar = max(maxSoFar, maxEndInHere);}return maxSoFar;}int main(){cout << "The input numbers are: " << endl;for (int i = 0; i < N; i++)cout << arr[i] << "\t";int begin = -1, end = -1;cout << endl << "cube_algo: The max sum is : ";cout << cube_algo(arr, N, begin, end) << endl;cout << "The position is : " << begin << "\t" << end << endl;cout << endl << "square_algo: The max sum is : ";cout << square_algo(arr, N, begin, end) << endl;cout << "The position is : " << begin << "\t" << end << endl;cout << endl << "square_algo2: The max sum is : ";cout << square_algo(arr, N, begin, end) << endl;cout << "The position is : " << begin << "\t" << end << endl;cout << endl << "nlogn_algo: The max sum is : ";cout << nlogn_algo(arr,0, N) << endl;cout << "The position is : " << begin << "\t" << end << endl;cout << endl << "scan_algo: The max sum is : ";cout << scan_algo(arr, N) << endl;cout << "The position is : " << begin << "\t" << end << endl;system("pause");return 0;}</span></span>
- 最大连续子向量(分治策略和扫描算法)
- 一个数组中找出连续子向量的最大和 分治算法 扫描算法O(n)
- 【扫描算法】求一个向量的元素和最大的连续子向量 --编程珠玑
- 扫描算法求一个向量的元素和最大的连续子向量
- 求连续向量的最大子和问题(扫描算法)
- 分治算法实现最大连续子数组和
- 编程珠玑 8.3分治法的错误 连续子向量的最大和
- 连续子数组的最大和:线性扫描和分治法
- 《算法导论》读书笔记(三)——分治策略之和最大连续子序列
- 算法导论笔记ch4_分治策略_最大连续子数组之和
- 算法导论,分治策略,最大子数组
- 连续子向量的最大和
- 最大连续子向量和的计算
- 找出连续子向量的最大和
- 分治策略__解决最大连续子数组的问题
- #初学算法#分治策略---最大子数列的暴力求解和分治求解对比
- 分支策略。。。最大连续子数组和
- 《github一天一道算法题》:分治法求数组最大连续子序列和
- c++11之 Lambda表达式
- NYOJ 5 Binary String Matching (KMP)
- 微信语音下载到服务器 上传到七牛转换为mp3
- 采用PHP函数uniqid生成一个唯一的ID
- Swift必备开发库(高级篇)
- 最大连续子向量(分治策略和扫描算法)
- ButterKnife笔记
- 史上比较用心的纯代码实现AutoLayout
- Android多线程总结(转载)
- Linux驱动子系统之I2C 硬件特性
- 高仿QQ最新版本的安卓APP源码 界面华丽 还有WIFI-FTP功能
- 简单的例子谈java的并发
- NYOJ 35 表达式求值 (栈 stack)
- linux yum命令详解