調皮的宏

《編程珠璣》第九章一道題目:
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)來計算數組長度。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章