N筆資料用循序搜尋演算法的時間複雜度為何

二分搜尋法有很多種不同的條件、例子,上面的範例,只是要求在一連串數列裡面回答有沒有找到,有的話在第幾個位置,但其實原理都很類似,一樣是用二分搜尋去排除最多的數字,但是在一些條件判斷上會有些微差異,如果條件沒寫好的話,很容易會變成無窮迴圈。

O(n²) 選擇排序(Selection Sort)

效能較差的演算法。像是選擇排序,大致上來說,包了兩層 for 迴圈的都是 n² 。
選擇排序只需要重複執行兩個步驟:

1.從數列中找出最小值

2.將最小值與數列最左邊的數交換,結束排序。回到 (1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let input = [5, 4, 1, 3, 2]
function selectionSort(arr) {
let len = arr.length // 陣列長度有幾個,就要找幾輪
let minIndex
for (let i = 0 ; i < len ; i++) { // i 以前的元素都排序好了
let min = arr[i] // 預設第一個是最小的
minIndex = i
for (let j = i ; j < len ; j++) { // 找未排序的最小值
if (arr[j] < min) {
min = arr[j]
minIndex = j
}
}
[arr[minIndex], arr[i]] = [arr[i], arr[minIndex]] // 交換兩個數
}
return arr
}
selectionSort(input) // [1, 2, 3, 4, 5]

第一個回合要從 5 個數字中找到最小值,需要 5 個步驟。第二個回合則是從 4 個數字中找,需要 4 個步驟,如果總共要排序的數字有 n 個,則第一個回合需要 n 個步驟,第二回合需要 n-1 個,一直到最後一個回合需要 1 個步驟為止。
可以得知需要經過:

(n + (n - 1) + (n - 2) + … + 1)

= n * (n + 1) / 2

個步驟。

時間複雜度就是 O(n²),最好、最壞、平均都是一樣的,因為無論原本的陣列長怎樣,都要經過這麼多輪比較。 為什麼會是 O(n²) 後面會提到

O(2^n) 費波那契數列(Fibonacci numbers)

代表著執行步驟會是 2 的 n 次方。這樣的執行效率非常的慢,因此,這樣的時間複雜度是大部分工程師在設計演算法時想要避免的,最常見的例子是以遞迴計算費波那契數列。
規則:

第 0 項 = 0
第 1 項 = 1
第 n 項 = 第 n-1 項 + 第 n-2 項
ex : 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55

1
2
3
4
// 遞迴不斷呼叫自己
let fib = n => n > 1 ? fib(n - 1) + fib(n - 2) : n
fib(2) // 1
fib(10) // 55

執行時間的計算方式

該如何測量執行時間隨輸入內容而變化的程度?最實際的方式是,編程並在電腦上運作,計算真正耗費的時間。不過就算是相同的演算法,也會因為使用的電腦不同,影響執行時間,這點會造成不便。

因此,使用「步驟次數」來表示執行時間。「1個步驟」是執行的基本單位,用「運作結束之前,共執行幾次基本單位」來測量執行時間。

用上面選擇排序的例子來看執行時間,選擇排序的演算法如下:

1.從數列中找出最小值

2.將最小值與數列最左邊的數交換,結束排序。回到 (1)

假設數列中有

let color = ['red', 'yellow', 'blue']
console.log(color[2]) // blue
1 個數,確認完
let color = ['red', 'yellow', 'blue']
console.log(color[2]) // blue
1 個數後,(1)「找出最小值」的過程即結束。將「確認 1 個數」的操作視為基本單位,耗費的時間是
let color = ['red', 'yellow', 'blue']
console.log(color[2]) // blue
3。,因此,(1) 是經過
let color = ['red', 'yellow', 'blue']
console.log(color[2]) // blue
4的時間後結束。

接著,將「交換 2 個數」也視為基本單位,設定為耗費

let color = ['red', 'yellow', 'blue']
console.log(color[2]) // blue
5 的時間。如此一來,(2) 的過程並不隨
let color = ['red', 'yellow', 'blue']
console.log(color[2]) // blue
1 變化,只進行 1 次數的交換,經過
let color = ['red', 'yellow', 'blue']
console.log(color[2]) // blue
5 的時間後結束。

反覆進行 (1) 和 (2) n 次,加上每回合要確認的數會減少 1 個,總計的執行時間如下:

(n Tc + Ts)+((n - 1) x Tc + Ts)+((n - 2) x Tc + Ts) + … + (2 Tc + Ts) + (1 * Tc + Ts)

= 1/2Tcn(n + 1) + Tsn

= 1/2Tcn² + (1/2Tc + Ts)n

上面已求出執行時間,接著來看如何稍微簡化結果。

let color = ['red', 'yellow', 'blue']
console.log(color[2]) // blue
3 和
let color = ['red', 'yellow', 'blue']
console.log(color[2]) // blue
5 是基本單位,與輸入無關。會隨輸入而變動的是數列長度,假設
let color = ['red', 'yellow', 'blue']
console.log(color[2]) // blue
1 極大的情況下,當
let color = ['red', 'yellow', 'blue']
console.log(color[2]) // blue
1 越大,上列數式中的
1
2
3
4
5
6
7
8
2 就會變得非常大,其他部分相對變小。因此,最容易受影響的是
let color = ['red', 'yellow', 'blue']
console.log(color[2]) // blue
1 ,所以可刪除其他部分,將式子變成:

1/2Tcn² + (1/2Tc + Ts)n = O(n²)

可以得知,選擇排序的執行時間,大致會依輸入的數列長度

let color = ['red', 'yellow', 'blue']
console.log(color[2]) // blue
1 的平方成比例變化,同樣地,比方說某個演算法的運作量分別如下:

5Tx * n³ + 20Tyn² + 3Tzn

此時,用 O(n³) 表示。

5n log n + 2Tyn

此時,用 O(n log n) 表示。

O 這個符號表示「忽略重要項目之外的部分」的意思。 O(n²) 是表示「執行時間在最糟的情況時,能控制在 n² 的常數倍以內」。透過這種表示方式,可以直覺地理解演算法的執行時間,舉例來說,當選擇排序的執行時間是 O(n²) ,快速排序的執行時間是 O(n log n)馬上就能知道快速排序的執行時間較短。此外,隨著輸入的 n 的大小,執行時間會有多大的變化也能一目瞭然。

最佳情形下,二元搜尋樹搜尋的時間複雜度為何?

自平衡二元搜尋樹可以克服上述缺點,其時間複雜度為O(nlog n)。 一方面,排序的問題使得CPU Cache效能較差,特別是當節點是動態記憶體分配時。 而堆積排序的CPU Cache效能較好。 另一方面,排序是最佳的增量排序(incremental sorting)演算法,保持一個數值序列的有序性。

搜尋演算法有哪些?

Search 搜尋,這也是演算法之中基礎的基礎,主要想要解決的問題是,在一個已排序或是未排序的序列中,找到目標的元素。.
Sequential Search 循序搜尋.
Binary Search 二元搜尋.
Exponential Search 指數搜尋.

怎麼看時間複雜度?

時間複雜度可以用函式T(n) 的自然特性加以分類,舉例來說,有著T(n) = O(n) 的演算法被稱作「線性時間演算法」;而T(n) = O(Mn) 和Mn= O(T(n)) ,其中M ≥ n > 1 的演算法被稱作「指數時間演算法」。

二分搜尋適合哪一種資料結構?

是對數)。 二分搜尋演算法使用常數空間,對於任何大小的輸入資料,演算法使用的空間都是一樣的。 除非輸入資料數量很少,否則二分搜尋演算法比線性搜尋更快,但陣列必須事先被排序。 儘管一些特定的、為了快速搜尋而設計的資料結構更有效(比如雜湊表),二分搜尋演算法應用面更廣。