算法导论 - 最大和连续子数组

1. 问题描述

已知一个整型数组 src[ st : ed ](本文以 src[ st : ed ] 的形式表示数组 src 的下标范围是从 st 到 ed),数组中一个元素或连续的多个(包括全部)元素可构成了该整型数组的子数组。将每个子数组中所有元素相加,即得到该子数组的和。求一个子数组 subarray[ low : high ],它的和在所有子数组中最大。

本文分别给出利用分治算法和的动态规划的算法解决该问题的示例代码(C 语言实现,python 实现或 JAVA 实现),前者的时间复杂度为 O(n * log n),后者的时间复杂度为 O(n)。

 

2. 分治算法(Divide and Conquer),时间复杂度为 O(n * log n)

可将 src[ st : ed ] 分为左半部份 src[ st : md ] 和右半部份 src[ md+1 : ed ],那么 src[ st : ed ] 的最大和子数组可能是

- src[ st : md ] 的一个子数组

- src[ md+1 : ed ] 的一个子数组

- src[ i : j ] 的一个子数组,其中 st <= i <= md,md+1 <= j <= ed。亦即至少包含 src[md ] 和 src[ md+1 ] 的一个子数组。

如果我们分别计算出上述三个子数组集的最大和子数组,然后比较它们的大小,得到的最大和子数组就是 src[ st : ed ]  最大和子数组。

(1) C 语言实现

#include <stdio.h>
typedef struct{
	int low;
	int high;
	int max_sum;
} SubArray;	//use SubArray to return more than one values: the starting/ending index of the subarray and the sum of the subarray

/* maxCrossSum 计算数组 src[st:ed] 的最大和子数组,并且该子数组至少包含两项: src[md] 和 src[md+1]。注:在调用函数中,保证 src[md] 和 src[md+1] 是 src[st:ed] 的子数组 */ 
void maxCrossSum(int *src, int st, int md, int ed, SubArray *cross_subarray){
	int left_sum = src[md];
	int right_sum = src[md+1];
	cross_subarray->low  = md;
	cross_subarray->high = md+1;
	
	int i;
	int tmp_sum=0;

	for(i=md; i>=st; --i){
		tmp_sum += src[i];
		if(tmp_sum > left_sum){
			left_sum = tmp_sum;
			cross_subarray->low = i;
		}
	}

	tmp_sum = 0;
	for(i=md+1; i<=ed; ++i){
		tmp_sum += src[i];
		if(tmp_sum > right_sum){
			right_sum = tmp_sum;
			cross_subarray->high	= i;
		}
	}
	
	cross_subarray->max_sum = left_sum + right_sum;
}

/* Divide and conquer: divide the src[st:ed] into a left subarray and a right subarray, and calculate the maximum sum subarray of the left subarray and the right subarray. Recursively invoke itself until the length of the target array is 1 */
int maxSumSubarray(int *src, int st, int ed, SubArray *max_sum_subarray){
	
	if(st > ed){
		return 1;
	}

	if(st == ed){
		max_sum_subarray->low = st;
		max_sum_subarray->high = ed;
		max_sum_subarray->max_sum = src[st];
		return 0;
	}

	int md = (st+ed)/2;
	SubArray left_sum, right_sum, cross_sum;

	maxSumSubarray(src, st, md, &left_sum);
	maxSumSubarray(src, md+1, ed, &right_sum);

	maxCrossSum(src, st, md, ed, &cross_sum);
        /* 比较时,需注意必须使用大于等于比较操作符,而不是大于操作符 */
	if(left_sum.max_sum >= right_sum.max_sum && left_sum.max_sum >= cross_sum.max_sum){
		max_sum_subarray->low = left_sum.low;
		max_sum_subarray->high = left_sum.high;
		max_sum_subarray->max_sum = left_sum.max_sum;
	}else if(right_sum.max_sum >= left_sum.max_sum && right_sum.max_sum >= cross_sum.max_sum){
		max_sum_subarray->low = right_sum.low;
		max_sum_subarray->high = right_sum.high;
		max_sum_subarray->max_sum = right_sum.max_sum;
	}else{
		max_sum_subarray->low = cross_sum.low;
		max_sum_subarray->high = cross_sum.high;
		max_sum_subarray->max_sum = cross_sum.max_sum;
	}

	return 0;
}

