由快速排序引申而來--如何學習算法

  大部分人都知道,其實我們一般都不需要去學什麼算法。除非是,要麼是學生(立志參加ACM),或者做純粹算法研究的專業人員,再者要麼是爲了進一些大公司而準備面試,要麼是純興趣使然。真正因爲參加工作要用很多算法的人實在是少之又少。當然,或許做圖像處理或者數據處理,數據挖掘,再或者,有關搜索引擎等等之類的東西(恕我才識淺陋,從這篇文章看各自相關算法的應用領域:當今世界最爲經典的十大算法--投票進行時,亦可窺知一二)。我甚至認爲,絕大部分的人是肯定掌握了一些跟數據結構有關的基本算法的,所以,總而言之,我始終相信,一個人,尤其是學生,實在是沒有必要花太多精力在算法相關上的。

    但有兩個朋友關於快速排序的理解,讓我對此前的觀點--認爲不需要多學算法,稍稍產生了懷疑。

  • 在7月初找工作的時候,一位朋友(以前做過面試官)跟我聊天時談到,剛畢業的學生當中,十個人有九個人不能把快速排序寫出來。我當時就震驚了。因爲,我不信。我無法理解一個如此基礎而重要的簡單的快速排序竟有那麼多的人寫不出來。總共才二十多來行代碼阿。於是,我想試試自己的臨場發揮能力。便當即把快速排序寫了出來(不過,到底還是有一些細節性的錯誤,後來又修改了一次)。這是其一。
  • 昨晚認識了兩個在廣東的朋友,他們於第二天將到我現在這家公司實習。其中一位朋友談到他在公司面試的時候,面試官問他快速排序的時間複雜度和空間複雜度時,他說他完全懵了,此前根本就沒有這個意識,不知道。我當即告訴他,說快速排序的時間複雜度平均爲O(N*logN),最壞爲O(N^2),空間複雜度由於是由數組存儲,所以,當然是O(N)。我對他說,你拿一張紙和一支筆,在五分鐘之內,我能當場把快速排序準確無誤的寫出來。朋友連稱厲害。我當時明白了兩點:1、的確有人對快速排序不夠理解,寫不出快速排序,甚至連最基本的瞭解都沒有;2、很多人稱我厲害,原來我只是知道一些他們不知道的但同時又夠簡單的基礎算法。

    自從我開始寫經典算法研究系列之後,便有很多很多的朋友叫我寫一篇如何學習算法的文章,或者談談算法學習方面的心得與體會。本來之前是因爲一來算法這個東西在實際工作中應用不多(就像上面提到的快速排序算法,標準庫裏一個sort直接搞定,根本不再需要你去寫十幾行的代碼,既如此,又何必重複造輪子)。二來是我個人覺得,學習這種事情你學就對了,只要你有興趣,便什麼問題都能解決。應該不用像一些“大家”,“專家”那樣扯起長篇大論。但現在,情況已經不同了(我特別討厭那些在我面前裝的人。說什麼自己算法很牛逼,或者自以爲是,或說一個搞算法研究的人可以去做圖書編輯,搞笑)。

    關於如何學習算法,下面,個人簡略談三點(大方之家見諒):

  1. 興趣。學好任何一個東西,首先便必須得具備興趣。算法也不例外。如果你對算法實在沒有興趣,也大可不必擔憂。畢竟,各有所愛。
  2. 態度,重試程度。如果你覺得因爲算法在實際工作中應用不多,或者標準庫裏都封裝了一切。或者認爲,最基本的快速排序都不必去了解,那麼此文可以一掃而過了。
  3. 實踐。看書,如數據結構方面的教材,一定要把各種最基本的數據結構,如數組,字符串,棧,堆,隊列,樹,圖弄得通通透透(其實,看我之前整理的微軟面試100題,也有很大幫助,因爲那些面試題大多都跟數據結構和算法相關的,而我也是這麼做過來的)。然後可以讀編程珠璣,算法導論(很多人都說算法導論看不懂,實則是其上面的很多數學證明,我也不是很懂。具體可多在紙上畫畫,如紅黑樹的相關操作及代碼),再加上推敲--反覆思考,反覆研究,最後實踐--結合實際應用,編碼實現(寫代碼實現一個算法比研透一個算法更有用)。僅以上,無它也。

    很多朋友還問到,我是怎麼學習算法,或者說學習過程是怎樣的,我是這樣子學的:從去年12月開始接觸算法起(寫第一篇算法文章,A*搜尋算法),我先是因爲要寫有關算法的文章,所以很多的時候都要去參考資料,包括書籍和網上的,特別注重把一個算法真正闡述清楚,而要闡述清楚的話,那麼我自己本身就得先把那個算法真正弄懂弄透,即只有自己懂了,我纔可以講明白。然後是我一直在做那有關微軟等公司的面試題(大部都涉及到數據結構和算法),然後,再與他人多多交流,最後,在反覆推敲和思考某一個算法之後,我便開始編寫代碼實現某個算法了。這就是我學算法的學習過程

    ok,閒不多扯,接下來,咱們來具體看看快速排序算法的實現。如下圖所示,是前兩天在公司練習的快速排序,諸位可以參考下:

 

    個人認爲,完整寫出這個快速排序還是不難的,不過要注意很多細節問題,如:

  1. 我們知道,快速排序的分治partition過程有兩種方法,一種是上面所述的兩個指針索引一前一後逐步向後掃描的方法(算法導論上採用的是這種方法),還有一種方法是兩個指針從首位向中間掃描的方法(大多數的人和一般的教材採用的是這第二種首尾向中間掃描法)。
  2. partition過程中,要注意一些邊界值問題。如i、j索引的初始化,for循環中,j從數組中第一個位置l 到倒數第二個位置h-1處,且是當data[j]<=privot(是小於等於,非小於),然後找到了之後,i++,再交換。最後還有一次data[i+1]與data[h]的交換。最後,便是返回值的問題,返回i+1。
  3. 第三個要注意的方面是遞歸處。if(l<h),才進入遞歸。

    主要要注意的問題就是上述三個方面。只要把握好細節,那麼快速排序算法,二十幾行自能搞定。下圖是快速排序算法的第二種實現(就是上面所提到的首尾向中間掃描法):

    這裏,也要注意一個小問題,就是上述partition過程中,while循環內,下述A,B兩個過程的順序不能搞錯。因爲如果把A,B兩個過程調換過來的話,即相當於把data[l]先賦給了privot,所以那樣先從低處掃描的話,那麼data[i],即privot覆蓋了高處data[j] 的值。像原來的正常順序,爲什麼又可以了列?因爲data[l]已經保存在privot裏面,所以 ,纔不怕privot 覆蓋了低處的值。

  while(data[j]>=privot && i<j)
   j--;
  data[i]=data[j];    //以上三行爲A過程
  while(data[i]<=privot && i<j)
   i++;
  data[j]=data[i];    //以上三行爲B過程

    你如果還不理解的話,看下如下的示例過程就知道了。首先,我們以正常的過程來進行第一趟排序:

   a:3   8   7   1   2   5   6   4   //以第一個元素爲主元
        2   8   7   1        5   6   4
   b:2        7   1   8   5   6   4
   c:2   1    7       8   5   6   4
   d:2   1        7   8   5   6   4
   e:2   1   3   7   8   5   6   4   //最後補上,關鍵字3

    那如果上面的代碼A,B過程的順序被調換,也就是被弄錯了列?如下:

while(data[i]<=privot && i<j)
   i++;
  data[j]=data[i];    //以上三行爲B過程

 while(data[j]>=privot && i<j)
   j--;
  data[i]=data[j];    //以上三行爲A過程

    排序的過程將如下所述,低處的元素8將覆蓋高處的元素4,元素4不像上面的元素3被事先保存在privot裏,所以,元素4就丟了:

    a:3   8   7   1   2   5   6   4   //以第一個元素爲主元
         3        7    1   2   5   6   8
   b:.....

    OK,有關快速排序算法實現的更多版本,請參考此篇文章:十二、快速排序算法之所有版本的c/c++實現

    結語(下述第2點所示的圖片語錄來自一匿名網友,不過,他表達了我想說的意思

  1. 雖然實際項目中80%不需要自己構造算法,但對足夠基礎的算法有所瞭解是理所必然的。快速排序(僅指學生)寫不出來的確沒什麼,但與此同時,表露了一個不重視基礎算法的態度問題。

    

來自結構之法 算法之道


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