1. 程式人生 > >轉 程式設計師該如何學習資料結構與演算法?

轉 程式設計師該如何學習資料結構與演算法?

資料結構與演算法的重要性對程式設計師來說不言而喻,本文就來分享下我是如何學習資料結構與演算法的,希望對你們有所幫助。

學習演算法的捷徑就是多刷題

要說捷徑,我覺得就是腳踏實地,多刷題。

但是,如果你是小白,也就是說你連常見的資料結構(如連結串列、樹)以及常見的演算法思想(如遞迴、列舉、動態規劃)這些都沒學過,那麼,我不建議你直接去刷題。而是先去找本書先去學習這些,然後再去刷題。

也就是說,假如你要去諸如leetcode這些網站刷題,那麼你要先具備一定的基礎。這些基礎包括:

  • 常見資料結構:連結串列、樹(如二叉樹)。

  • 常見演算法思想:貪婪法、分治法、窮舉法、動態規劃,回溯法。

以上列出來的算是最基本的吧。就是說你刷題之前,要把這些過一遍再去刷題。如果你連這些最基本的都不知道的話,那麼你在刷題的過程中會很痛苦的,思路也會相對比較少。

總之,千萬不要急,先把這些基本的過一遍,力求理解,再去刷題。這些基礎的資料結構與演算法,我是通過看書學的。那時候看的書是:

  • 演算法分析與分析基礎:這本比較簡單,推薦新手看。

  • 資料結構與演算法分析—-C語言描述:程式碼用C寫的,推薦看。

  • 挑戰程式設計競賽(第二版):也是很不錯的一本書,推薦看。

說實話,我那一段時間幾乎都花在資料結構與演算法上,但刷的題很少,只是書本上的一些例題。所以當我把這些基本的過一遍之後,再去一些網站刷題依舊非常菜。所以千萬別指望以為自己把這些思想學完之後刷題會很牛,只有多刷題,只有多動手實踐,你的靈敏度才會提高起來。

總結下,提高資料結構與演算法沒啥捷徑,最好的捷徑就是多刷題。但是,刷題的前提是你要先學會一些基本的資料結構與演算法思想。

追求完美

如何刷題?如何對待一道演算法題?

我覺得,在做題的時候一定要追求完美,千萬不要把一道題做出來之後,提交通過,然後就趕緊下一道。

演算法能力的提升和做題的數量是有一定的關係,但並不是線性關係。也就是說,在做題的時候要力求一題多解,如果自己實在想不出來其他辦法了,可以去看看別人是怎麼做的,千萬不要覺得模仿別人的做法是件丟人的事。

我做題的時候,可能第一想法就是用很粗糙的方式做,因為很多題採用暴力法都會很容易做,就是時間複雜度很高。之後,我就會慢慢思考,看看有沒其他方法來降低時間複雜度或空間複雜度。最後,我會去看一下別人的做法,當然,並不是每道題都會這樣執行。

衡量一道演算法題的好壞無非就是時間複雜度和空間複雜度,所以我們要力求完美,就要把這兩個降到最低,令它們相輔相成。

我舉道例題吧:

一隻青蛙一次可以跳上1級臺階,也可以跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法?

方法1:暴力遞迴

這道題不難,或許你會採取下面的做法:

public static int solve(int n){
   if(n == 1 || n == 2){
       return n;
   }else if(n <= 0){
       return 0;
   }else{
       return solve(n-1) + solve(n-2);
   }
}

這種做法的時間複雜度很高,指數級別了。但是如果你提交之後僥倖通過了,然後你就接著下一道題了,那麼你就要好好想想了。

方法二:空間換時間

力求完美,我們可以考慮用空間換時間:這道題你去仔細想一想,會發現有很多是重複執行了。所以可以採取下面的方法:

//用一個HashMap來儲存已經計算過的狀態
static Map<Integer,Integer> map = new HashMap();
public static int solve(int n){
   if(n <= 0)return 0;
   else if(n <= 2){
       return n;
   }else{//是否計算過
       if(map.containsKey(n)){
           return map.get(n);
       }else{
           int m = solve(n-1) + solve(n-2);
           map.put(n, m);
           return m;
       }
   }
}

這樣,可以大大縮短時間。也就是說,當一道題你做了之後,發現時間複雜度很高,那麼可以考慮下,是否有更好的方法,是否可以用空間換時間。

方法三:斐波那契數列

實際上,我們可以把空間複雜度弄的更小,不需要HashMap來儲存狀態:

public static int solve(int n){
   if(n <= 0)
      return 0;
   if(n <= 2){
       return n;
   }

   int f1 = 0;
   int f2 = 1;
   int sum = 0;
   for(int i = 1; i<= n; i++){
       sum = f1 + f2;
       f1 = f2;
       f2 = sum;
   }
   return sum;
}

我弄這道題給你們看,並不是在教你們這道題怎麼做,而是有以下目的:

  • 在刷題的時候,我們要力求完美。

  • 我想不到這些方法怎麼辦?那麼你就可以去看別人的做法。之後,遇到類似的題,你就會更有思路、更知道往哪個方向想。

  • 可以從簡單暴力入手做一道題,在空間與時間的衡量中一點點去優化。

推薦一些刷題網站

我一般是在leetcode和牛客網刷題,感覺挺不錯,題目難度不是很大。

在牛客網那裡,我主要刷劍指Offer,不過那裡也有個線上刷leetcode,但裡面的題量比較少。牛客網刷題有個非常方便的地方就是有個討論區,那裡會有很多大佬分享他們的解題方法,不用我們去百度找題解。所以你做完後,實在想不出,可以很方便地去看別人是怎麼做的。至於leetcode,大部分題目官方都有給出答案,也是個不錯的刷題網站。你們可以兩個挑選一個,或者兩個都刷。

當然,還有其他刷題的網站,不過,其他網站沒刷過,不大清楚如何。

再說資料結構

前面主要是說了我平時都是怎麼學習演算法的。在資料結構方法,我只是列舉了你們一定要學習連結串列和樹(二叉堆),但這是最基本的,刷題之前要掌握的。對於資料結構,我列舉下一些比較重要的:

  • 連結串列(如單向連結串列、雙向連結串列)。

  • 樹(如二叉樹、平衡樹、紅黑樹)。

  • 圖(如最短路徑的幾種演算法)。

  • 佇列、棧、矩陣。

對於這些,自己一定要動手實現一遍。你可以看書,也可以看視訊,新手可以先看視訊,不過前期看視訊後期還是要看書的。

例如對於平衡樹,可能你跟著書本的程式碼實現之後,過陣子你就忘記,不過這不要緊,雖然你忘記了,但是如果你之前用程式碼實現過,理解過,那麼當你再次看到的時候,會很快就記起來,很快就知道思路,而且你的抽象能力會在不知不覺中提升起來。之後再學習紅黑樹、資料結構都會學得很快。

最後,動手去做,動手去做,動手去做。重要的話說三遍。可以先學習最基本的,然後刷題,刷題是一個需要長期堅持的事情。在刷題的過程中,當然也可以穿插學習其他資料結構。