如何选择数据结构和算法(转)
文章目錄
- 1. 時間、空間復雜度 != 性能
- 2. 拋開數據規模談數據結構和算法都是“耍流氓”
- 3. 結合數據特征和訪問方式來選擇數據結構
- 4. 區別對待IO密集、內存密集和計算密集
- 5. 善用語言提供的類,避免重復造輪子
- 6. 千萬不要漫無目的地過度優化
熟知每種數據結構和算法的功能、特點、時間空間復雜度,還是不夠的。工程上的問題往往都比較開放,往往需要綜合各種因素,比如編碼難度、維護成本、數據特征、數據規模等,最終選擇一個工程的最合適解,而非理論上的最優解。
1. 時間、空間復雜度 != 性能
- 復雜度不是執行時間和內存消耗的精確值
大O表示法表示復雜度的時候,復雜度給出的只能是一個非精確量值的趨勢。 - 代碼的執行時間有時不跟時間復雜度成正比
時間復雜度是O(nlogn)的算法,比時間復雜度是O(n2)的算法,執行效率要高。前提是,算法處理的是大規模數據的情況。 - 對于處理不同問題的不同算法,其復雜度大小沒有可比性
2. 拋開數據規模談數據結構和算法都是“耍流氓”
- 在數據規模很小的情況下,普通算法和高級算法之間的性能差距會非常小。
大部分情況下,我們直接用最簡單的存儲結構和最暴力的算法就可以了。
比如,對于長度在100以內的字符串匹配,我們直接使用樸素的字符串匹配算法就夠了。如果用KMP、BM這些更加高效的字符串匹配算法,實際上就大材小用了。因為這對于處理時間是毫秒量級敏感的系統來說,性能的提升并不大。相反,這些高級算法會徒增編碼的難度,還容易產生bug。
3. 結合數據特征和訪問方式來選擇數據結構
如何將一個背景復雜、開放的問題,通過細致的觀察、調研、假設,理清楚要處理數據的特征與訪問方式,這才是解決問題的重點。
比如前面講過,Trie樹這種數據結構是一種非常高效的字符串匹配算法。但是,如果你要處理的數據,并沒有太多的前綴重合,并且字符集很大,顯然就不適合利用Trie樹了。所以,在用Trie樹之前,我們需要詳細地分析數據的特點,甚至還要寫些分析代碼、測試代碼,明確要處理的數據是否適合使用Trie 樹這種數據結構。
再比如,圖的表示方式有很多種,鄰接矩陣、鄰接表、逆鄰接表、二元組等等。你面對的場景應該用哪種方式來表示,具體還要看你的數據特征和訪問方式。如果每個數據之間聯系很少,對應到圖中,就是一個稀疏圖,就比較適合用鄰接表來存儲。相反,如果是稠密圖,那就比較適合采用鄰接矩陣來存儲。
4. 區別對待IO密集、內存密集和計算密集
- 如果要處理的數據存儲在磁盤,比如數據庫中。那代碼的性能瓶頸有可能在磁盤IO,而并非算法本身。這個時候,你需要合理地選擇數據存儲格式和存取方式,減少磁盤IO的次數。
比如最終推薦人的例子。如果某個用戶是經過層層推薦才來注冊的,獲取他的最終推薦人的時候,就需要多次訪問數據庫,性能顯然不高。
我們知道,某個用戶的最終推薦人一旦確定,就不會變動。所以,可以離線計算每個用戶的最終推薦人,并且保存在表中的某個字段里。當要查看某個用戶的最終推薦人的時候,訪問一次數據庫就可以獲取到。
- 如果數據是存儲在內存中,那還需要考慮,代碼是內存密集型的還是CPU密集型的。
所謂CPU密集型,簡單點理解就是,代碼執行效率的瓶頸主要在CPU執行的效率。我們從內存中讀取一次數據,到CPU緩存或者寄存器之后,會進行多次頻繁的CPU計算(比如加減乘除),CPU計算耗時占大部分。所以,在選擇數據結構和算法的時候,要盡量減少邏輯計算的復雜度。比如,用位運算代替加減乘除運算等。
所謂內存密集型,簡單點理解就是,代碼執行效率的瓶頸在內存數據的存取。對于內存密集型的代碼,計算操作都比較簡單,比如,字符串比較操作,實際上就是內存密集型的。每次從內存中讀取數據之后,我們只需要進行一次簡單的比較操作。所以,內存數據的讀取速度,是字符串比較操作的瓶頸。因此,在選擇數據結構和算法的時候,需要考慮是否能減少數據的讀取量,數據是否在內存中連續存儲,是否能利用CPU緩存預讀。
5. 善用語言提供的類,避免重復造輪子
大部分常用的數據結構和算法,編程語言都提供了現成的類和函數實現。比如,Java中的HashMap就是散列表的實現,TreeMap就是紅黑樹的實現等。除非有特殊的要求,一般直接使用編程語言中提供的這些類或函數。
這些編程語言提供的類和函數,經過無數驗證過的,不管是正確性、魯棒性,都要超過你自己造的輪子。
重復造輪子,并沒有那么簡單。你需要寫大量的測試用例,并且考慮各種異常情況,還要團隊能看懂、能維護。出力不討好。
這也是很多高級的數據結構和算法,比如Trie樹、跳表等,在工程中,并不經常被應用的原因。但這并不代表,學習數據結構和算法是沒用的。深入理解原理,有助于你能更好地應用這些編程語言提供的類和函數。能否深入理解所用工具、類的原理,這也是普通程序員跟技術專家的區別。
6. 千萬不要漫無目的地過度優化
一段代碼執行只需要0.01秒,你非得用一個非常復雜的算法或者數據結構,將其優化成0.005秒。這種微小優化的意義也并不大。維護成本高。
要學會估算。不僅要對普通情況下的數據規模和性能壓力做估算,還需要對異常以及將來一段時間內,可能達到的數據規模和性能壓力做估算。這樣,我們才能做到未雨綢繆,寫出來的代碼才能經久可用。
還有,當你真的要優化代碼的時候,一定要先做Benchmark 基準測試。這樣才能避免你想當然地換了一個更高效的算法,但真實情況下,性能反倒下降了。
總結
以上是生活随笔為你收集整理的如何选择数据结构和算法(转)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java json path_Java使
- 下一篇: LeetCode 461. 汉明距离(异