JavaScript 算法之最好、最壞時間複雜度分析

上一篇--JavaScript 算法之複雜度分析文章中介紹了複雜度的分析,相信小夥伴們對常見代碼的時間或者空間複雜度確定能分析出來了。算法

思考測試

話很少說,出個題目考考你們,分析下面代碼的時間複雜度(ps: 雖說並不會這麼寫)數組

function find(n, x, arr) {
        let ind = -1;
        for (let i = 0; i < n; i++) {
          if (arr[i] === x) ind = i;
        }
        return ind;
      }
複製代碼

上面函數的功能就是查找一個變量 x 是否在 數組 arr 中,若是在的話,返回所在的位置,不然就返回 -1。經過上一節的學習分析,這個函數的時間複雜度就很容易知道了,爲 O(n)。bash

接下來,稍微優化下這個 find 函數,若是查找到目標的話,就不必再日後查找了。數據結構

請分析下優化後函數的時間複雜度:函數

function find(n, x, arr) {
        let ind = -1;
        for (let i = 0; i < n; i++) {
          if (arr[i] === x) {
              ind = i;
              break;
          }
        }
        return ind;
      }
複製代碼

如今代碼的時間複雜度還爲 O(n)嗎?不肯定,利用上一章的分析法就沒法解決了。post

不一樣狀況

由於要查找的變量 x 可能會出如今數組的任意位置。若是變量 x 剛好是數組中第一個元素,那麼函數就會 break ,後續就不會繼續遍歷了,那時間複雜度就是 O(1)。但若是剛好是數組中第末個元素,或者數組中不存在變量 x 的話,那就須要把整個數組都遍歷一遍,時間複雜度就成了 O(n)。因此,不一樣的狀況下,這個函數的時間複雜度是不同的。學習

那爲了表示代碼在不一樣狀況下的不一樣時間複雜度,須要瞭解如下三個概念:最好狀況時間複雜度、最壞狀況時間複雜度和平均狀況時間複雜度**。
複製代碼

理想狀況

最好狀況時間複雜度:在最理想的狀況下,執行這段代碼的時間複雜度。好比說剛纔那段函數,在最理想的狀況下,要查找的變量 x 正好是數組的第一個元素,這種狀況下對應的時間複雜度就是最好狀況時間複雜度測試

糟糕狀況

最壞狀況時間複雜度:在最糟糕的狀況下,執行這段代碼的時間複雜度。好比說剛纔那段函數,要查找的變量 x 正好是數組的第末個元素或者不在數組中存在 ,查找函數就會把數組都遍歷一遍,這種狀況下對應的時間複雜度就是最壞狀況時間複雜度優化

平均狀況

可是,最好狀況時間複雜度最壞狀況時間複雜度對應的都是極端狀況下的代碼複雜度,發生的機率其實並不大。ui

爲了更好地表示平均狀況下的複雜度,引入另外一個概念:平均狀況時間複雜度,簡稱爲平均時間複雜度。

那如何分析平均時間複雜度呢,仍是拿剛纔那段查找函數來講:

要查找的變量 x 在數組中的位置,有 n+1 種狀況:在數組的 0~n-1 位置中和不在數組中。而後把每種狀況下,查找須要遍歷的元素個數累加起來,而後再除以 n+1,就能夠獲得須要遍歷的元素個數的平均值。

根據上章所說,時間複雜度的大 O 標記法中,能夠省略掉係數、低階、常量,因此,把這個公式簡化以後,獲得的平均時間複雜度就是 O(n)。

可是上面計算的過程當中,沒有考慮到機率的問題,由於出如今每一個位置的機率是不同的,因此得從新計算,以下分析:

要查找的變量 x,要麼在數組裏,要麼就不在數組裏。簡單標記這兩種狀況下的機率都爲 1/2。另外,要查找的數據出如今 0~n-1 這 n 個位置的機率也是同樣的,爲 1/n。因此,根據機率乘法法則,要查找的數據出如今 0~n-1 中任意位置的機率就是 1/(2n)。那咱們把每種狀況發生的機率都考慮進去,計算表達式就變成了:

最後的結果也叫作機率中的加權平均值,那最後此段函數的平均時間複雜度就爲 O(n)。

這麼看,平均時間複雜度是否是好麻煩,還須要機率計算。實際上,在大多數狀況下,咱們並不須要區分最好、最壞、平均狀況時間複雜度三種狀況。不少時候,咱們使用一個複雜度就能夠知足需求了。只有同一塊代碼在不一樣的狀況下,時間複雜度有量級的差距,咱們纔會使用這三種複雜度表示法來區分。

均攤狀況

接下來再看一個概念,特殊的平均時間複雜度:均攤時間複雜度。

先來看一個特殊的函數,分析下它的時間複雜度:

{
      var arr = new Array(n); // n 表明任意數字
      var ind = 0;
      function add(num) {
        if (ind === arr.length) {
          var sum = 0;
          for (var i = 0; i < arr.length; i++) {
            sum += arr[i];
          }
          arr[0] = sum;
          ind = 1;
        }
        arr[ind] = num;
        ind++;
      }
    }