/* 用例测试,注意递归函数 maxSumSubarray 中,针对目标数组的操作,从下标 st 开始,到下标 ed 止,包含 ed  */
int main(){
	int src[] = {13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7};
	//int src[] = {-13, -3, -25, -20, -3, -16, -23, -18, -20, -7, -12, -5, -22, -15, -4, -7};
	//int src[] = {13, 10, 15, 4, 7};
	//int src[] = {13};

	SubArray max_sum_subarray = {0, 0, 0};
	
	maxSumSubarray(src, 0, 15, &max_sum_subarray);
	
	printf("The largest sum is: %d.\n", max_sum_subarray.max_sum);
	printf("The subarray is from index %d to %d.\n", max_sum_subarray.low, max_sum_subarray.high);
	printf("The subarray is: \n");
	
	int i=0;
	for(i=max_sum_subarray.low; i<=max_sum_subarray.high; ++i){
		printf("%d\t", src[i]);
	}
	printf("\n");

	return 0;
}

(2)python 实现

#!/usr/bin/env python
import os
import sys

def maxCrossSum(src, st, md, ed):
	""" Returns the maximum sum subarray which contains at least two crossing elements. """
	subarray = {}
	left_sum = src[md]
	low = md
	right_sum = src[md+1]
	high = md+1
	cur_sum = src[md]
	# xrange generate a list from args[0] to args[1], increase by args[2]
	for i in xrange(md-1, st, -1):
		cur_sum += src[i]
		if cur_sum > left_sum:
			left_sum = cur_sum
			low = i
	cur_sum = src[md+1]
	for i in range(md+2, ed):
		cur_sum += src[i]
		if cur_sum > right_sum:
			right_sum = cur_sum
			high = i
	subarray['low'] = low 
	subarray['high'] = high
	subarray['sum'] = left_sum + right_sum
	return subarray

def maxSumSubarray(src, st, ed):
	""" Divide and conquer """
	subarray = {}
	if(st==ed):
		subarray['low'] = st 
		subarray['high'] = ed
		subarray['sum'] = src[st]
		return subarray
        # // is floor division operator
	md = (st+ed)//2
	left_sum = maxSumSubarray(src, st, md)
	right_sum = maxSumSubarray(src, md+1, ed)
	cross_sum = maxCrossSum(src, st, md, ed)

	if left_sum['sum'] >= right_sum['sum'] and left_sum['sum'] >= cross_sum['sum']:
		subarray['low'] = left_sum['low'] 
		subarray['high'] = left_sum['high']
		subarray['sum'] = left_sum['sum']
	elif right_sum['sum'] >= left_sum['sum'] and right_sum['sum'] >= cross_sum['sum']:
		subarray['low'] = right_sum['low'] 
		subarray['high'] = right_sum['high']
		subarray['sum'] = right_sum['sum']
	else:
		subarray['low'] = cross_sum['low'] 
		subarray['high'] = cross_sum['high']
		subarray['sum'] = cross_sum['sum']
	return subarray

def main():
	# src = [13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7]
	src = [-13, -3, -25, -20, -3, -16, -23, -18, -20, -7, -12, -5, -22, -15, -4, -7]
	
	max_sum_subarray = maxSumSubarray(src, 0, 15)
	print "The largest sum is: %d." %(max_sum_subarray['sum'])
	print "The subarray is from index %d to %d." %(max_sum_subarray['low'], max_sum_subarray['high'])
	print "The subarray is:"

	for i in range(max_sum_subarray['low'], max_sum_subarray['high']+1):
		print "%d\t" %(src[i]), 
	print 

if __name__ == '__main__':
	main()

3. 动态规划算法(Dynamic Programming, DP),时间复杂度为 O(n)

动态规划过程是:每次决策依赖于当前状态,本次决策后立即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。

针对最大和子数组问题,可以有如下的考虑。

设数组 src[ st : ed ] 前 cur 项构成的子数组 src[ st : cur ] 的最大和子数组为 src[ i : j ],其中 st <= i <= j <= cur,记 T[ cur ] = src[ i ] + src[ i+1 ] + ... + src[ j ] 为 src[ st : cur ] 的子数组最大和。

那么子数组 src[ st : cur+1 ] 的子数组最大和可能是

- T[ cur + 1 ] = T[ cur ],此情况的充要条件是 src[ j + 1 ] + ... + src[ cur + 1 ] <= 0

- T[ cur + 1 ] = src[ p ] + src[ p + 1 ] +... + src[ cur ] + src[ cur + 1 ],其中 i <= p <=  cur + 1

 

(1)C 语言实现

#include <stdio.h>

typedef struct{
	int low;
	int high;
	int max_sum;
} SubArray;

