最大值段和(分治法,动态规划)

问题

概念:给定n个整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的子段和的最大值。当所给的整均为负数时定义子段和为0,依此定义,所求的最优值为:Max{0,a[i]+a[i+1]+…+a[j]},1<=i<=j<=n
一个数组a[]:需要求最大子段和的数组;
一个数组b[]:数组a子段和的数组,{a[0],a[0]+a[1],a[0]+a[1]+a[2]...........}
例如,当(a[1],a[2],a[3],a[4],a[5],a[6])=(-2,11,-4,13,-5,-2)时,最大子段和为20。

解题

分治法

思路

如果将所给的序列a[1:n]分为长度相等的两段a[1:n/2]a[n/2+1:n],分别求出这两段的最大子段和,则a[1:n]的最大子段和:
a[]:1..........................n
分段处理:1..........n/2........n

三种情况

  • a[1:n]的最大子段和与a[1:n/2]的最大子段和相同(左边的子段和)
  • a[1:n]的最大子段和与a[n/2+1:n]的最大子段和相同(右边的子段和)
  • a[1:n]的最大子段和为a[i]+…+a[j],并且1<=i<=n/2,n/2+1<=j<=n。(子段和=左边子段和+右边子段和)

对于(1)和(2)两种情况可递归求得,但是对于情况(3),容易看出a[n/2],a[n/2+1]在最大子段中。
第3种情况:我们可以在a[1:n/2]中计算出s1=max(a[n/2]+a[n/2-1]+…+a[i]),0<=i<=n/2,并在a[n/2+1:n]中计算出s2= max(a[n/2+1]+a[n/2+2]+…+a[i]),n/2+1<=i<=n。则s1+s2为出现情况(3)的最大子段和。

C版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include<stdio.h>
#define MAX100
int maxsub(int left,int right);
int a[MAX];
int main()
{
int i;
int count;
scanf("%d",&count);
for(i=0;i<count;i++)
{
scanf("%d",&a[i]);
}
printf("%d/n",maxsub(0,count-1));
return 0;
}

//
int maxsub(int left,int right)
{
int center,i;
int sum,left_sum,right_sum;
int left_max,right_max;
center=(left+right)/2;
//只有1位
if(left==right)
return a[left];
else
{
left_sum=maxsub(left,center);
right_sum=maxsub(center+1,right);
sum=0;
left_max=0;
//遍历左边
//获取左边的最大子段和
for(i=center;i>=left;i--)
{
sum+=a[i];
if(sum>left_max)
left_max=sum;
}
sum=0;
right_max=0;
//遍历右边,获取最大子段和
for(i=center+1;i<=right;i++)
{
sum+=a[i];
if(sum>right_max)
right_max=sum;
}
// 第三种情况,最大子段和,
//最大字段和=左边子段和+右边子段和
sum=right_max+left_max;
if(sum<left_sum)
sum=left_sum;
if(sum<right_sum)
sum=right_sum;
}
return sum;
}

Java版本

1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package max.number;

public class FenZhi {

/**
* 获取左边最大的字段和(连续的)
* @param a
* @return
*/
public static int getLeft(int[] a) {
int left = 0, sum = 0, left_max = 0;
int center = (a.length - 1) / 2;
// 遍历左边
// 获取左边的最大子段和
for (int i = center; i >= left; i--) {
sum += a[i];
if (sum > left_max)
left_max = sum;
}
return left_max;
}

/**
* 获取右边的子段和(连续)
* @param a
* @return
*/
public static int getRight(int[] a) {
int right = a.length - 1, sum = 0, right_max = 0;
int center = (a.length - 1) / 2;
for (int i = center + 1; i <= right; i++) {
sum += a[i];
if (sum > right_max)
right_max = sum;
}
return right_max;
}

public static void main(String[] args) {
int[] a = { -2, 11, -4, 13, -5, -2 };
int left_max = getLeft(a);
int right_max = getRight(a);
int sum = left_max + right_max;
System.out.println("第一种情况:left_max:" + left_max);
System.out.println("第二种情况:right_max:" + right_max);
System.out.println("第三种情况:sum:" + sum);

}
}

2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package max.number.demo3;

/**
* 分治法
*/
public class FenZhiFa {

/**
*
* @param a
* 数组
* @param left
* 最左边
* @param right
* 最右边
* @return
*/
public static int MaxSum(int a[], int left, int right) {
int sum = 0;
if (left == right) {// 如果序列长度为1,则可以直接求解
if (a[left] > 0) {
sum = a[left];
} else {
sum = 0;
}
} else {
int center = (left + right) / 2; // 划分
int leftSum = MaxSum(a, left, center);// 对应情况1,即最大子段和为左边的一段,递归求解, 这里递归获取最大值,和总和的值进行比较
int rightSum = MaxSum(a, center + 1, right);// 对应情况2,即最大子段和为右边的一段,递归求解

int s1 = 0, lefts = 0;// 对应情况3,即最大子段和为左右两段之间的某段,先求解s1
for (int i = center; i >= left; i--) {
lefts += a[i];
if (lefts > s1) {
s1 = lefts;
}
}

int s2 = 0, rights = 0;// 再求解s2
for (int j = center + 1; j <= right; j++) {
rights += a[j];
if (rights > s2) {
s2 = rights;
}
}
sum = s1 + s2;// 计算第3种情况的最大子段和
if (sum < leftSum) {
sum = leftSum;
}
if (sum < rightSum) {
sum = rightSum;
}// 在这三种情况中选择较大者
}
return sum;
}

public static void main(String[] args) {
int[] a = { -2, 11, -4, 13, -5, -2 };
int SUM = MaxSum(a, 0, a.length - 1);
System.out.println("最大子段和为:" + SUM);
}
}

