题目描述:有31,-41,59,26,-53,58,97,-93,-23,84十个数。SUM(N,M)表示从第N个数到到第M个数的和。例如:SUM(2,3)=-41+59=18。问:最大的和是多少?对应的N和M是多少?
这个题目并不难,实现的方法多种多样。最坏的算法,遍历所有的情况,求出最大和。
我在这儿提一个算法的思路,不是最优的,主要是讲解这个算法的。
模拟生物算法(谢谢装配脑袋指正,这应该是遗传算法)。
根据题目,构造生物S,生物S有三个属性N、M、V。N表示开始的下标,M表示结束的下标,和题目中的定义一样。V表示从第N个数到第M个数的和,V和N、M是相关的。因此,可以用S(N,M)表示这个生物。
先期构造10个这样的生物。称为第一代。
生物有两个特性,繁衍性和变异性。
繁衍性:生物S1(N1,M1)和生物S2(N2,M2)繁衍的后代为生物S3(N1,M2)和生物S4(N2,M1)
变异性:生物S1(N1,M1)产生变异,得到S2(N2,M1)或者是S2(N1,M2)
第一代生物通过繁衍和变异得到10个后代(繁衍和变异的比例自定)。这样一共有20个生物。然后这20个生物采用优胜劣汰的方法,保留10个V最大的生物,淘汰10个生物。这称为自然选择的一代。
模拟大自然的自然选择,通过初期的10个生物,经过5代的自然选择,基本上就能得到最优解。
模拟生物算法(遗传算法),就是利用繁衍和变异以及优胜劣汰,保留最优的生物,得到最优解。在某些实际问题中,能达到不错的效果。不过模拟生物算法(遗传算法)不能保证一定能在最优解收敛,但基本上能保证在局部最优解上实现收敛。
本题用模拟生物算法(遗传算法)并不是最合适,我只是利用这道题简单介绍模拟生物算法的基本思想。
欢迎各位提出本题的最优算法,空间上的最优算法、时间上的最优算法。
精彩评论:
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
|
int
[] data =
new
int
[] { 31, -41, 59, 26, -53, 58, 97, -93, -23, 84 };
int
current, max, n = 0, m = 0;
max = current = data[0];
for
(
int
i = 1; i < data.Length; i++)
{
if
(current > 0)
// 第i个元素之前最大子段之和是否大于0
{
current = current + data[i];
//大于0则将第i个元数也加入其中,形成更大子段和
if
(current > max)
{
max = current;
// 更新最大值
m = i;
}
}
else
{
current = data[i];
//小于0则则第i个数做为:第i个元素之前最大子段之和
if
(current > max)
{
max = current;
m = i;
n = i;
}
}
}
Console.Out.WriteLine(
"max:"
+ max);
Console.Out.WriteLine(
"n:"
+ (n + 1));
Console.Out.WriteLine(
"m:"
+ (m + 1));
|
我的思路是这样的:
理论基础:
因为SUM(N,M)最大,那么N-1和M+1号元素一定小于0(如果N>0或者M<Length)
那么,我们可以把这个数列根据"负数"来分成几个"小段":
如上面数列就分成了:
1、31,-41,(59,26),-53,(58,97),(-93,-23),84
2、找出其中SUM最大的,上面的序列中最大的子段为:(58,97)
3、a、我们此时先假设我们的结果就是(58,97);
b、向左两两相加,如果和大于0,比如"SUM((59,26),-53)>0",那么把此段加入我们的结果集:(59,26),-53,(58,97),如果<0则继续向前相加,直到结果>0,如果向前遍历到0号元素,结果还是小于0,那么舍弃。
c、向右两两相加,同理如果和大于0,那么并入结果集,否则继续向右加:SUM(-93,-23)<0,则继续向右加SUM((-93,-23),84)<0则舍弃右边。
4、通过第3步,我们很容易地得到结果集,并且不需要递归,也不需要循环。。。
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
|
struct
SumItem
{
public
int
Start;
public
int
End;
public
int
Sum;
}
public
static
void
FindMaxSum()
{
int
[] data =
new
int
[] { 31, -41, 59, 26, -53, 58, 97, -93, -23, 84 };
var
positiveIndice = (
from
i
in
Enumerable.Range(0, data.Length)
where
data[i] > 0
select
i).ToArray();
List<SumItem> items =
new
List<SumItem>();
SumItem item =
new
SumItem();
item.Start = positiveIndice[0];
item.End = item.Start;
item.Sum = data[item.Start];
bool
inPositiveRange =
true
;
SumItem maxItem = item;
for
(
int
i = positiveIndice[0] + 1; i <= positiveIndice[positiveIndice.Length - 1]; i++)
{
if
(inPositiveRange && data[i] < 0
|| !inPositiveRange && data[i] > 0)
{
inPositiveRange = !inPositiveRange;
items.Add(item);
if
(item.Sum > maxItem.Sum)
{
maxItem = item;
}
item =
new
SumItem();
item.Start = i;
item.End = i;
item.Sum = data[i];
}
else
{
item.End = i;
item.Sum += data[i];
}
}
items.Add(item);
if
(item.Sum > maxItem.Sum)
{
maxItem = item;
}
List<SumItem> items2 =
new
List<SumItem>();
for
(
int
i = 0; i < items.Count - 2; i += 2)
{
int
sum = items[i].Sum;
for
(
int
j = i + 1; j < items.Count; j++)
{
sum += items[j].Sum;
if
(sum > maxItem.Sum)
{
maxItem.Start = items[i].Start;
maxItem.End = items[j].End;
maxItem.Sum = sum;
}
}
}
Console.Out.WriteLine(
"max:"
+ maxItem.Sum);
Console.Out.WriteLine(
"n:"
+ maxItem.Start);
Console.Out.WriteLine(
"m:"
+ maxItem.End);
}
|
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
|
public
int
MaxSum(
int
[] a)
{
// define a 2-d array to hold the sum for each i and j
// t[i, j] indicates the sum from index i to j in array a
int
r = a.Length ;
int
c = r;
int
[,] t =
new
int
[r, c];
int
maxSum =
int
.MinValue;
// Fix the diagonal elements
// t[i, i] equals a[i]
for
(
int
i = 0; i < r; i++)
{
t[i, i] = a[i];
maxSum = Math.Max(maxSum, t[i, i]);
}
// foreach t[i, j], we calculate it's value by add a[j] to t[i, j - 1]
// we only need the upper triangle elements
for
(
int
i = 0; i < r; i++)
{
for
(
int
j = i + 1; j < c; j++)
{
t[i, j] = t[i, j - 1] + a[j];
maxSum = Math.Max(maxSum, t[i, j]);
}
}
return
maxSum;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
int
[] a =
new
int
[12] { -1, 31, -41, 59, 26, -53, 58, 97, -93, -23, 84, -1 };
for
(
int
i = 0; i + 1 < a.Count(); i++)
{
int
count = 0;
if
(a[i] < 0 && a[i + 1] > 0)
{
int
j = i + 1;
while
(j + 1 < a.Count())
{
count += a[j];
if
(a[++j] < 0)
{
Console.WriteLine(
"从第"
+ (i + 1) +
"到"
+ (j - 1) +
"个,最大值为"
+ count);
}
}
}
}
|
算法分析:当我们加上一个正数时,和会增加;当我们加上一个负数时,和会减少。如果当前得到的和是个负数,那么这个和在接下来的累加中应该抛弃并重新清零,不然的话这个负数将会减少接下来的和。
加一个负数,会使当前和curMaxSum 变小,但只要curMaxSum 不小于0,就还有翻盘——反超maxSum的机会,所以maxSum哼着这样的小曲——我的心在等待永远在等待……
此外,如果没有正数(循环结束后maxSum为0),那么需要再次进行遍历所有元素,找到0(最大)就立刻返回,否则就比较出最大的负数来返回。