找了好一阵,终于找到了效率不高的代码:
n:=LeftCount;
while n>=0do
//寻找最高的起点
begin
if A[n]>LeftSum then
Dec
else
break;
end;
如果在此处改用折半查找,无疑会降低算法的总体时间复杂度。但是,我认为这样做带来的
效率提升不会有多明显——我的目的是在一个升序序列中,从后往前找到比指定值小的第一个
数。采用while循环是最笨但最直接的方法,虽然它的时间复杂度与LeftCount相当,但是每一步
的操作非常简单——判断一个数、递减、比较;比较而言,如果采用折半法,时间复杂度将
降低到Log2(LeftCount),但它的每一步都要进行两个数的比较——A[n]要小于等于LeftSum,
并且A[n+1]要大于LeftSum,只有这样才能满足“第一个小于”的要求,除此之外,为了实现
“折半”,还要进行额外的运算。因此,可以认为折半法每一步花费的时间相当于前者的2倍。
另外,对本问题来说,N在20到50之间,一般情况下可以取36作为平均。由于本问题的特殊性,
LeftCount的统计分布必然是从0到N单调递减的,并且基本上是以几何级数递减(因为每个大的
LeftCount将递归调用若干个较小的LeftCount过程——除非遇到A[0]>LeftSum的情况)——越
靠近0的LeftCount出现的概率越大。为了证实我的估计,我进行了统计:
...
n:=LeftCount;
{$IFDEF CountLeftSum}
Inc(LSCount[n]);
//统计数组
{$ENDIF}
while ...
结果如下:
0: 60636 //注意,0..7呈等比下降数列
1: 30390
2: 15094
3: 7765
4: 4578
5: 2827
6: 1648
7: 1113
8: 730
9: 432
10: 299
11: 153
12: 103
13: 48
14: 45
15: 44
16: 42
17: 40
18: 38
19: 35
20: 33
21: 29
22: 26
23: 22
24: 19
25: 15
26: 5
27: 4
28: 3
29: 3
30: 3
31: 3
32: 2
33: 1
34: 1
35: 2
——哈哈!比我想象的还要理想!完全可以认为,平均说来,LeftCount为1左右(大于5的
情况完全可以忽略)!!!在这样的条件之下,折半法根本毫无优势可言,甚至很可能用更
长的时间!
看来,我采用的“笨办法”是极为高效的。
)
我认为,除非在算法的系统框架上进行改进,否则就不会有太大的速度提升。