您的位置:首页 > 其它

求最大子段和的几种方法以及性能测试

2015-07-29 21:57 330 查看
我是计院出身的,但是感觉自己算法方面比较薄弱,这些天抽些时间把以前的问题拿出来总结一下。最大子段和问题是一个比较基础的问题,在《数据结构与算法:c语言实现》比较靠前的位置。

为了便于说明,假设存在一个数组a,长度length.a和b做下标且a小于b。

算法一

时间复杂度:O(n^3)

算法思想:对于每一个可能的区间[a,b]都去求其和,找到其中的最大值。

int max_sub_sum_1(const int a[], int length){
int thisSum, maxSum = 0, i, j, k;
for (i = 0; i < length; i++){
for (j = 0; j < length; j++){
thisSum = 0;
for (k = i; k < j; k++){
thisSum += a[k];
}
maxSum = thisSum>maxSum ? thisSum : maxSum;
}
}
return maxSum;
}


这个算法是最简单粗暴的一种,也的确非常好理解。

算法二

时间复杂度:O(n^2)

算法思想:和上一个算法大同小异,区别是先只确定开始的下标a。

int max_sub_sum_2(const int a[],int length){
int thisSum, maxSum, i, j;
maxSum = 0;
for (i = 0; i < length; i++){
thisSum = 0;
for (j = i; j < length; j++){
thisSum += a[j];
if (thisSum>maxSum){
maxSum = thisSum;
}
}
}
return maxSum;
}


这算法其实也很好理解哒~

算法三

时间复杂度:O(n*logn)

算法简述:使用分治法策略。将这个数组一分为二,则最大子段有三种情况

在左半部分

在右半部分

左半部分和右半部分都有

先看看代码

int max_sub_sum_3_part(const int a[], int left, int right){
//分治策略
//【112,2131,-123,123,-993】
//最大子序列可能的情况有三种,左半部,右半部,或者左右结合部
int leftMaxSum = 0, rightMaxSum = 0, leftBorderMaxSum = 0, rightBorderMaxSum = 0;
int leftBorderSum = 0,rightBorderSum = 0;
int center, i;
//递归的终点应该是此“单元”的规模缩减到了1
if (left == right){
//base 到达了单个
if (a[right] > 0){
return a[right];
}
else{
return 0;
}
}
center = (left + right) / 2;
leftMaxSum = max_sub_sum_3_part(a, left, center);
rightMaxSum = max_sub_sum_3_part(a, center+1, right);
//
for (i = center+1; i<=right; i++){
rightBorderSum += a[i];
if (rightBorderSum>rightBorderMaxSum){
rightBorderMaxSum = rightBorderSum;
}
}
for (i = center; i >=left; i--){
leftBorderSum += a[i];
if (leftBorderSum > leftBorderMaxSum){
leftBorderMaxSum = leftBorderSum;
}
}
return max(max(leftMaxSum, rightMaxSum), leftBorderMaxSum + rightBorderMaxSum);
}


//为了保持其一致性
int max_sub_sum_3(const int a[], int length){
//使用了分治法
return max_sub_sum_3_part(a, 0, length - 1);

}


左半部分和右半部分的最大子段用递归求得,而左半部分和右半部分兼有的最大子段就需要用点小手段了。之后将三者进行比较,返回三者最大者。

其实最不好理解的部分就是递归的“终点”,如果当前的值大于0则返回当前值,如果小于零则返回0。这个应该怎么考虑呢?对于当前的单位而言,有两种选择,选或者不选。不选则为0。

要想理解这个算法,我觉得最重要的还是理解清楚分治的目的到底是什么。——>求出当前范围内的最大值。无论当前单元是多大,哪怕只有1(虽然特殊,但是目的没有变)。

算法四

这个算法其实是在大二的时候就已经看到过了,但是直到今日重新看起,还是觉得很秒。尤其是和前几个算法对比,不光代码精简,时间复杂度也降低到了O(n)。程序设计老师所说的好的代码是艺术的,这大概就是一份好的代码。

动态规划

时间复杂度:O(n)

算法思想:动态规划

int max_sub_sum_4(const int a[], int length){
int thisSum = 0, maxSum = 0;
int i;
for (i = 0; i < length; i++){
thisSum += a[i];
if (thisSum>maxSum){
maxSum = thisSum;
}
else if (thisSum < 0){
thisSum = 0;
}
}
return maxSum;
}


这个方法刚看上去有些反直觉。我的第一反应就是:这真的可以得到最大子段和么?

首先我们看一看这个问题是否符合动态规划算法要求的性质:

能采用动态规划求解的问题的一般要具有3个性质:

最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。

这个是肯定的,假设有两个段,A和B且,A-B=a,则A的最大子段和就是B的最大子段和+(a或者0)。

无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。

如题,算法四并没有影响之前的状态。

有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)

每一次更新都要同maxSum进行比较。

这个问题确实可以使用动态规划的思想!

回头再看看算法四:其实每一次循环过程都是对上一次循环的拓展。

我来拙劣地表示一下

thisSum(n)=max(thisSum(n-1)+thisNum,thisNum)

性能测试

为了生成足够大的数组,我写了一个方法,可以生成一组任意大小的随机数。为了方便之后的对比,还会把这组随机数写入到文件中保存。

#include <time.h>
#include <stdio.h>
#include <random>
#include <stdlib.h>
#include <math.h>
#define random(x) (rand()%x)

/******声称测试数据**************************/
int get_test_data_f(int *data, const char* file_name, int size, int max_num){
FILE* output_file = fopen(file_name, "w+");
if (NULL == output_file){
return -1;
}
srand((int)time(0));
for (int i = 0; i < size; i++){
data[i] = random(max_num);
fprintf(output_file, "%d\n", data[i]);
}
return 0;
}


调用测试的函数如下

#include "_data.h"
#include "stdlib.h"
#include "max_sub_sum.h"
#include "binary_search.h"
#define SIZE 1000
#define MAX_NUM 200
int main(){
clock_t start_time, finish_time;

int *a = (int*)malloc(sizeof(int)*SIZE);
get_test_data_f(a, "data_intent_test.txt", SIZE, MAX_NUM);
for (int i = 0; i < SIZE; i++){
printf("%d\n", a[i]);
}
start_time = clock();
printf("%d\n",max_sub_sum_1(a,SIZE));
finish_time = clock();
printf("算法1运行时间为:%d\n", (finish_time - start_time));

start_time = clock();
printf("%d\n", max_sub_sum_2(a, SIZE));
finish_time = clock();
printf("算法2运行时间为:%d\n", (finish_time - start_time));

start_time = clock();
printf("%d\n", max_sub_sum_3(a, SIZE));
finish_time = clock();
printf("算法3运行时间为:%d\n", (finish_time - start_time));

start_time = clock();
printf("%d\n", max_sub_sum_4(a, SIZE));
finish_time = clock();
printf("算法4运行时间为:%d\n", (finish_time - start_time));

system("pause");
return 0;
}


当测试规模为1000时



当测试规模为5000时



这个时候明显看到了算法一和算法二已经很吃性能了!

当测试规模打到10000时



我去minecraft烧了一片树林才跑完,这个时候算法一已经没法看了。。

但是当测试规模达到了100000时呢?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: