最近在緩慢地讀《編程珠璣(第二版)》(英文名Programming Pearls),書很薄(正文才160多頁),但正如其封面「近20年來衆多大師級程序員一致推崇的做品」所示,這本經典哪能是我一會兒就能讀完的?書中有不少簡潔但有趣的例子分析,更有做者的實際經驗及注意點的闡述,值得一點點地讀,一遍不夠之後再來第2、三遍...程序員
另外,前些天發現51CTO讀書頻道有《編程珠璣(續)》(英文名 More Programming Pearls)整本書的在線閱讀,真是太讚了!算法
回到正題。第二章「啊哈!算法」中做者給出三個「小題目」,其中第二個是:將一個具備n個元素的一維向量x向左旋轉i個位置。例如,將 ABC123DEF456 向左旋轉 3 個位置以後獲得 123DEF456ABC。要求是花費與n成比例的時間來完成這個操做。編程
題目雖小,但正如本章標題「啊哈!算法」所示,做者想要強調的是算法的巧妙與強大,其中有本題的3種解決方案。數組
*.使用臨時數組編程語言
這大概是大多數人都會想到的一種方案,即將所給向量x的前i個複製到臨時數組中,再將剩下的n-i 個元素往左移動i個位置,最後將臨時數組中的i個元素複製回x中後面的位置。編輯器
代碼實現: ide
- public void tempArrReverse(char [] arr, int len, int m){
- char [] temp = new char[m];
- // 將數組前m個元素保存到臨時數組
- for(int i = 0; i < m; i++){
- temp[i] = arr[i];
- }
- // 將數組後面(len-m)個元素前移
- for(int i = m; i < len; i++){
- arr[i-m] = arr[i];
- }
- // 將臨時數組全部元素複製到原數組
- for(int i = 0; i < m; i++){
- arr[len-m+i] = temp[i];
- }
- }
很明顯上面的解法雖然簡單移動,但用到了i個額外的空間,比較浪費空間。因而又有一個做者稱讚「堪稱巧妙的雜技表演」的解法。 函數
*.「巧妙的雜技表演」spa
先將x[0]移到臨時變量t中,再把x[i]移到x[0]騰出來的位置中,x[2i]移到x[i]中,以此類推(每一次移動時將x[]中的下標對其長度n取模),直到又回到從x[0]提取元素,此時應該把臨時變量t放入到上一個位置中,並開始下一次迭代,即對x[1]進行相似的操做。code
找來書中直觀的圖以下:
代碼實現:
- public void juggleReverse(char [] arr, int len, int m){
- int k = 0;
- for(int i = 0; i < m; i++){
- k = 1; //每次迭代都從1倍位移開始
- char temp = arr[i];
- while((k*m+i) % len != i){
- arr[(k-1)*m+i] = arr[k*m+i];
- k++;
- }
- arr[(k-1)*m+i] = temp;
- }
- }
可是,正如做者所提醒的「將這個想法轉換爲代碼,請務必當心!」實際上個人實現中只能是當原向量長度n是位移i的整數倍時纔可行,碰巧書中的也是用 i=3,n=12 這個例子來演示說明的,並且感受此處的譯文有點兒彆扭,理解得不暢,得找找原文是怎麼說的。
若是你在紙上畫一下不是整數倍的狀況,會發現其實狀況複雜得多,有可能某些元素被放到其正確位置的後面去了,而我又不想用不少if...else語句來判斷所出現的狀況,由於我認爲是我沒理解到做者的思路,因此暫時只能這麼實現(還要再思考),但願高手看到能指教一下。=)
*.另外一種更巧妙的思路-原語的力量
其實題目的意思就是字符串反轉(reverse,不知道原文中「旋轉」是否是這個詞,得查證),書中這一小節「原語的力量」提到「有些編程語言提供了旋轉做爲對向量的原語運算」。假如已經有將數組指定部分元素進行反轉的函數(原語操做),接下來的解法就更有意思了。
原問題能夠這樣來看:將原數組當作 ab,須要轉換成 ba,先單獨對子數組a進行反轉獲得a'b(a'表示a反轉後的結果),同理單獨反轉b,獲得 a'b',最後將獲得的 a'b' 一塊兒進行一次反轉可得 (a'b')',而這就是最終結果 ba了,不信你在紙上嘗試一下(或者請看下面更有趣的、可操做的例子)。
代碼實現:
- // 原語操做-反轉數組
- public void reverse(char [] arr, int start, int end){
- int mid = (start + end) / 2;
- for(int s = start, i = 1; i < mid; s++, i++){
- char temp = arr[s];
- arr[s] = arr[end-i];
- arr[end-i] = temp;
- }
- }
- public void useReverse(char [] arr, int len, int m){
- reverse(arr, 0, m);
- reverse(arr, m, len);
- reverse(arr, 0, len);
- }
其實到這裏我認爲做者已經讓我感覺到算法巧妙之處了,對於相同問題的不一樣理解、見解能夠激發出這麼精彩的解法。但實際上你還會發現這本經典書還很頻繁地列舉其餘大師級人物對某問題的解法與運用之類的歷史信息。例如,書中提到 Ken Thompson也是將這段代碼用在其編輯器和轉置代碼的,並且「聲稱即便在那個時候,那也可編入神話故事了」。難道還不夠精彩嗎?請看下面。
*.可操做的證實方法-手搖法
若是要將一個具備10個元素(咱們只有10個手指啊)的數組向上旋轉5個位置,先讓兩隻手的掌心正對你本身,左右放在右手上面(其實兩隻手是贊成平面上的),看下圖:
其實若是親自讀這本書的話,會發現更多精彩!這樣一本書,難道不值得一點點地讀嗎?