void maxSumSubarray(int *src, int st, int ed, SubArray *max_sum_subarray){
	if(st == ed) {
		max_sum_subarray->low = st;
		max_sum_subarray->high = ed;
		max_sum_subarray->max_sum = src[st];
		return;
	}
	int current_sum = src[st];
	int max_sum = src[st];
	
	int low = 0, high = 0, cur_st = 0;
	int i=0;
	for(i=st+1; i<=ed; ++i){
		if(current_sum < 0){
			current_sum = 0;
			cur_st = i;
		}

		current_sum += src[i];

		if(current_sum > max_sum){
			max_sum = current_sum;
			low = cur_st;
			high = i;
		}
	}
	max_sum_subarray->low = low;
	max_sum_subarray->high = high;
	max_sum_subarray->max_sum = max_sum;
}

int main(){
	int src[] = {13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7};
	//int src[] = {-13, -3, -25, -20, -3, -16, -23, -18, -20, -7, -12, -5, -22, -15, -4, -7};
	//int src[] = {13, 10, 15, 4, 7};
	//int src[] = {13};

	SubArray max_sum_subarray = {0, 0, 0};
	
	maxSumSubarray(src, 0, 15, &max_sum_subarray);
	
	printf("The largest sum is: %d.\n", max_sum_subarray.max_sum);
	printf("The subarray is from index %d to %d.\n", max_sum_subarray.low, max_sum_subarray.high);
	printf("The subarray is: \n");
	
	int i=0;
	for(i=max_sum_subarray.low; i<=max_sum_subarray.high; ++i){
		printf("%d\t", src[i]);
	}
	printf("\n");
	
	return 0;
}

(2)Java 实现

分别构造了 SubArray 类,用以保存找到的最大和子数组;MaxSumDynamic 类,用以实现动态规划算法;MaxSumSubarray 类,用以测试。

package maxSumSubarray;

public class SubArray {
	private int low;
	private int high;
	private int sum;
	
	public SubArray(){
		this.low = 0;
		this.high = 0;
		this.sum = 0;
	}
	public SubArray(int low, int high, int sum){
		this.low = low;
		this.high = high;
		this.sum = sum;
	}
	
	public int getLow() {
		return low;
	}
	public void setLow(int low) {
		this.low = low;
	}
	public int getHigh() {
		return high;
	}
	public void setHigh(int high) {
		this.high = high;
	}
	public int getSum() {
		return sum;
	}
	public void setSum(int sum) {
		this.sum = sum;
	}
	
	@Override
	public String toString(){
		return "from index " + this.low + " to " + this.high + ", the maximum sum is " + this.sum;
	}
}
package maxSumSubarray;

import maxSumSubarray.SubArray;

public class MaxSumDynamic {
	public SubArray maxSumSubarray(int[] src, int st, int ed){
		SubArray res = new SubArray();
		if(st==ed){
			res.setLow(st);
			res.setHigh(ed);
			res.setSum(src[st]);
			return res;
		}
		
		int low = 0, high = 0, cur_st = 0;
		int cur_sum = src[st], max_sum = src[st];
		for(int i = st+1; i<=ed; ++i){
			if(cur_sum < 0){
				cur_st = i;
				cur_sum = 0;
			}
			cur_sum += src[i];
			if(cur_sum > max_sum){
				max_sum = cur_sum;
				low = cur_st;
				high = i;
			}
		}
		res.setLow(low);
		res.setHigh(high);
		res.setSum(max_sum);
		
		return res;
	}
}
package maxSumSubarray;
import maxSumSubarray.MaxSumDynamic;
import maxSumSubarray.SubArray;

public class MaxSumSubArray {
	public static void main(String []args){
		int src[] = {13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7};
        //int src[] = {-13, -3, -25, -20, -3, -16, -23, -18, -20, -7, -12, -5, -22, -15, -4, -7};
        //int src[] = {13, 10, 15, 4, 7};
        //int src[] = {13};
        MaxSumDynamic maxSumDynamic = new MaxSumDynamic();
        
        SubArray res = maxSumDynamic.maxSumSubarray(src, 0, 15);
        
        System.out.println("The largest sum array is: "+res.toString());
        for(int i = res.getLow(); i<=res.getHigh(); ++i){
        	System.out.print(src[i]+"\t");
        }
        System.out.print("\n");
	}
}

编译运行 java 文件

/* javac 命令编译目标java 文件,类之间有依赖关系,所以需要指定参数 -cp,即 -classpath,指定编译类所需的其它 java 文件。参数 -d 指定编译后 class 文件的存放位置 */
javac -cp /path/to/java/folder *.java -d classes
/* java 命令运行目标 class,classpath 参数设定目标 class 的运行环境,即其依赖的其它 class 文件或 jar 包, (需要依赖jar包时,各个jar包之间使用 : 分割开),需要注意 java 包名不作为路径名,不能包括在路径中,而是作为目标 class 的包路径 */
java -cp /path/to/package/folder maxSumSubarray.MaxSumSubArray

得到正确结果。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值