数据结构算法入门--一文了解什么是复杂度
圖片來自 Pixabay,作者:TeroVesalainen
2019 年第 74 篇文章,總第 98 篇文章
本文大約 3000?字,閱讀大約需要 10?分鐘
最近會開始更新一個數(shù)據(jù)結(jié)構(gòu)算法的學習系列,同時不定期更新 leetcode 的刷題。
這是第一篇文章,在開始介紹各種數(shù)據(jù)結(jié)構(gòu)和算法之前,先了解下什么是復雜度,包括時間復雜度和空間復雜度。
今日推薦閱讀:
精心挑選了這些牛人的公眾號,來看看吧!
什么是復雜度分析
數(shù)據(jù)結(jié)構(gòu)和算法解決是“如何讓計算機更快時間、更省空間的解決問題”。
因此需從執(zhí)行時間和占用空間兩個維度來評估數(shù)據(jù)結(jié)構(gòu)和算法的性能。
分別用時間復雜度和空間復雜度兩個概念來描述性能問題,二者統(tǒng)稱為復雜度。
復雜度描述的是算法執(zhí)行時間(或占用空間)與數(shù)據(jù)規(guī)模的增長關(guān)系。
為什么需要復雜度分析
和性能測試相比,復雜度分析有不依賴執(zhí)行環(huán)境、成本低、效率高、易操作、指導性強的特點。
掌握復雜度分析,將能編寫出性能更優(yōu)的代碼,有利于降低系統(tǒng)開發(fā)和維護成本。
如何進行復雜度分析
對于時間復雜度的分析,通常使用大O復雜度表示法,表示代碼執(zhí)行時間隨數(shù)據(jù)規(guī)模增長的變化趨勢,所以,也叫作漸進時間復雜度(asymptotic time complexity),簡稱時間復雜度。
用公式表示,就是?T(n) = O(f(n))表示,其中?T(n)?表示算法執(zhí)行總時間,f(n)?表示每行代碼執(zhí)行總次數(shù),而?n?表示數(shù)據(jù)的規(guī)模。
由于時間復雜度描述的是算法執(zhí)行時間與數(shù)據(jù)規(guī)模的增長變化趨勢,所以常量階、低階以及系數(shù)實際上對這種增長趨勢不產(chǎn)決定性影響,所以在做時間復雜度分析時可以忽略這些項。
具體分析的時候,有下列三個方法:
單段代碼只看循環(huán)次數(shù)最多的部分;
多段代碼取復雜度最高的:即有個多個循環(huán),但只看循環(huán)次數(shù)量級最高的那段代碼
乘法法則--嵌套代碼進行乘積:多個循環(huán)嵌套,就是相乘
常見的時間復雜度
按照數(shù)量級遞增,常見的時間復雜度量級有:
常量階?O(1)
對數(shù)階?O(logn)
線性階?O(n)
線性對數(shù)階?O(nlogn)
平方階?O(n^2),立方階?O(n^3)…k次階?O(n^k)
指數(shù)階?O(2^n)
階乘階?O(n!)
其中,最后兩種情況是非常糟糕的情況,當然?O(n^2)?也是一個可以繼續(xù)進行優(yōu)化的情況。
接下來簡單介紹上述復雜度中的幾種比較常見的:
O(1)
O(1)?表示的是常量級時間復雜度,也就是只要代碼的執(zhí)行時間不隨 n 的增大而增長,都記作?O(1)?。一般只要算法不包含循環(huán)語句和遞歸語句,時間復雜度都是?O(1)
像下列代碼,有 3 行,但時間復雜度依然是O(1),而非?O(3)。
a?=?3 b?=?4 print(a?+?b)O(logn)、O(nlogn)
O(logn)?也是一個常見的時間復雜度,下面是一個?O(logn)?的代碼例子:
i?=?1 count?=?0 n?=?20 while?i?<=?n:count?+=?1i?*=?2 print('while?循環(huán)運行了?{}?次'.format(count))這段代碼其實就是每次循環(huán)都讓變量 i 乘以 2,直到其大于等于 n,這里我設置 n=20,然后運行了后,輸出結(jié)果是循環(huán)運行了 5 次。
實際上這段代碼的結(jié)束條件,就是求?2^x=n?中的?x?是等于多少,那么循環(huán)次數(shù)也就知道了,而求?x?的數(shù)值,方法就是??,那么時間復雜度就是?
假如上述代碼進行簡單的修改,將?i *= 2?修改為?i *= 3?,那么同理可以得到時間復雜度就是?。
但在這里,無論是以哪個為對數(shù)的底,我們都把對數(shù)階的時間復雜度記為?O(logn)。
這里主要原因有兩個:
對數(shù)可以互換,比如?,也就是?,常量
基于前面的理論,系數(shù)可以被忽略,也就是這里的常量 C 可以忽略
基于這兩個原因,對數(shù)階的時間復雜度都忽略了底,統(tǒng)一為?O(logn)?。
至于?O(nlogn)?,根據(jù)乘法法則,只需要將對數(shù)階復雜度的代碼,運行 n 次,就可以得到這個線性對數(shù)階復雜度了。
注意,?O(nlogn)?是非常常見的時間復雜度,常用的排序算法如歸并排序、快速排序的時間復雜度都是?O(nlogn)
O(m+n)、O(m*n)
前面介紹的情況都是只有一個數(shù)據(jù)規(guī)模?n?,但這里介紹有兩個數(shù)據(jù)規(guī)模的情況--m和?n。
#?O(m+n) def?cal(n,?m):result?=?0for?i?in?range(n):result?+=?ifor?j?in?range(m):result?+=?j?*?2return?result簡單的代碼示例如上述所示,如果事先無法評估?m?和?n?的量級大小,那么這里的時間復雜度就沒法選擇量級最大的,所以其時間復雜度就是?O(m+n)?。
同理,對于嵌套循環(huán),就是?O(m*n)?的時間復雜度了。
最好、最壞、平均、均攤時間復雜度
這四種復雜度的定義如下:
最好情況時間復雜度:代碼在最理想的情況下執(zhí)行的時間復雜度;
最壞情況時間復雜度:代碼在最壞情況下執(zhí)行的時間復雜度;
平均情況時間復雜度:代碼在所有情況下執(zhí)行的次數(shù)的加權(quán)平均值表示;
均攤時間復雜度:代碼執(zhí)行的所有復雜度情況中,絕大多數(shù)都是低級別的復雜度,個別情況會發(fā)生最高級別復雜度且發(fā)生具有時序關(guān)系時,可以將個別高級別復雜度均攤到低級別復雜度上。基本上均攤復雜度就等于低級別復雜度,也可以看作是特殊的平均時間復雜度。
為什么會有這四種復雜度呢?原因是:
同一段代碼在不同情況下時間復雜度會出現(xiàn)量級差異,為了更全面、更準確描述代碼的時間復雜度,引入這四種復雜度的概念;
但通常除非代碼是出現(xiàn)量級差別的時間復雜度,才需要區(qū)分這四種復雜度,大多數(shù)情況都不需要區(qū)分它們。
下面是給出第一個代碼例子:
#?在數(shù)組?arr?中查找目標數(shù)值?x def?find(arr,?x):for?val?in?arr:if?val?==?x:return?Truereturn?False這個例子假設數(shù)組?arr?的長度是?n?,那么它最好的情況,就是第一個數(shù)值就是需要查找的?x?,此時復雜度是?O(1)?,但最壞情況就是最后一個數(shù)值或者不存在需要查找的?x?,那么此時就遍歷一遍數(shù)組,復雜度就是?O(n)?,因此這段代碼最好和最壞情況是會出現(xiàn)量級差別的,O(1)?和?O(n)?分別是最好情況復雜度和最壞情況復雜度。
而這段代碼的平均情況時間復雜度是?O(n)?,具體分析就是首先考慮所有可能的情況以及對應出現(xiàn)的概率,可能發(fā)生的情況先分為兩種,存在和不存在需要查找的數(shù)值?x,也就是分別是 1/2 的概率,然后對于存在的情況下,又有?n?種情況,即出現(xiàn)在數(shù)組任意位置的概率都是均等的,那么它們的概率乘以存在的概率就是?1/2n?,接著再考慮每種情況需要搜索的元素個數(shù),其實就是代碼執(zhí)行的次數(shù),這個分別就是從 1 到 n,并且對于不存在的情況,也是 n ,需要遍歷一遍數(shù)組才發(fā)現(xiàn)不存在,所以平均時間復雜度的計算過程如下:加權(quán)平均值,也叫期望值,所以平均時間復雜度的全稱應該叫加權(quán)平均時間復雜度或者期望時間復雜度。
這里用大 O 表示法表示,并且去掉常量和系數(shù)后,就是 O(n)。
最后介紹下均攤時間復雜度,需要滿足以下兩個條件才使用:
1)代碼在絕大多數(shù)情況下是低級別復雜度,只有極少數(shù)情況是高級別復雜度;
2)低級別和高級別復雜度出現(xiàn)具有時序規(guī)律。均攤結(jié)果一般都等于低級別復雜度。
空間復雜度分析
和時間復雜度的定義類似,空間復雜度全稱就是漸進空間復雜度(asymptotic space complexity),表示算法的存儲空間與數(shù)據(jù)規(guī)模之間的增長關(guān)系。
簡單介紹下一個程序所需要的空間主要由以下幾個部分構(gòu)成:
指令空間:是值用來存儲經(jīng)過編譯之后的程序指令所需要的空間。
數(shù)據(jù)空間:是指用來存儲所有常量和變量值所需的空間。其主要由兩個部分構(gòu)成:
存儲常量和簡單變量所需要的空間
存儲復合變量所需要的空間。這一類空間包括數(shù)據(jù)結(jié)構(gòu)所需要的動態(tài)分配的空間
環(huán)境棧空間:用來保存函數(shù)調(diào)用返回時恢復運行所需要的信息。例如,如果函數(shù) fun1 調(diào)用了函數(shù) fun2,那么至少必須保存 fun2 結(jié)束時 fun1 將要繼續(xù)執(zhí)行的指令的地址。
本系列主要參考極客時間上的數(shù)據(jù)結(jié)構(gòu)與算法之美課程,目前這門課正在做活動,拼團只需 65 元,課程的設計非常合理,而且介紹得很詳細,有對應的代碼輔助理解,還有一個開源的 GitHub,目前已經(jīng)有1w+的star了:
https://github.com/wangzheng0822/algo
歡迎關(guān)注我的微信公眾號--算法猿的成長,或者掃描下方的二維碼,大家一起交流,學習和進步!
如果覺得不錯,在看、轉(zhuǎn)發(fā)就是對小編的一個支持!
總結(jié)
以上是生活随笔為你收集整理的数据结构算法入门--一文了解什么是复杂度的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JS 创建对象的七种方式
- 下一篇: 用计算机制作贺卡,用计算机制作贺卡的操作