最大连续子列求和
·问题描述
给定一个数组,求出最大的连续子列和及对应的子列。
·求解方法
方法1:使用三重循环的方法枚举出所有的子序列,然后选出最大和子序列,具体的实现代码如下:
//使用3层循环枚举出所有的结果,选出最大的求和子序列 double Enumerate3(double list[], int n,int* Start, int* Len) { double Max_value=0; for (int start = 0; start<n; start++) { for (int len = 1; len <=n-start; len++) {//枚举0~end序列的所有长度的子列和 double temp_max=0; for (int k = start; k < start+ len; k++) { temp_max += list[k]; } if (Max_value < temp_max) { Max_value = temp_max; *Start = start; *Len = len; } } } return Max_value; }
方法2:二重循环的方法,相比三重循环的方法,此方法不每次都重新计算一个子列的和,而是依据前一次的结果直接算出新的子列和,减少一重循环。
//使用2重循环计算出所有的结果 double Enumerate2(double nums[], int n, int* Start, int* Len) { double Max_value = 0; for (int start=0; start<n; start++) { double temp_max = 0; for (int len = 1; len <=n-start ; len++) {//比之前的使用3重循环相比,2重循环不用循环计算 temp_max += nums[len+start-1]; //每一长度的子列和,用前一长度的子列和加上该元素得到当前长度的子列和 if (Max_value < temp_max) { Max_value = temp_max; *Start = start; *Len = len; } } } return Max_value; }
方法3:分治方法。对原序列,取中间为分界点,分成左右两半,最大子序列S恰有三种情况:完全落在左半部分(可递归求解),完全落在右半部分(可递归求解),跨两半。对于跨两半的情况,把最大子序列分成左右两段L和R,注意左段L一定是以分界点为终点的最大子序列(否则若L'>L则S'=L'+R>L+R=S矛盾),即可以从分界点开始向左扫描一遍求出L。
//使用分治的思想来求出最大子列求和 //将数列按照中间的元素分成左右两部分,最大子列和有三种情况: //1:完全在左边 2:完全在右边 3:左右两边都存在 //按照这三种情况,进行递归求解 double DivideConquer(double nums[], int n, int* Start, int* Len) { double Maxvalue = 0; if (n == 0) {//处理边界条件n=0的情况 *Start = 0; *Len = 0; Maxvalue = 0; return Maxvalue; } if (n == 1) {//处理边界条件n=1的情况 if (nums[0] > 0) { *Start = 0; *Len = 1; Maxvalue = nums[0]; } else { *Start = 0; *Len = 0; Maxvalue = 0; } return Maxvalue; } //处理最大子列和完全在左边的情况 int L = n / 2; int start, len; double MaxLeft = DivideConquer(nums, L, &start, &len); //比较左边的最大子列和与记录整个数列最大子列和的Maxvalue的大小关系 if (MaxLeft > Maxvalue) { Maxvalue = MaxLeft; *Start = start; *Len = len; } //处理最大子列和完全在右边的情况 int R = n-L; double MaxRight = DivideConquer(nums+L, R, &start, &len); //比较右边的最大子列和与记录整个数列最大子列和的Maxvalue的大小关系 if (MaxRight > Maxvalue) { Maxvalue = MaxRight; *Start = start; *Len = len; } //最大子序列在左右两边的情况 double LeftMaxValue=0, RightMaxValue=0,LRMaxValue=0; double temp = 0; for (int l = L-1; l >=0; l--) { temp += nums[l]; if (temp > LeftMaxValue) { LeftMaxValue = temp; start = l; } } temp = 0; for (int r = L ; r < n; r++) { temp += nums[r]; if (temp > RightMaxValue) { RightMaxValue = temp; len = r - start+1; } } LRMaxValue = LeftMaxValue + RightMaxValue; if (LRMaxValue > Maxvalue) { Maxvalue = LRMaxValue; *Start = start; *Len = len; } return Maxvalue; }
方法4:使用动态规划的方法,记dp[i]为以第i个元素结尾的最大子列求和,则dp[i+1]有两种情况,当dp[i]<=0时,则有dp[i+1]=nums[i+1],否则dp[i+1]=dp[i]+nums[i+1]
(nums为输入的数组),则可以得到状态转移方程dp[i+1]=max{nums[i+1],dp[i]+nums[i+1]}。注:写动态规划方法的代码时,将每一种状态转移转化为判断语句。
//使用动态规划的方法来求出最大子列 double DynamicProgram(double nums[], int n, int* Start, int* Len) { double Maxvalue = 0; double tempvalue = 0; int len = 0, start = 0; for (int i = 0; i < n; i++) { tempvalue+= nums[i]; len++; if (tempvalue < nums[i]) { tempvalue = nums[i]; start = i; len = 1; } if (tempvalue > Maxvalue) { Maxvalue = tempvalue; *Start = start; *Len = len; } } return Maxvalue; }
方法5:累加序列求和的方法,求出输入数组的累加序列求和数组,对于以第i个元素结尾的最大子列的值,为累加数列中的第i个元素减去前i-1个累加数列中的最小元素之差的值,
从头遍历累加数组,选出最大的子列和即可得到符合问题要求的结果。
//使用累积求和的方法求出每一元素前的累积和 //当前元素的最大子列和为该元素前的累计和减去该元素前的最小累积和 double CumulativeSum(double nums[], int n, int* Start, int* Len) { //求累计和序列 double* cum = new double[n]; if (n > 0) { cum[0] = nums[0]; } for (int i = 1; i < n; i++) { cum[i] = cum[i - 1] + nums[i]; } double min=cum[0] ;//用于标记前i-1个累计和中的最小值 double maxvalue = 0; int start=0; for (int i = 1; i < n; i++) { if (min > cum[i-1]) { min = cum[i-1]; start = i-1; } if (maxvalue < cum[i] - min) { maxvalue = cum[i] - min; *Start = start+1;//最大序列的第一个元素的位置为前i-1个累计和中的最小值的位置加一 *Len = i - start;//这里不用再加上1,因为start为最大序列的第一个元素的位置,不是最大序列开始的位置 } } delete[] cum; return maxvalue; }
完整的代码及测试例子
//求不定长序列的最大、最小子序列和 #include<iostream> using namespace std; void show(double nums[],int start,int len) {//显示数组元素 for (int i = start ; i < start+len; i++) cout <<"["<< nums[i] << "]"<<" "; cout << endl; } //使用3层循环枚举出所有的结果,选出最大的求和子序列 double Enumerate3(double list[], int n,int* Start, int* Len) { double Max_value=0; for (int start = 0; start<n; start++) { for (int len = 1; len <=n-start; len++) {//枚举0~end序列的所有长度的子列和 double temp_max=0; for (int k = start; k < start+ len; k++) { temp_max += list[k]; } if (Max_value < temp_max) { Max_value = temp_max; *Start = start; *Len = len; } } } return Max_value; } //使用2重循环计算出所有的结果 double Enumerate2(double nums[], int n, int* Start, int* Len) { double Max_value = 0; for (int start=0; start<n; start++) { double temp_max = 0; for (int len = 1; len <=n-start ; len++) {//比之前的使用3重循环相比,2重循环不用循环计算 temp_max += nums[len+start-1]; //每一长度的子列和,用前一长度的子列和加上该元素得到当前长度的子列和 if (Max_value < temp_max) { Max_value = temp_max; *Start = start; *Len = len; } } } return Max_value; } //使用分治的思想来求出最大子列求和 //将数列按照中间的元素分成左右两部分,最大子列和有三种情况: //1:完全在左边 2:完全在右边 3:左右两边都存在 //按照这三种情况,进行递归求解 double DivideConquer(double nums[], int n, int* Start, int* Len) { double Maxvalue = 0; if (n == 0) {//处理边界条件n=0的情况 *Start = 0; *Len = 0; Maxvalue = 0; return Maxvalue; } if (n == 1) {//处理边界条件n=1的情况 if (nums[0] > 0) { *Start = 0; *Len = 1; Maxvalue = nums[0]; } else { *Start = 0; *Len = 0; Maxvalue = 0; } return Maxvalue; } //处理最大子列和完全在左边的情况 int L = n / 2; int start, len; double MaxLeft = DivideConquer(nums, L, &start, &len); //比较左边的最大子列和与记录整个数列最大子列和的Maxvalue的大小关系 if (MaxLeft > Maxvalue) { Maxvalue = MaxLeft; *Start = start; *Len = len; } //处理最大子列和完全在右边的情况 int R = n-L; double MaxRight = DivideConquer(nums+L, R, &start, &len); //比较右边的最大子列和与记录整个数列最大子列和的Maxvalue的大小关系 if (MaxRight > Maxvalue) { Maxvalue = MaxRight; *Start = start; *Len = len; } //最大子序列在左右两边的情况 double LeftMaxValue=0, RightMaxValue=0,LRMaxValue=0; double temp = 0; for (int l = L-1; l >=0; l--) { temp += nums[l]; if (temp > LeftMaxValue) { LeftMaxValue = temp; start = l; } } temp = 0; for (int r = L ; r < n; r++) { temp += nums[r]; if (temp > RightMaxValue) { RightMaxValue = temp; len = r - start+1; } } LRMaxValue = LeftMaxValue + RightMaxValue; if (LRMaxValue > Maxvalue) { Maxvalue = LRMaxValue; *Start = start; *Len = len; } return Maxvalue; } //使用动态规划的方法来求出最大子列 double DynamicProgram(double nums[], int n, int* Start, int* Len) { double Maxvalue = 0; double tempvalue = 0; int len = 0, start = 0; for (int i = 0; i < n; i++) { tempvalue+= nums[i]; len++; if (tempvalue < nums[i]) { tempvalue = nums[i]; start = i; len = 1; } if (tempvalue > Maxvalue) { Maxvalue = tempvalue; *Start = start; *Len = len; } } return Maxvalue; } //使用累积求和的方法求出每一元素前的累积和 //当前元素的最大子列和为该元素前的累计和减去该元素前的最小累积和 double CumulativeSum(double nums[], int n, int* Start, int* Len) { //求累计和序列 double* cum = new double[n]; if (n > 0) { cum[0] = nums[0]; } for (int i = 1; i < n; i++) { cum[i] = cum[i - 1] + nums[i]; } double min=cum[0] ;//用于标记前i-1个累计和中的最小值 double maxvalue = 0; int start=0; for (int i = 1; i < n; i++) { if (min > cum[i-1]) { min = cum[i-1]; start = i-1; } if (maxvalue < cum[i] - min) { maxvalue = cum[i] - min; *Start = start+1;//最大序列的第一个元素的位置为前i-1个累计和中的最小值的位置加一 *Len = i - start;//这里不用再加上1,因为start为最大序列的第一个元素的位置,不是最大序列开始的位置 } } delete[] cum; return maxvalue; } int main() { double nums[] = { -3,-1,2,90,-19,-2,-9,11,3,4,5,6,-7 }; int* start=new int; int* len= new int; int n = 13; cout << "原始数组为:"; show(nums, 0, n); cout << "--------------------三重循环的方式--------------------------" << endl; cout << "最大子列求和:" << Enumerate3(nums, n, start, len) << endl; cout << "长度:" << *len << endl; cout << "具体的最大子列为:"<<endl; show(nums, *start, *len); cout << "--------------------二重循环的方式--------------------------" << endl; cout << "最大子列求和:" << Enumerate2(nums, n, start, len) << endl; cout << "长度:" << *len << endl; cout << "具体的最大子列为:" << endl; show(nums, *start, *len); cout << "-----------------------分治的方式---------------------------" << endl; cout << "最大子列求和:" << DivideConquer(nums, n, start, len) << endl; cout << "长度:" << *len << endl; cout << "具体的最大子列为:" << endl; show(nums, *start, *len); cout << "---------------------动态规划的方式--------------------------" << endl; cout << "最大子列求和:" << DynamicProgram(nums, n, start, len) << endl; cout << "长度:" << *len << endl; cout << "具体的最大子列为:" << endl; show(nums, *start, *len); cout << "---------------------累计序列求和的方式----------------------" << endl; cout << "最大子列求和:" << CumulativeSum(nums, n, start, len) << endl; cout << "长度:" << *len << endl; cout << "具体的最大子列为:" << endl; show(nums, *start, *len); delete start; delete len; return 0; }