字符串反轉,神奇的算法-讀《編程珠璣(第二版)》

最近在緩慢地讀《編程珠璣(第二版)》(英文名Programming Pearls),書很薄(正文才160多頁),但正如其封面“近20年來衆多大師級程序員一致推崇的作品”所示,這本經典哪能是我一下子就能讀完的?書中有很多簡潔但有趣的例子分析,更有作者的實際經驗及注意點的闡述,值得一點點地讀,一遍不夠以後再來第二、三遍...

另外,前些天發現51CTO讀書頻道有《編程珠璣(續)》(英文名 More Programming Pearls)整本書的在線閱讀,真是太讚了!

 

回到正題。第二章“啊哈!算法”中作者給出三個“小題目”,其中第二個是:將一個具有n個元素的一維向量x向左旋轉i個位置。例如,將 ABC123DEF456 向左旋轉 3 個位置之後得到 123DEF456ABC。要求是花費與n成比例的時間來完成這個操作。

題目雖小,但正如本章標題“啊哈!算法”所示,作者想要強調的是算法的巧妙與強大,其中有本題的3種解決方案。

*.使用臨時數組

這大概是大多數人都會想到的一種方案,即將所給向量x的前i個複製到臨時數組中,再將剩下的n-i 個元素往左移動i個位置,最後將臨時數組中的i個元素複製回x中後面的位置。

代碼實現: 

  1. public void tempArrReverse(char [] arr, int len, int m){ 
  2.         char [] temp = new char[m]; 
  3.         // 將數組前m個元素保存到臨時數組 
  4.         for(int i = 0; i < m; i++){ 
  5.             temp[i] = arr[i]; 
  6.         } 
  7.         // 將數組後面(len-m)個元素前移 
  8.         for(int i = m; i < len; i++){ 
  9.             arr[i-m] = arr[i]; 
  10.         } 
  11.         // 將臨時數組所有元素複製到原數組 
  12.         for(int i = 0; i < m; i++){ 
  13.             arr[len-m+i] = temp[i]; 
  14.         } 
  15.     } 

 

很明顯上面的解法雖然簡單移動,但用到了i個額外的空間,比較浪費空間。於是又有一個作者稱讚“堪稱巧妙的雜技表演”的解法。 

*.“巧妙的雜技表演”

先將x[0]移到臨時變量t中,再把x[i]移到x[0]騰出來的位置中,x[2i]移到x[i]中,以此類推(每一次移動時將x[]中的下標對其長度n取模),直到又回到從x[0]提取元素,此時應該把臨時變量t放入到上一個位置中,並開始下一次迭代,即對x[1]進行類似的操作。

找來書中直觀的圖如下:

代碼實現:

  1. public void juggleReverse(char [] arr, int len, int m){ 
  2.         int k = 0
  3.         for(int i = 0; i < m; i++){ 
  4.             k = 1;  //每次迭代都從1倍位移開始 
  5.             char temp = arr[i]; 
  6.             while((k*m+i) % len != i){ 
  7.                 arr[(k-1)*m+i] = arr[k*m+i]; 
  8.                 k++; 
  9.             } 
  10.             arr[(k-1)*m+i] = temp; 
  11.         } 
  12.     } 


但是,正如作者所提醒的“將這個想法轉換爲代碼,請務必小心!”實際上我的實現中只能是當原向量長度n是位移i的整數倍時纔可行,碰巧書中的也是用 i=3n=12 這個例子來演示說明的,而且感覺此處的譯文有點兒彆扭,理解得不順暢,得找找原文是怎麼說的。

如果你在紙上畫一下不是整數倍的情況,會發現其實情況複雜得多,有可能某些元素被放到其正確位置的後面去了,而我又不想用很多if...else語句來判斷所出現的情況,因爲我認爲是我沒理解到作者的思路,所以暫時只能這麼實現(還要再思考),希望高手看到能指教一下。=) 

*.另一種更巧妙的思路-原語的力量

其實題目的意思就是字符串反轉(reverse,不知道原文中“旋轉”是不是這個詞,得查證),書中這一小節“原語的力量”提到“有些編程語言提供了旋轉作爲對向量的原語運算”。假如已經有將數組指定部分元素進行反轉的函數(原語操作),接下來的解法就更有意思了。

原問題可以這樣來看:將原數組看成 ab,需要轉換成 ba,先單獨對子數組a進行反轉得到a'ba'表示a反轉後的結果),同理單獨反轉b,得到 a'b',最後將得到的 a'b' 一起進行一次反轉可得 (a'b'',而這就是最終結果 ba了,不信你在紙上嘗試一下(或者請看下面更有趣的、可操作的例子)。

代碼實現:

  1. // 原語操作-反轉數組 
  2.     public void reverse(char [] arr, int start, int end){ 
  3.         int mid = (start + end) / 2
  4.         for(int s = start, i = 1; i < mid; s++, i++){ 
  5.             char temp = arr[s]; 
  6.             arr[s] = arr[end-i]; 
  7.             arr[end-i] = temp; 
  8.         } 
  9.     } 
  10.      
  11.     public void useReverse(char [] arr, int len, int m){ 
  12.         reverse(arr, 0, m); 
  13.         reverse(arr, m, len); 
  14.         reverse(arr, 0, len); 
  15.     } 

 

其實到這裏我認爲作者已經讓我感受到算法巧妙之處了,對於相同問題的不同理解、看法可以激發出這麼精彩的解法。但實際上你還會發現這本經典書還很頻繁地列舉其他大師級人物對某問題的解法與運用之類的歷史信息。例如,書中提到 Ken Thompson也是將這段代碼用在其編輯器和轉置代碼的,而且“聲稱即使在那個時候,那也可編入神話故事了”。難道還不夠精彩嗎?請看下面。 

*.可操作的證明方法-手搖法

如果要將一個具有10個元素(我們只有10個手指啊)的數組向上旋轉5個位置,先讓兩隻手的掌心正對你自己,左右放在右手上面(其實兩隻手是同意平面上的),看下圖:

 

其實如果親自讀這本書的話,會發現更多精彩!這樣一本書,難道不值得一點點地讀嗎?

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章