通常狀況下,算法中基本操做重複執行的次數是問題規模n的某個函數,用T(n)表示,如有某個輔助函數f(n),使得當n趨近於無窮大時,T(n)/f(n)的極限值爲不等於零的常數,則稱f(n)是T(n)的同數量級函數。記做T(n)=O(f(n)),稱O(f(n))爲算法的漸進時間複雜度(O是數量級的符號 ),簡稱時間複雜度。git
當問題規模隨着處理數據的增加時,基本操做要重複執行的次數一定會隨着增加,那麼咱們須要知道執行次數是以什麼樣的數量級增加的。github
接下來咱們用大O表示法標識一下常見的時間複雜度量級:算法
常數階的複雜度,這種複雜度不管數據規模n如何增加,計算時間是不變的。數組
一個簡單的例子:函數
const add = (a, b) => a + b
複製代碼
無論n如何增加,都不會影響到這個函數的執行時間,所以這個函數的時間複雜度爲O(1)。ui
線性複雜度,隨着數據規模n的增加,時間複雜度也會隨着n線性增加spa
const indexOf = (arr, target) => {
let len = arr.length
while(len--) {
if (arr[len] === target) {
return arr[len]
}
}
}
複製代碼
對數複雜度,隨着問題規模n的增加,計算時間也會隨着n對數級數增加。 如數據增大1024倍時,時間只增大32倍,是比線性複雜度還要低的時間複雜度。code
典型的例子就是二分查找法。(二分查找只支持已經排好序的數組)排序
functions binarySearch(arr, target) {
let max = arr.length - 1
let min = 0
while (min <= max) {
let mid = Math.floor((max + min) / 2)
if (target < arr[mid]) {
max = mid - 1
} else if (target > arr[mid]) {
min = mid + 1
} else {
return mid
}
}
return -1
}
複製代碼
在二分查找法的代碼中,經過while循環,成 2 倍數的縮減搜索範圍,也就是說須要通過 log2^n 次便可跳出循環。遞歸
事實上在實際項目中,O(logn)是一個很是好的時間複雜度,好比當n=100的數據規模時,二分查找只須要7次,線性查找須要100次,這對於計算機而言差距不大,可是當有10億的數據規模的時候,二分查找依然只須要30次,而線性查找須要驚人的10億次,O(logn)時間複雜度的算法隨着數據規模的增大,它的優點就越明顯。
平方級複雜度,典型狀況是當存在雙重循環的時候,即把 O(n) 的代碼再嵌套循環一遍,它的時間複雜度就是 O(n²) 了,表明應用是冒泡排序算法。
冒泡排序
實現原理: 以最終目標爲升序排列爲例:
// 冒泡排序
function bubbleSort(arr) {
for (var i = 0; i < arr.length - 1; i++) {
for (var j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
var temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
}
}
}
return arr
}
複製代碼
冒泡排序加強版
爲了提高效率,增長最小值,和最大值,而且正反分別對比一次。減小執行的對比次數,從而達到提升效率的目的。
function bubbleSortEnhancement(arr) {
let low = 0
let high = arr.length - 1
let i
let temp
while (low < high) {
for (i = low; i < high; i++) {
if (arr[i] > arr[i + 1]) {
temp = arr[i]
arr[i] = arr[i + 1]
arr[i + 1] = temp
}
}
high--
for (i = high; i > low; i--) {
if (arr[i] < arr[i - 1]) {
temp = arr[i]
arr[i] = arr[i - 1]
arr[i - 1] = temp
}
}
low++
}
return arr
}
複製代碼
當數據增大n倍時,執行時間隨着增大nlogn倍,這個複雜度高於線性複雜度,低於平方複雜度。歸併排序和快速排序就是典型的表明。
快速排序
實現原理:
// 快速排序
function quickSort(arr) {
if (arr.length <= 1) {
return arr
}
var pivotIndex = Math.floor(arr.length / 2)
var pivot = arr.splice(pivotIndex, 1)[0]
var left = []
var right = []
for (var i = 0; i < arr.length; i++) {
if (arr[i] <= pivot) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
return quickSort(left).concat([pivot], quickSort(right))
}
複製代碼
歸併排序
實現原理:
function mergeSort(arr) {
//採用自上而下的遞歸方法
var len = arr.length
if (len < 2) {
return arr
}
var middle = Math.floor(len / 2),
left = arr.slice(0, middle),
right = arr.slice(middle)
return merge(mergeSort(left), mergeSort(right))
}
function merge(left, right) {
var result = []
console.time('歸併排序耗時')
while (left.length && right.length) {
if (left[0] <= right[0]) {
result.push(left.shift())
} else {
result.push(right.shift())
}
}
while (left.length) result.push(left.shift())
while (right.length) result.push(right.shift())
console.timeEnd('歸併排序耗時')
return result
}
複製代碼