到底什麼是時間複雜度

 

咱們經常在武俠小說中看到一位內力精深的高手在學習新的招式的時候修煉速度異常驚人,我心目中最經典的片斷就是倚天屠龍記中張無忌學習乾坤大挪移和太極拳的時候了,他能在極短的時間內領會常人數十年所不能掌握的東西,即便拍了不少版本,每次看到這,我都大呼過癮,仍然看的津津有味~程序員

 

數據結構和算法對於程序員來講就像是武俠世界中江湖人士的內功心法,其重要程度不言而喻,而開啓數據結構與算法歷練之路大門的鑰匙則是複雜度的分析,這裏的複雜度主要指的就是時間複雜度。面試

 

數據結構與算法須要掌握的知識不少,後來通過大牛們的概括總結提煉出圖中 10 種數據結構與 10 種經常使用的算法,只須要掌握下面這張圖咱們平常工做中就能夠遊刃有餘了:算法

 

 

聰明的同窗會發現其實圖中知識點仍是不少的,看着少是由於沒有展開腦圖而已 ,要掌握的知識可能是好事,說明咱們進步的空間很大,學習之路可能很遠,不要緊,慢慢來,咱們來日方長~數組

 

鋪墊了這麼多,相信你們對數據結構與算法也有了一些認識,我目前也是一名小白,指望經過每次的分享可以在數據結構與算法的道路上走的更遠一些。markdown

 

下面咱們開始入門第一課 :時間複雜度的分析數據結構

 

主要包括如下 4 點:數據結構和算法

 

  • 大O複雜度表示法學習

     

  • 經常使用的時間複雜度表示ui

     

  • 最好、最壞、平均、均攤時間複雜度spa

     

  • 空間複雜度

 

在一些面試題當中常常會出現對某一算法進行時間複雜度分析,在給出的選項中會有相似 O(1),O(n),O(logn)....的寫法,那麼像這樣的O()的寫法是什麼意思呢?

 

這就要提到時間複雜度分析經常使用的 大O複雜度表示法。

 

  • 大O複雜度

 

大O複雜度實際上並不具體表明代碼真正的執行時間,而是表示代碼執行時間隨數據規模增加的變化趨勢,也叫漸進時間複雜度,簡稱爲時間複雜度

 

看下面一個例子:

 

int sum(int n) { int sum = 0; int i = 1; int j = 1; for (; i <= n; ++i) { j = 1; for (; j <= n; ++j) { sum = sum + i * j; } }}

 

對上述代碼而言,假設每一行代碼的執行時間都是相同的記爲 time(time 爲常量),第2-4 行代碼都是執行一次,各消耗時間爲 time,第五、6行代碼各執行 n 次,各消耗時間爲 n * time,第七、8 行代碼碼各執行 n*n 次,各消耗時間爲 n*n * time,因此上述代碼的總消耗時間爲:

2*n*n*time + 2*n*time+3*time = (2n²+2n+3)*time

 

用大 O 表示法則記爲 O((2n²+2n+3)* time), 因爲 time爲常量,n爲變量,即原式能夠化簡爲 O(2n²+2n+3)。

 

當n很大時,甚至能夠認爲它趨近於無窮大時,根據極限的知識咱們能夠認爲 O(2n²+2n+3) 中低階、常量、係數三部分並不左右增加趨勢,因此能夠忽略,只用記錄最高階就能夠了,即 O(2n²+2n+3) 能夠寫成 O(n²),這也就是上述代碼的時間漸進複雜度,簡稱時間複雜度

 

由上面的例子能夠看出,其實在分析時間複雜度的時候,咱們只要關心階數最高或者說是循環次數最多的一段代碼就能夠了,由於咱們通常會忽略低階、常量、係數這三部分。

 

時間複雜度經常使用到的另外2個法則是:加法法則和乘法法則,都比較簡單,這裏就不在多說了。

 

 

  • 經常使用的時間複雜度表示

 

 

O(1) , O(logn) , O(n) , O(nlogn) , O(n²),O(n³),O(2^n) ,O(n!),從左到右時間複雜度依次遞增

 

  • O(1)

     

O(1) 表示常量階,並非說只執行了一行代碼,只要代碼的執行時間不隨着 n 的增大而增大就能夠定爲常量階,哪怕有成千上萬行代碼。另外若是有循環次數是常量的循環也定義常量階。

 

  • O(logn) 、O(nlogn)

 

這種對數階時間複雜度也是比較常見的。

 

int i = 1;while(i <= n){  i = i * 2;}

 

從代碼中能夠看出 i 是成倍往上增加的,當 i 大於 n 時,循環就會結束,這實際上是一道很簡單的對數題:

 

2^x = n, 求 x 的值

 

咱們都知道 x = logn (以 2 爲底 n 的對數),那時間複雜度爲何不寫成 O(logn )呢?

 

其實寫成O(logn )也並無錯,若是咱們把上述代碼第4行 改爲 i = i * 3,那是否是要寫成 O(log3 n )了,顯然不是,這樣寫雖然不錯可是太麻煩了。

 

由咱們僅存的高中數學知識可知,對數是能夠相互轉化的 :

 

 log3n = log32 * log2n,log32爲常數

 

