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