代码的优化
任何一道题,都有许多解法,但是各种方法的时间、空间复杂度各不相同。在原有代码的基础上进行优化,是我们OIer必不可少的素质。现在我就一道弱小的递归题展现整个优化的过程。
【题目描述】(rqnoj153) 我们要求找出具有下列性质数的个数(包含输入的自然数n): 先输入一个自然数n(n<=1000),然后对此自然数按照如下方法进行处理: 1.不作任何处理; 2.在它的左边加上一个自然数,但该自然数不能超过原数的一半; 3.加上数后,继续按此规则进行处理,直到不能再加自然数为止. 样例: 输入: 6 满足条件的数为(此部分不必输出) 6 16 26 126 36 136 输出: 6 【输入格式】 一个自然数:n 【输出格式】 具有以上性质的数的个数:s 【样例输入】 6 【样例输出】 6
显然,每次取1到n div 2中的一个数,然后进行递归find(x),tot每次加1,直到x=1时无法再分为止。
代码:
1 program number;
2 var
3 n:integer;
4 tot:longint;
5 procedure find(x:integer);
6 var
7 i:integer;
8 begin
9 if x<=1 then exit;
10 for i:=x div 2 downto 1 do
11 begin
12 inc(tot);
13 find(i);
14 end;
15 end;
16 begin
17 readln(n);
18 find(n);
19 inc(tot);
20 writeln(tot);
21 end.
这种方法看上去很简练,但是这弱小的朴素代码当你输入1000时在10s内已经无法出解(当时硬着头皮交了,结果竟然过了..囧)
让我们来分析一下。我们是在1到 x div 2中进行递归。但是自然数包括奇数和偶数(显然*.*||),比如500 div 2和501div 2的值是一样的,都是250。然后从250开始以后两个过程是完全相同的。所以看上去完美的朴素递归,竟然有一半过程是没有用的!
这是我们想到了记忆化搜索。只要记录下来每个数可以扩展的数目,之后再用到就可以直接调用,不用再花时间进行搜索。
代码:
1 program number;
2 var
3 n:integer;
4 f:array[1..10000]of longint;
5 function find(x:integer):longint;
6 var
7 i:integer;
8 begin
9 if f[x]<>0 then exit(f[x]); //当f[x]已经有值时就不用再搜了
10 for i:=1 to x div 2 do //在可能的范围内寻找解
11 f[x]:=f[x]+find(i); //累加,记录以x为原数的可行解
12 f[x]:=f[x]+1;
13 exit(f[x]);
14 end;
15 begin
16 readln(n);
17 f[1]:=1; //初始化f数组
18 writeln(find(n));
19 end.
记忆化搜索的代码不是很好理解。这是我们想到了动态规划。因为从左到右找,从低位到高位,没有后效性,这样记录1到n所有的解,直接输出a[n]即可。
代码:
1 program number;
2 var
3 a:array[1..1000]of longint;
4 i,j,n:integer;
5 begin
6 readln(n);
7 for i:=1 to n do a[i]:=1; //初始化。就只有n一个数也是一种情况
8 for i:=2 to n do //循环n的大小,找出所有n前面的数的解
9 for j:=1 to i div 2 do //循环加上的数
10 a[i]:=a[j]+a[i]; //更新a数组的值
11 writeln(a[n]);
12 end.
好了,整个优化过程就此完毕。其实代码的优化就是这样的一个过程,去除无用枝,提高时间效率,再简化代码。这样我们的代码就能简洁高效,简单易懂。
(saltless原创,转载请注明出处)