最大子序列和整理,复杂度从O(n^3)到O(n)

求一个序列的子序列的最大值,这是一个经典算法,这里稍作整理。

问题:任给一个整数序列,如{-2, 5, 7, 11, -4, 13, -5, -2, -6, 3, -1, 3},求出这个序列中连续子序列的和的最大值,这个例子中最大值为32,子序列为{5, 7, 11, -4, 13}。

方法一:最简单的暴力法。确立一个起点,一个终点,计算起点到终点的和。下面代码中int brute_force(int* a,int n)实现了该算法,时间复杂度由

计算可得为O(n^3)。

方法二:对暴力法的优化,使用累加的方法,确定一个起点之后,将前面序列的和累加到终点,选取一个最大值。下面代码中int improved_brute_force(int* a, int n),实现了该算法。时间复杂度由

可得为O(n^2)。

方法三:采用分治法的思想,从中间点将序列分成两个子序列,整个序列的最大子序列位置分三种情况(见图,此图来自老师PPT)
1.在左侧子序列中
2.在右侧子序列中
3.包含中间点,两个序列各有一部分。


下面的int divide_conquer(int* a,int start,int end)方法实现了该算法,该递归实现的递归式为T(n)=2T(n/2)+cn。由Master theorem可得时间复杂度为O(nlogn).

方法四:采用动态规划的思想,又称为Online algorithm,在线学习算法的定义:线上算法是一种处理输入资料的独特形式,算法开始的时候不需要所有的数据都准备好,而是对逐步输入的数据进行处理,并在输入完成之后输出运算结果。与之相对的是离线算法,假设所有数据在算法开始时都已经准备好,选择排序是离线算法,插入排序是在线算法。用动态规划处理最大子序列长度的思想为用变量max_value保存最大子序列的和,用current_value保存从i=0累加的的和,如果current_value大于max_value则保存,否则放弃,如果current减小到零,则将current_value从0重新开始计因为再讲current_value累加的话只会减少子序列的和。下面的int dynamic_prom(int* a,int len)实现了该算法。该算法时间复杂度为O(n)

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define len_array 12

/*
求最大子序列的和
*/

//brute force:时间复杂度n^3
int brute_force(int* a,int n){ 
	int i,j,k;
	int max_value = INT_MIN;
	int current_value;

	for(i=0;i<n;i++){			//以i为起点
		for(j=i;j<n;j++){		//以j为终点
			current_value = 0;
			for(k=i;k<=j;k++){				//求从i到j的子序列的和
				current_value += a[k];
			}
			if(current_value > max_value){
				max_value = current_value;
			}
		}	
	}

	return max_value;
}

//提高的brute force, 时间复杂度n^2
int improved_brute_force(int* a, int n){
	int i,j;
	int max_value = INT_MIN;
	int current_value = 0;
	for(i=0;i<n;i++){			//以i为起点
		current_value = 0;		//设置以i为起点的子序列的初始值为0
		for(j=i;j<n;j++){		//以j为终点
			current_value += a[j];
			if(current_value>max_value){
				max_value = current_value;
			}
		}
	}
	return max_value;
}

//分治法,时间复杂度nlog(n)
//从中间点将序列分成两个子序列,整个序列的最大子序列位置分三种情况
//1.在左侧子序列中
//2.在右侧子序列中
//3.包含中间点,两个序列各有一部分
int divide_conquer(int* a,int start,int end){
	
	int mid;
	int mid_to_left_max = 0;
	int mid_to_right_max = 0;
	int i,current_left=0,current_right=0;
	int side_max,mid_max,max,left_max,right_max;

	if(start==end)
		return a[start];

	mid = (start+end)/2;
	left_max = divide_conquer(a,start,mid);		//寻找左侧子序列的最大值
	right_max = divide_conquer(a,mid+1,end);	//寻找右侧子序列的最大值
	
	for(i=mid;i>=0;i--){						//寻找以中间点为起点,向左延伸的最大子序列
		current_left += a[i];
		if(current_left>mid_to_left_max){
			mid_to_left_max = current_left;
		}
	}
	
	for(i=mid;i<=end;i++){						//寻找以中间点为起点,向右延伸的最大子序列
		current_right += a[i];
		if(current_right>mid_to_right_max){
			mid_to_right_max = current_right;
		}
	}

	mid_max = mid_to_left_max+mid_to_right_max-a[mid];
	side_max = left_max>right_max?left_max:right_max;
	max = side_max>mid_max?side_max:mid_max;

	return max;
}

//又称为Online algorithm,在线学习算法的定义:线上算法是一种处理输入资料的独特形式,算法开始的时候
//不需要所有的数据都准备好,而是对逐步输入的数据进行处理,并在输入完成之后输出运算结果。与之相对的是
//离线算法,假设所有数据在算法开始时都已经准备好,选择排序是离线算法,插入排序是在线算法
//用动态规划处理最大子序列长度的思想为用变量max_value保存最大子序列的和,用current_value保存从i=0累加的
//的和,如果current_value大于max_value则保存,否则放弃,如果current减小到零,则将current_value从0重新开始计
//因为再讲current_value累加的话只会减少子序列的和
int dynamic_prom(int* a,int len){
	int current_value = 0;
	int max_value = INT_MIN;
	int i;

	for(i=0;i<len;i++){
		current_value += a[i];
		if(current_value>max_value){	//当前值大于最大值,更新
			max_value = current_value;
		}
		
		if(current_value<0){			//当前值小于零,从0开始
			current_value = 0;
		}
	}

	return max_value;
}



int main(){
	int a[12] = {-2, 5, 7, 11, -4, 13, -5, -2, -6, 3, -1, 3};

	printf("brute force: %d\n",brute_force(a,len_array));

	printf("improved brute force: %d\n",improved_brute_force(a,len_array));

	printf("divide conquer: %d\n",divide_conquer(a,0,len_array-1));

	printf("dynamic programming: %d\n",dynamic_prom(a,len_array));

	printf("\n");
	
	return EXIT_SUCCESS;
}

你可能感兴趣的:(最大子序列和整理,复杂度从O(n^3)到O(n))