複製代碼

add 函數就是實現一個往數組中添加數據的功能。先定義一個任意長度的空數組,而後給數組添加數據。當達到數組長度後,也就是ind === array.length時,用 for 循環遍歷數組求和,將求和以後的 sum 值放到數組的第一個位置,而後再將新的數據插入。但若是數組一開始就有空的話,則直接將數據添加到數組中。

來分析下此函數的時間複雜度:

最理想的狀況下,數組中有剩餘位置,咱們只須要將數據添加到數組下標爲 ind 的位置就能夠了,因此最好狀況時間複雜度爲 O(1)。 最糟糕的狀況下,數組中沒有剩餘位置,咱們須要先作一次數組的遍歷求和,而後再添加數據,因此最壞狀況時間複雜度爲 O(n)。

接下來分析須要計算的 平均時間複雜度

因爲數組的長度是 n,根據數據添加的位置的不一樣,能夠分爲 n 種狀況,每種狀況的時間複雜度是 O(1)。除此以外,還有一種特殊的狀況,就是在數組沒有空閒空間時添加一個數據,這個時候的時間複雜度是 O(n)。並且,這 n+1 種狀況發生的機率同樣,都是 1/(n+1)。

因此根據大 O 表示法,平均時間複雜度就爲 O(1)。

其實 add 函數的平均複雜度不須要這麼複雜,接下來咱們看看 find 函數和add函數的區別:

  • find 函數在極端狀況下,時間複雜度才爲 O(1)。但 add 函數在大部分狀況下,時間複雜度都爲 O(1)。只有個別狀況下,時間複雜度才比較高,爲 O(n)。
  • 對於 add 函數來講,O(1) 時間複雜度的添加和 O(n) 時間複雜度的添加,出現的頻率是很是有規律的,並且有必定的先後順序,通常都是一個 O(n) 添加以後,緊跟着 n-1 個 O(1) 的添加操做,循環往復。

因此,針對這樣一種特殊場景的複雜度分析,咱們並不須要像以前講平均複雜度分析方法那樣,找出全部的輸入狀況及相應的發生機率,而後再計算加權平均值。

針對這種特殊的狀況,咱們引入了一種更加簡單的分析方法:攤還分析法。經過攤還分析獲得的時間複雜度,叫 均攤時間複雜度

那如何使用攤還分析法來分析算法的均攤時間複雜度呢?

仍是看 add 函數。每一次 O(n) 的添加操做,都會跟着 n-1 次 O(1) 的添加操做,因此把耗時多的那次操做均攤到接下來的 n-1 次耗時少的操做上,均攤下來,這一組連續的操做的均攤時間複雜度就是 O(1)。這就是均攤分析的大體方法。

通常狀況總結爲:

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

生活舉例

看高人如何把複雜度利用到生活中:

今天你準備去老王家拜訪下,惋惜老王的愛人叫他去打個醬油,她告訴你說她限時 n 分鐘給他去買。
那麼你想着以他家到樓下小賣部來回最多一分鐘,那麼 「最好的狀況」就是你只用等他一分鐘。
那麼也有可能遇到突發狀況,好比說電梯沒電了,或者路上摔了一跤,天知道他去幹了什麼,用了 n 分鐘。沒辦法,老婆有令,n 分鐘限時,那這就是「最壞的狀況」。
那「平均時間複雜度」 就是他有多是第 1,2,3,...,n 中的某個分鐘回來,那平均就是 1+2+3+...n/n,把 全部可能出現的狀況的時間複雜度 相加除以狀況數 。
「均攤時間複雜度」的話就是把花時間多的分給花時間少的,獲得一箇中間值。假如 n 是 10 分鐘,那麼 9 分鐘分 4 分鐘到 1 分鐘那,8 分 3 給 2...,那均攤下來就是 5 分鐘。

總結

4 個概念

  • 最好狀況時間複雜度:代碼在最理想狀況下執行的時間複雜度。
  • 最壞狀況時間複雜度:代碼在最糟糕狀況下執行的時間複雜度。
  • 平均狀況時間複雜度:用代碼在全部狀況下執行的次數的加權平均值表示。
  • 均攤時間複雜度:在代碼執行的全部複雜度狀況中絕大部分是最好狀況時間複雜度,個別狀況是最壞狀況時間複雜度且發生具備時序關係時,能夠將個別最壞狀況時間複雜度均攤到最好狀況時間複雜度上。基本上均攤結果就等於最好狀況時間複雜度。

引入目的

  • 同一段代碼在不一樣狀況下時間複雜度會出現量級差別,爲了更全面,更準確的描述代碼的時間複雜度,因此引入這4個概念。
  • 代碼複雜度在不一樣狀況下出現量級差異時才須要區別這四種複雜度。大多數狀況下,是不須要區別分析它們的。

重點

若是有錯誤或者錯別字,還請給我留言指出,謝謝。

咱們下期見。

相關文章
相關標籤/搜索