通常狀況下,算法中基本操做重複執行的次數是問題規模n的某個函數,用T(n)表示,如有某個輔助函數f(n),使得當n趨近於無窮大時,T(n)/f(n)的極限值爲不等於零的常數,則稱f(n)是T(n)的同數量級函數。記做T(n)=O(f(n)),稱O(f(n))爲算法的漸進時間複雜度(O是數量級的符號),簡稱時間複雜度。算法
若是lim(T(n)/f(n))的值爲不等於0的常數,則稱f(n)是T(n)的同數量級函數。記做T(n)=O(f(n))。數組
這篇文章討論兩種不一樣時間複雜度的實現方法函數
function findDifferentElements1(array1, array2) {
// 先對兩個數組進行去重操做。基本操做次數2n(最糟糕的狀況)。
const arrayA = Array.from(new Set(array1))
const arrayB = Array.from(new Set(array2))
// 定義一個數組A,B內必定不存在的變量 SAME_ELE 。基本操做次數1
const SAME_ELE = Symbol('$$__SAME__ELE__TAG')
// 提早取出數組長度常數。基本操做次數2
const lengthA = arrayA.length
const lengthB = arrayB.length
// 用兩層 for 循環嵌套檢查出每個相同的元素。基本操做次數 3n^2 + 4n + 2
for(let i = 0; i < lengthA; i++ ){ // n次i比較 n次i++ 初始化i記一次 一共操做2n+1次
for(let j = 0; j < lengthB; j++) { // n次j比較 n次j++ 初始化j記一次 一共操做2n+1次
if(arrayA[i] === arrayB[j]){ // 基本操做次數1(外層循環n*n次)
// 而後用 splice 函數將相同的元素替換成SAME_ELE。
arrayA.splice(i,1,SAME_ELE) // 基本操做次數1(外層循環n*n次)
arrayB.splice(j,1,SAME_ELE) // 基本操做次數1(外層循環n*n次)
//break (實際上能夠用break優化,這裏不討論break優化的狀況)
}
}
}
// 合併兩個數組而後將數組內全部的 SAME_ELE 去掉,留下的就是不一樣的元素了。基本操做次數2n+1
return Array.prototype.concat(arrayA,arrayB).filter(item=>item!==SAME_ELE)
}
複製代碼
這個函數前面定義了5個變量基本操做數爲2n+3。性能
接下來是兩個for循環嵌套,假設兩個數組的長度都是n,那麼第一個for循環須要運行n次,第二個for循環在最壞的狀況下也要運行n次,也就是說被這兩個for循環包裹起來的基本操做將會被重複運行n*n次,被包裹起來的代碼一共有3條語句,加上for循環自己有4n次操做因此一共有3*n*n+4n次基本操做。接下來的return語句是一個複合語句concat基本操做記爲n,filter的基本操做記爲n,那麼整個函數的基本操做函數測試
T(n) = 4n^2+8n+6 .優化
令 f(n) = T(n) 的數量級。ui
lim(T(n)/f(n)) = k {k|k≠0,k∈常數}spa
f(n) = n^2 (口訣是去掉常數項和低次冪項以及去掉最高次冪項的係數)prototype
T(n) = O(n^2)code
function findDifferentElements2(array1, array2) {
// 定義一個空數res組做爲返回值的容器,基本操做次數1。
const res = []
// 定義一個對象用於裝數組一的元素,基本操做次數1。
const objectA = {}
// 使用對象的 hash table 存儲元素,而且去重。基本操做次數2n。
for(const ele of array1) { // 取出n個元素n次
objectA[ele] = undefined // 存入n個元素n次
}
// 定義一個對象用於裝數組二的元素,基本操做次數1。
const objectB = {}
// 使用對象的 hash table 存儲元素,而且去重。基本操做次數2n。
for(const ele of array2){ // 取出n個元素n次
objectB[ele] = undefined // 存入n個元素n次
}
// 使用對象的 hash table 刪除相同元素。基本操做次數4n。
for(const key in objectA){ //取出n個key (n次操做)
if(key in objectB){ // 基本操做1次 (外層循環n次)
delete objectB[key] // 基本操做1次 (外層循環n次)
delete objectA[key] // 基本操做1次 (外層循環n次)(總共是3n 加上n次取key的操做 一共是4n)
}
}
// 將第一個對象剩下來的key push到res容器中,基本操做次數是3n次(最糟糕的狀況)。
for(const key in objectA){ // 取出n個元素n次(最糟糕的狀況)。
res[res.length] = key // 讀取n次length n次,存入n個元素n次,一共2n(最糟糕的狀況)。
}
// 將第二個對象剩下來的key push到res容器中,基本操做次數也是3n次(最糟糕的狀況)。
for(const key in objectB){ // 取出n個元素n次(最糟糕的狀況)。
res[res.length] = key // 讀取n次length n次,存入n個元素n次,一共2n(最糟糕的狀況)。
}
// 返回結果,基本操做次數1。
return res
}
複製代碼
改進後的函數基本操做次數算起來很是簡單,註釋寫的很清楚了,因此
T(n) = 14n+7 。
令 f(n) = T(n) 的數量級。
lim(T(n)/f(n)) = k {k|k≠0,k∈常數}
f(n) = n (去掉常數項和低次冪項以及去掉最高次冪項的係數)
T(n) = O(n)
當n趨於無窮大的時候
算法1應該比算法2的性能慢 O(n^2)/O(n) 倍
=> O(n) 慢 n 倍
// 定義兩個數組
const array1 = []
const array2 = []
// 數據量爲100
const n = 100
// 初始化
for(let i = 0; i < n; i++){
array1.push(i)
array2.push(n - i) // 這兩個數組中相同的元素距離相距都比較遠,算是比較接近糟糕的狀況的但也不是時最糟糕的狀況。
}
// 開始測試第一個方法耗時
console.time()
const res1 = findDifferentElements1(array1,array2)
console.timeEnd()
console.log(res1)
// 開始測試第二個方法耗時
console.time()
const res2 = findDifferentElements2(array1,array2)
console.timeEnd()
console.log(res2)
複製代碼