动态规划法

概念:记b[j]=max(a[i]+a[i+1]+..+a[j]),其中1<=i<=j,并且1<=j<=n。则所求的最大子段和为max b[j],1<=j<=n
b[]:最大字段和的数组
b[j]的定义可易知,当b[j-1]>0时,b[j]=b[j-1]+a[j],否则b[j]=a[j]。(先初始化b[0]的数据)
1<=i<=j<=n
a[]:{20,30,-10,20}
b[]:{0,0,0,0},默认a[]一样的长度
b[]={a[0]a[0]+a[1],a[0]+a[1]+a[2]}= {a[0],b[0]+a[1],b[1]+a[2]}:b[0]=a[0]b[1]=a[0]+a[1].............................=b[j-1]+a[j]=b[j]
计算时候:a,b数组从1开始计算

C版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<stdio.h>
int main()
{
int count;
int a[100];
int b[100];
int i;
int max;
scanf("%d",&count);
for(i=0;i<count;i++)
{
scanf("%d",&a[i]);
}
b[0]=a[0];
max=b[0];
for(i=1;i<count;i++)
{
if(b[i-1]>0){
b[i]=b[i-1]+a[i];
}else{
b[i]=a[i];
}
if(b[i]>max){
max=b[i];
}
}
printf("%d/n",max);
return 0;
}

Java版本

1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package max.number;

/**
* 简单的最大子段和
*/
public class Max {

public static void main(String[] args) {

// int[] a = { -2, 11, -4, 13, -5, -2 };
// 待确定的数组
int[] a = { 1, 2, -1, 1, 3, 2, -2, 3, -1, 5, -7, 3, 2, -2, 4 - 5 };
// 子段和的数组,选出最大的那个值
int[] b = new int[a.length];
b[0] = a[0];
int max = b[0];
for (int i = 1; i < a.length; i++) {
if (b[i - 1] > 0) {
b[i] = b[i - 1] + a[i];
} else {
b[i] = a[i];
}
if (max < b[i]) {
max = b[i];
}
}
System.out.println(max);
}
}

2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package max.number;

/**
* 最大子段和
*
* 动态规划方法: 若记b[j]=max(a[i]+a[i+1]+..+a[j]),其中1<=i<=j,并且1<=j<=n。则所求的最大子段和为max
* b[j],1<=j<=n。 由b[j]的定义可易知,当b[j-1]>0时b[j]=b[j-1]+a[j],否则b[j]=a[j]。
* 故b[j]的动态规划递归式为: b[j]=max(b[j-1]+a[j],a[j]),1<=j<=n。
*
* 主要有打印:最大子段数组
*
* 写的不是很好
*
*/
public class MaxArray {

/**
* 输出数组
*
* @param a
*/
public static void prtArray(int[] a) {
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
System.out.print("\n");
}

/**
* 2个数字最大值 返回最大值
*
* @param a1
* @param a2
* @return
*/
public static int max(int a1, int a2) {
if (a1 >= a2 || a1 == a2)
return a1;
return a2;
}

/**
* 设置最大B:最大字段和
*
* a,b,beg数组长度相同
*
* @param n
* 长度,数组位置下标
* @param b
* @param a
* @param beg
*/
public static void setB(int n, int[] b, int[] a, int[] beg) {
// 下标是0
if (n == 0) {
b[n] = a[n];
beg[n] = n;
} else {
// n递减
setB(n - 1, b, a, beg);
// 最大值b[n]
b[n] = max(b[n - 1] + a[n], a[n]);
if (b[n - 1] + a[n] > a[n])
beg[n] = beg[n - 1];
else
beg[n] = n;
}
}

/**
* 最大子数组 b[] 最大子段数组
*
* @param a
*/
public static void maxChildArry(int[] a) {
// 新建a长度的b数组
int b[] = new int[a.length];
// 新建最大子段和的开始的位置
int beg[] = new int[a.length];
// a,b数组第一位相同
b[0] = a[0];
// 遍历数组
for (int n = 0; n < b.length; n++)
setB(n, b, a, beg);
// 最大的数字
int maxsum = b[getMaxElementIndex(b)];
System.out.println("MaxSum: " + maxsum);
PrtMaxChildArry(a, beg[getMaxElementIndex(b)], getMaxElementIndex(b));
}

/**
* 打印最大子段合的子数组
*
* @param aa
* @param beg
* @param end
*/
public static void PrtMaxChildArry(int aa[], int beg, int end) {
System.out.print("The Maxmium Child-Array is: { ");
for (int i = beg; i <= end; i++) {
System.out.print(aa[i] + " ");
}
System.out.println("}\n");

}

/**
* 获取最大值子段合的最大节点下标
*
* @param b
* @return
*/
public static int getMaxElementIndex(int[] b) {
int maxele = 0;
int i;
for (i = 1; i < b.length; i++) {
if (b[maxele] < b[i]) {
maxele = i;
}
}
return maxele;

}

/**
* @param args
*/
public static void main(String[] args) {
// int[] a = { 1, 2, -1, 1, 3, 2, -2, 3, -1, 5, -7, 3, 2, -2, 4 - 5 };
// prtArray(a);
int[] a = { -2, 11, -4, 13, -5, -2 };
maxChildArry(a);
}

}

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×