相信每一位錄友都接觸過期間複雜度,「代碼隨想錄」已經也講了上百道經典題目了,是時候對時間複雜度來一個深度的剖析了,很早以前就寫過一篇,當時文章尚未人看,Carl感受有價值的東西值得讓更多的人看到,哈哈。
面試
因此從新整理的時間複雜度文章,正式和你們見面了!算法
「時間複雜度是一個函數,它定性描述該算法的運行時間」。ide
咱們在軟件開發中,時間複雜度就是用來方便開發者估算出程序運行的答題時間。函數
那麼該如何估計程序運行時間呢,一般會估算算法的操做單元數量來表明程序消耗的時間,這裏默認CPU的每一個單元運行消耗的時間都是相同的。性能
假設算法的問題規模爲n,那麼操做單元數量便用函數f(n)來表示,隨着數據規模n的增大,算法執行時間的增加率和f(n)的增加率相同,這稱做爲算法的漸近時間複雜度,簡稱時間複雜度,記爲 O(f(n))。url
這裏的大O是指什麼呢,說到時間複雜度,「你們都知道O(n),O(n^2),卻說不清什麼是大O」。spa
算法導論給出的解釋:「大O用來表示上界的」,當用它做爲算法的最壞狀況運行時間的上界,就是對任意數據輸入的運行時間的上界。code
一樣算法導論給出了例子:拿插入排序來講,插入排序的時間複雜度咱們都說是O(n^2) 。blog
輸入數據的形式對程序運算時間是有很大影響的,在數據原本有序的狀況下時間複雜度是O(n),但若是數據是逆序的話,插入排序的時間複雜度就是O(n^2),也就對於全部輸入狀況來講,最壞是O(n^2) 的時間複雜度,因此稱插入排序的時間複雜度爲O(n^2)。排序
一樣的同理再看一下快速排序,都知道快速排序是O(nlogn),可是當數據已經有序狀況下,快速排序的時間複雜度是O(n^2) 的,「因此嚴格從大O的定義來說,快速排序的時間複雜度應該是O(n^2)」。
「可是咱們依然說快速排序是O(nlogn)的時間複雜度,這個就是業內的一個默認規定,這裏說的O表明的就是通常狀況,而不是嚴格的上界」。如圖所示:
咱們主要關心的仍是通常狀況下的數據形式。
「面試中說道算法的時間複雜度是多少指的都是通常狀況」。可是若是面試官和咱們深刻探討一個算法的實現以及性能的時候,就要時刻想着數據用例的不同,時間複雜度也是不一樣的,這一點是必定要注意的。
以下圖中能夠看出不一樣算法的時間複雜度在不一樣數據輸入規模下的差別。
時間複雜度,不一樣數據規模的差別在決定使用哪些算法的時候,不是時間複雜越低的越好(由於簡化後的時間複雜度忽略了常數項等等),要考慮數據規模,若是數據規模很小甚至能夠用O(n^2)的算法比O(n)的更合適(在有常數項的時候)。
就像上圖中 O(5n^2) 和 O(100n) 在n爲20以前 很明顯 O(5n^2)是更優的,所花費的時間也是最少的。
那爲何在計算時間複雜度的時候要忽略常數項係數呢,也就說O(100n) 就是O(n)的時間複雜度,O(5n^2) 就是O(n^2)的時間複雜度,並且要默認O(n) 優於O(n^2) 呢 ?
這裏就又涉及到大O的定義,「由於大O就是數據量級突破一個點且數據量級很是大的狀況下所表現出的時間複雜度,這個數據量也就是常數項係數已經不起決定性做用的數據量」。
例如上圖中20就是那個點,n只要大於20 常數項係數已經不起決定性做用了。
「因此咱們說的時間複雜度都是省略常數項係數的,是由於通常狀況下都是默認數據規模足夠的大,基於這樣的事實,給出的算法時間複雜的的一個排行以下所示」:
O(1)常數階 < O(logn)對數階 < O(n)線性階 < O(n^2)平方階 < O(n^3)(立方階) < O(2^n) (指數階)
可是也要注意大常數,若是這個常數很是大,例如10^7 ,10^9 ,那麼常數就是不得不考慮的因素了。
有時候咱們去計算時間複雜度的時候發現不是一個簡單的O(n) 或者O(n^2), 而是一個複雜的表達式,例如:
O(2*n^2 + 10*n + 1000)
那這裏如何描述這個算法的時間複雜度呢,一種方法就是簡化法。
去掉運行時間中的加法常數項 (由於常數項並不會由於n的增大而增長計算機的操做次數)。
O(2*n^2 + 10*n)
去掉常數係數(上文中已經詳細講過爲何能夠去掉常數項的緣由)。
O(n^2 + n)
只保留保留最高項,去掉數量級小一級的n (由於n^2 的數據規模遠大於n),最終簡化爲:
O(n^2)
若是這一步理解有困難,那也能夠作提取n的操做,變成O(n(n+1)) ,省略加法常數項後也就別變成了:
O(n^2)
因此最後咱們說:這個算法的算法時間複雜度是O(n^2) 。
也能夠用另外一種簡化的思路,其實當n大於40的時候, 這個複雜度會恆小於O(3 * n^2), O(2 * n^2 + 10 * n + 1000) < O(3 * n^2),因此說最後省略掉常數項係數最終時間複雜度也是O(n^2)。
平時說這個算法的時間複雜度是logn的,那麼必定是log 以2爲底n的對數麼?
其實否則,也能夠是以10爲底n的對數,也能夠是以20爲底n的對數,「但咱們統一說 logn,也就是忽略底數的描述」。
爲何能夠這麼作呢?以下圖所示:
假若有兩個算法的時間複雜度,分別是log以2爲底n的對數和log以10爲底n的對數,那麼這裏若是還記得高中數學的話,應該不能理解以2爲底n的對數 = 以2爲底10的對數 * 以10爲底n的對數
。
而以2爲底10的對數是一個常數,在上文已經講述了咱們計算時間複雜度是忽略常數項係數的。
抽象一下就是在時間複雜度的計算過程當中,log以i爲底n的對數等於log 以j爲底n的對數,因此忽略了i,直接說是logn。
這樣就應該不難理解爲何忽略底數了。
經過這道面試題目,來分析一下時間複雜度。題目描述:找出n個字符串中相同的兩個字符串(假設這裏只有兩個相同的字符串)。
若是是暴力枚舉的話,時間複雜度是多少呢,是O(n^2)麼?
這裏一些同窗會忽略了字符串比較的時間消耗,這裏並不像int 型數字作比較那麼簡單,除了n^2 次的遍歷次數外,字符串比較依然要消耗m次操做(m也就是字母串的長度),因此時間複雜度是O(m * n * n)。
接下來再想一下其餘解題思路。
先排對n個字符串按字典序來排序,排序後n個字符串就是有序的,意味着兩個相同的字符串就是挨在一塊兒,而後在遍歷一遍n個字符串,這樣就找到兩個相同的字符串了。
那看看這種算法的時間複雜度,快速排序時間複雜度爲O(nlogn),依然要考慮字符串的長度是m,那麼快速排序每次的比較都要有m次的字符比較的操做,就是O(m * n * logn) 。
以後還要遍歷一遍這n個字符串找出兩個相同的字符串,別忘了遍歷的時候依然要比較字符串,因此總共的時間複雜度是 O(m * n * logn + n * m)。
咱們對O(m * n * logn + n * m) 進行簡化操做,把m * n提取出來變成 O(m * n * (logn + 1)),再省略常數項最後的時間複雜度是 O(m * n * logn)。
最後很明顯O(m * n * logn) 要優於O(m * n * n)!
因此先把字符串集合排序再遍歷一遍找到兩個相同字符串的方法要比直接暴力枚舉的方式更快。
這就是咱們經過分析兩種算法的時間複雜度得來的。
「固然這不是這道題目的最優解,我僅僅是用這道題目來說解一下時間複雜度」。
本篇講解了什麼是時間複雜度,複雜度是用來幹什麼,以及數據規模對時間複雜度的影響。
還講解了被大多數同窗忽略的大O的定義以及log到底是以誰爲底的問題。
再分析瞭如何簡化複雜的時間複雜度,最後舉一個具體的例子,把本篇的內容串起來。
相信看完本篇,你們對時間複雜度的認識會深入不少!
若是感受「代碼隨想錄」很不錯,趕快推薦給身邊的朋友同窗們吧,他們發現和「代碼隨想錄」相見恨晚!
打算從頭開始打卡的錄友,能夠在「算法彙總」這裏找到歷史文章,不少錄友都在從頭打卡,你並不孤單!