那麼,O(log3n) = O (log32 * log2n)= O(log2n),因此在對數時間複雜度中,就能夠忽略對數的底,統一標識爲 O(logn)

 

而 nO(logn) 就顯而易見了,根據乘法法則,在外層再套一層時間複雜度爲 O (n)的循環,上述代碼複雜度就是 nO(logn) 了。

 

歸併排序、快速排序的時間複雜度都是O(nlogn)。

 

 

  • 最好、最壞、平均、均攤時間複雜度

 

在瞭解經常使用的複雜度後,咱們在深刻一層,以前的代碼都比較簡單,不須要考慮相應的狀況,下面咱們看一個比較複雜的例子:

 

//數組中查找變量 target 的位置,有則返回下標,沒有則返回1int find(int[] array, int n, int target) { int i = 0; int pos = -1; for (; i < n; ++i) { // n表示數組array的長度    if (array[i] == target) { pos = i; break; } } return pos;}

 

上面這段代碼的時間複雜度是多少呢?

 

這個時候咱們可能會有這樣的疑問:

 

目標值在不在數組中,若是不在怎麼辦,若是在,那具體在哪一個位置呢?

 

這裏咱們若是再用以前的方法分析,結果就會有誤差了,由於代碼中的循環是有可能被中斷的(當找到目標值後 break)此時引入最好時間複雜度、最壞時間複雜度、平均時間複雜度的概念了。

 

顧名思義,最好時間複雜度就是最理想的狀況下,也就是目標值就是數組的第一個元素,此時對應的時間複雜度爲 O(1)

 

最壞時間複雜度就是最差的狀況下,也就是說目標值不在數組中(或目標值在數組的末尾),此時須要循環n次中才能知道目標值是否在數組中,對應時間複雜度爲O(n)。

 

  • 平均時間複雜度

 

最好和最壞時間複雜度其實都是極特殊的狀況,爲了更好的解釋平均時間複雜度須要引入一個概念:平均狀況時間複雜度,後面簡稱爲平均時間複雜度

 

所謂的平均狀況與求平均值相似,上述的尋找目標值在數組中的位置,一共有n+1 中狀況,包括在數組 0 ~ n-1 的任一下標上 和不在數組中的狀況,把須要查找元素的個數累加起來,再除以 n+1 ,就能夠獲得遍歷元素的平均值:

 

(1+2+3+4……+n+n)/(n+1) = n(n+3)/2(n+1)

 

將O (n(n+3)/2(n+1)) 根據上述規則轉換後,能夠得出平均時間複雜度爲 O(n)。

 

  • 加權平均時間複雜度 

 

咱們雖然得出告終果,可是仔細一想這個結果好像並不許確,緣由在於 這 n+1 種狀況出現的機率實際上是不一樣的,並且目標值出如今數組中某一位置的機率也是不一樣的。

 

爲了方便計算,咱們假定目標值出如今數組中與不在數組中的機率是相等的,都爲 1/2 ;目標值出如今數組中某一位置的機率也是相等的,都爲 1/n,根據乘法法則,咱們要找的目標值出如今數組中的機率應該爲 (1/2) * (1/n), 1/(2n)。

 

因此前面的計算最大的問題是沒有考慮機率問題,將機率添加上的算式爲:

 

((1+2+3+4……+n)*(1/2n)+ n*(1/2) ) = (3n+1)/4

 

這個值就是機率論中的加權平均值,也叫做指望值,因此平均時間複雜度的全稱應該叫加權平均時間複雜度或者指望時間複雜度。 

 

實際上通常狀況下咱們並不區分最好、最差、平均時間複雜度這三種狀況。使用最開始的一個複雜度就能夠知足需求了。

 

若是出現一塊代碼在不一樣狀況下,時間複雜度有重量級差距,纔會使用這三種複雜度來區分。

 

 

  • 均攤時間複雜度

 

均攤時間複雜度應用場景比較特殊,因此咱們並不會常常用到。

 

均攤時間複雜度的主要思想是:

 

對一個數據結構進行一組連續操做中,大部分狀況下時間複雜度都很低,只有個別狀況下時間複雜度比較高,並且這些操做之間存在先後連貫的時序關係,這個時候,咱們就能夠將這一組操做放在一起分析,看是否能將較高時間複雜度那次操做的耗時,平攤到其餘那些時間複雜度比較低的操做上。並且,在可以應用均攤時間複雜度分析的場合,通常均攤時間複雜度就等於最好狀況時間複雜度 。

 

 

 

  • 空間複雜度

 

在瞭解了時間複雜度後,最後再補充一下空間複雜度,其實空間複雜度很簡單,它表示算法的存儲空間與時間的增加關係。

 

通常重用的空間複雜度就是 O(1)、 O(n)、 O(n),像O(logn)、 O(nlogn)這樣的對數階複雜度平時都用不到。

 

 

 

經過今天的分享,咱們主要了解了數據結構與算法的重要性,與時間複雜度相關的一些知識,以後咱們會繼續學習數據結構與算法相關的知識,一塊兒修煉內功,成爲「江湖中的大俠」。

相關文章
相關標籤/搜索