2、最大子序列和问题的四个算法
来源:互联网 发布:淘宝闲鱼官方下载 编辑:程序博客网 时间:2024/06/06 00:40
在本书的第二章,作者提到了最大子序列和问题:在一个列表中,连续多个数的最大值可以达到多少?为方便起见,如果全部数为负数,则最大值为0。例如,输入-2,11,-4,13,-5,-2,答案为20。
作者提到了4个算法,相差很大。本文复习之,并对其算法复杂度进行分析。
1、生成数据
稍微改一下之前的数据,我们用Python写出这样的代码:
# -*- coding:utf-8 -*- # 采用Python3.6编写import randomimport datetimeimport timedataCount = 5000 def genDataBase1(fileName,dataCount): outp = open(fileName,'w') i = 0 while i<dataCount: value=random.random()*dataCount-dataCount/2 mLine = "%f\n"%(value) outp.write(mLine) i += 1 outp.close()if __name__ == "__main__": random.seed() start = time.time() genDataBase1('F:\Data Structures and Algorithm Analysis in C\\Largest_subsequence.txt',dataCount) end = time.time() print('use time:',(end-start)) print('Ok')
2、利用C++实现第一个算法
第一个算法的思路用三个for循环嵌套,第一层是序列的长度,第二层是起始元素的索引值,第三层是在子序列中的索引值。这是一种穷举遍历的方式,算法思路最简单,运算时间最长。
由于C++要计算很长的时间,因此数据量改成了5000个。
用时24.279s,答案为:163908。
显然是一个三阶方法,即O(N^3)。
#include <iostream>#include<string>#include<fstream>#include<vector>#include <sstream>#include <time.h>using namespace std;int main(){ //读取txt的输入流,要#include<fstream> string filename = "F:\\Data Structures and Algorithm Analysis in C\\Largest_subsequence.txt"; ifstream infile(filename.c_str()); string temp; int len, index, i, N,position,length; float value,sum, sumMax; vector<float> list; clock_t start_time = clock();//声明计时器对象并开始计时 ,要#include <time.h> while (getline(infile, temp)) { //利用字符串流将字符串转换为浮点数,要#include <sstream> stringstream stream(temp); stream >> value; //将读入的浮点数添加到列表内 list.push_back(value); } N = list.size(); sumMax = 0; for (len = 1; len <= N; len++) { for (index = 0; index <= N - len; index++) { sum = 0; for (i = 0; i < len; i++) { sum += list[index + i ]; if (sum > sumMax) { sumMax = sum; position = index; length = len; } } } } clock_t end_time = clock(); //static_cast是标准转换运算符,确保类型转换成功。CLOCKS_PER_SEC定义了每秒钟包含多少了时钟单元数 cout << "运行时间:" << static_cast<double>(end_time - start_time) / CLOCKS_PER_SEC << "s" << endl;//输出已流失的时间 cout << "答案为:" << sumMax << endl;//输出在10000个数中最大序列值 cout << "起始位置:" << position << endl; cout << "终止位置:" << position+length-1 << endl; getchar();}
3、利用C++实现第二个算法
第二个算法的思路
用时0.024s,答案为:163908。
第一个算法的第三个for循环中有大量不必要的重复计算,如:计算i到j的和,然而i到j-1的和在前一次的循环中已经计算过,无需重复计算,故该for循环可以去掉。
显然,该算法的时间复杂度是O(N^2)。
#include <iostream>#include<string>#include<fstream>#include<vector>#include <sstream>#include <time.h>using namespace std;int main(){ //读取txt的输入流,要#include<fstream> string filename = "F:\\Data Structures and Algorithm Analysis in C\\Largest_subsequence.txt"; ifstream infile(filename.c_str()); string temp; int i, index, N,start,end; float value,sum, sumMax; vector<float> list; clock_t start_time = clock();//声明计时器对象并开始计时 ,要#include <time.h> while (getline(infile, temp)) { //利用字符串流将字符串转换为浮点数,要#include <sstream> stringstream stream(temp); stream >> value; //将读入的浮点数添加到列表内 list.push_back(value); } N = list.size(); sumMax = 0; for (index = 0; index < N; index++) { sum = 0; for (i = index; i < N; i++) { sum += list[i]; //每次累加后就与sumMax比较 if (sum > sumMax) { sumMax = sum; start = index; end = i; } } } clock_t end_time = clock(); //static_cast是标准转换运算符,确保类型转换成功。CLOCKS_PER_SEC定义了每秒钟包含多少了时钟单元数 cout << "运行时间:" << static_cast<double>(end_time - start_time) / CLOCKS_PER_SEC << "s" << endl;//输出已流失的时间 cout << "答案为:" << sumMax << endl;//输出在10000个数中最大序列值 cout << "起始位置:" << start << endl; cout << "终止位置:" << end << endl; getchar();}
4、利用C++实现第三个算法
用时0.009s,答案为:163908。
该算法利用“分而治之”的思想,将整个数组分成左半部和右半部,则最大子序列可能出现在左侧、右侧、跨两侧。如果是第三种情况,则最大和可以通过求出左侧最大和+左侧剩余靠近中部的所有元素(如果还有剩余)和+右侧最大和+右侧剩余靠近中部的所有元素(如果还有剩余)。
例如对于数列:4 -3 5 -2 -1 2 6 -2
左侧最大和为6,右侧最大和为8,中部最大和为:6-2-1+8=11。比较三个数大小,则中部最大和为全数列的最大子序列和。
该算法采用递归的思想,首先计算出从left到center之间的所有的元素的最大和,从center往左推进,再算出从center到right的所有元素的最大和,从center往右推进,两者之和即为中部最大和。再与递归得到的左侧最大和、右侧最大和进行比较,得到的值最大值为需要求的最大子序列和。
该算法的复杂度分析:
令T(N)是求解大小为N的最大子序列和问题所需要的时间,则如果N=1,算法3执行的时间成为一个单元时间,于是T(1)=1,否则程序需要运行两次递归调用,N=2,执行两次递归调用,每次递归调用的时间为O(N)。这是因为center = (left + right) / 2;和return Max3(maxLeftSum, maxRightSum, maxLeftBorderSum + maxRightBorderSum);均为O(1),直接忽略,两个for循环为O(N),则共计为O(N)。
接着,我们知道T(1)=1;T(N)=T(N/2)*2+O(N)=T(N/2)*2+N。
如果N=2^(m),则T(N)=2^m*T(1)+m*N;
则T(N)=N+m*N; 忽略N项,则T(N)=N*log(N)。
#include <iostream>#include<string>#include<fstream>#include<vector>#include <sstream>#include <time.h>using namespace std;float Max3(float a, float b, float c){ if (a >= b&&a >= c) return a; if (b >= a&&b >= c) return b; if (c >= a&&c >= b) return c;}float MaxSubSum(vector<float> &A, int left, int right){ float maxLeftSum, maxRightSum; float maxLeftBorderSum, maxRightBorderSum; float LeftBorderSum, RightBorderSum; int center, i; if (left == right) { return (A[left] > 0)?A[left]:0; } center = (left + right) / 2; maxLeftSum = MaxSubSum(A, left, center); maxRightSum = MaxSubSum(A, center+1, right); maxLeftBorderSum = 0; LeftBorderSum = 0; for (i = center; i >= left; i--) { LeftBorderSum += A[i]; if (LeftBorderSum > maxLeftBorderSum) { maxLeftBorderSum = LeftBorderSum; } } maxRightBorderSum = 0; RightBorderSum = 0; for (i = center+1; i <= right; i++) { RightBorderSum += A[i]; if (RightBorderSum > maxRightBorderSum) { maxRightBorderSum = RightBorderSum; } } return Max3(maxLeftSum, maxRightSum, maxLeftBorderSum + maxRightBorderSum);}int main(){ //读取txt的输入流,要#include<fstream> string filename = "F:\\Data Structures and Algorithm Analysis in C\\Largest_subsequence.txt"; ifstream infile(filename.c_str()); string temp; int N; float value,sumMax; vector<float> list; clock_t start_time = clock();//声明计时器对象并开始计时 ,要#include <time.h> while (getline(infile, temp)) { //利用字符串流将字符串转换为浮点数,要#include <sstream> stringstream stream(temp); stream >> value; //将读入的浮点数添加到列表内 list.push_back(value); } N = list.size(); sumMax = MaxSubSum(list, 0, N - 1); clock_t end_time = clock(); //static_cast是标准转换运算符,确保类型转换成功。CLOCKS_PER_SEC定义了每秒钟包含多少了时钟单元数 cout << "运行时间:" << static_cast<double>(end_time - start_time) / CLOCKS_PER_SEC << "s" << endl;//输出已流失的时间 cout << "答案为:" << sumMax << endl;//输出在10000个数中最大序列值 getchar();}
5、利用C++实现第四个算法
用时0.012s,答案为:163908。如果将加载数据不计入时间,则0.006s。
该算法理解起来稍微有点困难。设a[i]为和最大序列的起点,则如果a[i]是负的,那么它不可能代表最优序列的起点,类似的,任何负的子序列也不可能是最优子序列的前缀。
我们每次计算已经读入的数据,如果累计和大于上次得到的最大值,则将最大值更新,同时将终止索引更新为当次的索引值。如果累计和小于0,则将前面的数据全部清空,因为前面都是负子序列,同时将起始索引值更新为当次的索引值+1,即下一个数。
显然算法复杂度是线性的,O(N)。这是完美的联机算法:只对数据进行一次扫描,而且在任意时刻,算法都能对它读入的数据给出子序列问题的正确答案。
#include <iostream>#include<string>#include<fstream>#include<vector>#include <sstream>#include <time.h>using namespace std;int main(){ //读取txt的输入流,要#include<fstream> string filename = "F:\\Data Structures and Algorithm Analysis in C\\Largest_subsequence.txt"; ifstream infile(filename.c_str()); string temp; int i, N, start=0, end; float value, sum, sumMax; vector<float> list; clock_t start_time = clock();//声明计时器对象并开始计时 ,要#include <time.h> while (getline(infile, temp)) { //利用字符串流将字符串转换为浮点数,要#include <sstream> stringstream stream(temp); stream >> value; //将读入的浮点数添加到列表内 list.push_back(value); } N = list.size(); sum=sumMax = 0; for (i = 0; i < N; i++) { sum += list[i]; //每次累加后就与sumMax比较 if (sum > sumMax) { sumMax = sum; end=i; } else if (sum < 0) { sum = 0; start=i+1; } } clock_t end_time = clock(); //static_cast是标准转换运算符,确保类型转换成功。CLOCKS_PER_SEC定义了每秒钟包含多少了时钟单元数 cout << "运行时间:" << static_cast<double>(end_time - start_time) / CLOCKS_PER_SEC << "s" << endl;//输出已流失的时间 cout << "答案为:" << sumMax << endl;//输出在10000个数中最大序列值 cout << "起始位置:" << start << endl; cout << "终止位置:" << end << endl; getchar();}
- 2、最大子序列和问题的四个算法
- 算法2:最大子序列和问题
- java 之连续子序列最大和问题的四个解法
- 最大子序列和问题 算法
- 最大子序列和问题 分治算法
- [算法]最大子序列和问题
- 【算法】最大子序列和问题
- 算法的艺术--最大子序列和问题
- 算法笔记1-最大子序列和问题的求解
- 最大子序列和问题的四种算法
- 【数据结构与算法】最大子序列和问题的求解
- 求解最大子序列和问题的线性时间算法
- 最大子序列和的PHP算法
- 最大子序列和的分治算法
- 最大子序列和的算法分析
- 最大的子序列和的问题
- 最大子序列和问题的思考
- 最大子序列和问题的解
- 关于Recyclerview的一些常见问题
- 加油干 golang入坑系列
- DOM对象和js对象以及jQuery对象的区别
- 经典SQL语句
- 第1章 简介
- 2、最大子序列和问题的四个算法
- 用notepad++打造自己的IDE开发环境
- 十、VueJs 填坑日记之在项目中使用Amaze UI
- linux下zookeeper的安装配置
- Android adb.exe程序无法启动的方法
- udev和mdev学习总结
- 直播技术简单介绍之直播协议
- flask模板
- [LeetCode]20_Valid Parentheses