《编程珠玑》第九章一道题目:
n是数组最大尺寸的正整数,下面的递归C函数返回数组x[0...n-1]中的最大值:
float arrmax(int n){
if(n==1){
return x[0];
}else{
return max(x[n-1],arrmax(n-1));
}
}
max是一个宏:
#define max(a,b) ((a)>(b)?(a):(b))
分析这个函数的时间复杂度。
初看到这个这个题目时我觉得这个时间复杂度很简单,就是O(n)。但答案提示输入x是降序排列时,时间是O(2**n)。我实际运行了这个函数。
n=10,s=0.469s
n=15,s=6.266s
n=20,s=172.219s
很夸张的时间数据,绝对不是线性增长方式。我觉得这个递归函数的调用方式不就是:
arrmax(n)
arrmax(n-1)
...
arrmax(0)
很明显是线性的。我甚至怀疑我是不是对递归函数理解不对啊。我就另外写了一个简单递归函数:
int cumulate(n){
printf("n=%d\n",n);
if(n==0) return 2;
return cumulate(n-1)+2;
}
测试后,确实是线性调用。在比较那个函数,我决定把arrmax提出来:
float arrmax(int n){
if(n==1){
return x[0];
}else{
int tmp = arrmax(n-1);
return max(x[n-1],tmp);
}
}
突然醒悟了,宏要展开啊!!!
float arrmax(int n){
if(n==1){
return x[0];
}else{
return x[n-1]>arrmax(n-1) ? x[n-1]:arrmax(n-1);
}
}
如果x是降序,时间复杂度有O(n)=2*O(n-1),所以时间复杂度是O(2**n)。折磨你的宏!如果不使用宏下面的写法就是线性的。
float arrmax(int n){
if(n==1){
return x[0];
}else{
int tmp = arrmax(n-1);
return max(x[n-1],tmp);
}
}
虽然我不是很聪明,拿到问题就能解决。但是我想方法、花时间也搞懂了这个问题,也不错。一点感受:遇到问题怎么也想不通时,不要死想,拼命想不能解决问题(这个想法是我上学的想法),遇到问题要想办法、想途径、多动手,先放一放、换个思路的好处:一是让自己大脑休息一下;二是在尝试别的方法、途径过程中会得到启发。像这个题,如果我还是拼命想,我可能短时间还不会注意到宏,很容易跟自己较劲:这应该就是线性的,怎么会是幂增长。情绪上就会着急,影响思路的展开。我就写了个简单的递归函数,体会体会,也让自己平静,于是醒悟了,弄懂了题目。感觉问题不用怕,解决问题的过程可能会曲折,但整个过程还是有趣的。
最近跟老板交流,说不管你做什么,除了完成日常工作,都要去理解事情背后的methodology。每件小事要认真做,同时要跳出来看看这些事件背后的一些共用的方法。这样会在职业发展上走的更远些。这让我思考最近工作的一件小事:系统的一个feature有很多配置文件,不同的系统使用不同的配置文件,需要找出一个系统使用的配置文件。这是个小问题,但也至少包含了两个方法:一是从源代码处查找;二是从这个系统上这个feature的信息查找。如果把源代码理解为产品的一端,那第一个方法是从源头查找;把安装的系统理解为另一端,那第二个方法是从尽头查找。从两个相反的方法去寻找答案。想起看过的堆算法,在初始化堆时,就有两种方法:一是从第一个元素开始构建;另一个是从最后一个元素开始构建,后一个也是我们现在常用的。找使用的配置文件是小事,改进算法是大事,但是不论大小,两件事中都包含了相同的思考方法。
另外最近解决一个C语言的小bug。
最近部门把产品的代码merge到另一个平台上。merge之后测试过程中发现一个进程执行一个操作就死机。首先检查日志系统,没有发现特别的信息。然后用gdb设置断点来检查,最后定位到一个函数,进程崩溃时,backtrace显示参数指针为null。设置断点后,打印参数,是正确的,但进程崩溃时却是null,有点纳闷。进程每次都在同一个地方发生崩溃。这个函数是一个老的函数,我就从崩溃点往前阅读。发现了一段代码类似下面的初始化代码:
char a[100]
memset(a,0,strlen(a));
用strlen(a)计算数组长度显然不对,strlen会从数组开始一直计算到'\0'为止,strlen(a)的值不确定的,可能大于100,可能小于100,也许碰巧是100。 我特意设断点,果然strlen(a)=176,memset初始化后面76个字节时,把参数所在的字节也写为'\0',所以参数就变为了null。应该是用sizeof(a)/sizeof(char)来计算数组长度。