演算法複雜度&&陣列排序
計算演算法的複雜度
目錄
- 時間複雜度
- 空間複雜度
時間複雜度
-
什麼叫做時間複雜度呢??
-
我們來看一個簡單的程式
int n = 10 ;
System.out.println("輸出" + n);
這段虛擬碼運行了多少次呢! 1次 ,時間時間複雜度為O(1):常數複雜度/常數階。
for (int i = 0 ;i <n; i++){
System.out.println("第幾次"+i);
}
這個for迴圈了多少次呢! n次,時間複雜度為O(n):線性時間複雜度。
再看下一個
for (int i = 0 ;i <n; i++){ for (int j = 0 ;j < n; j ++) { System.out.println("第幾次" + i+""+j); } }
這個迴圈了多少次呢!n*n次,時間複雜度為O(n^2):平方複雜度。
- 百度百科對時間複雜度的定義是:在電腦科學中,演算法的時間複雜度是一個函式,它定性描述了該演算法的執行時間。
我們再把常見的複雜度列舉出來看看。
int j = 0;
for (int i = 0 ; i < n ; i++) {
i = i * 2;
j++;
System.out.println("第幾次"+j);
}
這個迴圈了多少次呢!假設我們把次數設為x,那2^x<n,解得次數x=log(n),時間複雜度為O(log(n)):對數。
for (int i = 0 ; i < Math.pow(2,n); i++) {
System.out.println("第幾次"+i);
}
這個迴圈了多少次呢!2n次,時間複雜度為O(2n):指數複雜度。
- 舉個例子:
- 快速排序
function quickSort(arr) { /* 終止條件 */ if (arr.length <= 1) { return arr; } /* 找到陣列的中間下標 */ var mid_index = arr.length / 2; /* 找到中間值 */ var mid = arr[mid_index]; var left_arr = []; var right_arr = []; for (var i = 0; i < arr.length; i++) { // 中點沒必要和中點進行比較 if (i === mid_index) continue; if (arr[i] > mid) { right_arr.push(arr[i]); } else { left_arr.push(arr[i]); } } return quickSort(left_arr).concat([mid], quickSort(right_arr)); } var arr = [4, 2, 5, 3, 1, 7, 6]; var res = quickSort(arr) console.log(res);
時間複雜度
快速排序涉及到遞迴呼叫,所以該演算法的時間複雜度還需要從遞迴演算法的複雜度開始說起;
遞迴演算法的時間複雜度公式:T[n] = aT[n/b] + f(n) ;對於遞迴演算法的時間複雜度這裡就不展開來說了;
最優情況下時間複雜度
快速排序最優的情況就是每一次取到的元素都剛好平分整個陣列(很顯然我上面的不是);
此時的時間複雜度公式則為:T[n] = 2T[n/2] + f(n);T[n/2]為平分後的子陣列的時間複雜度,f[n] 為平分這個陣列時所花的時間;
下面來推算下,在最優的情況下快速排序時間複雜度的計算(用迭代法):
T[n] = 2T[n/2] + n ----------------第一次遞迴
令:n = n/2 = 2 { 2 T[n/4] + (n/2) } + n ----------------第二次遞迴
= 2^2 T[ n/ (2^2) ] + 2n
令:n = n/(2^2) = 2^2 { 2 T[n/ (2^3) ] + n/(2^2)} + 2n ----------------第三次遞迴
= 2^3 T[ n/ (2^3) ] + 3n
......................................................................................
令:n = n/( 2^(m-1) ) = 2^m T[1] + mn ----------------第m次遞迴(m次後結束)
當最後平分的不能再平分時,也就是說把公式一直往下跌倒,到最後得到T[1]時,說明這個公式已經迭代完了(T[1]是常量了)。
得到:T[n/ (2^m) ] = T[1] ===>> n = 2^m ====>> m = logn;
T[n] = 2^m T[1] + mn ;其中m = logn;
T[n] = 2^(logn) T[1] + nlogn = n T[1] + nlogn = n + nlogn ;其中n為元素個數
又因為當n >= 2時:nlogn >= n (也就是logn > 1),所以取後面的 nlogn;
綜上所述:快速排序最優的情況下時間複雜度為:O( nlogn )
最差情況下時間複雜度
最差的情況就是每一次取到的元素就是陣列中最小/最大的,這種情況其實就是氣泡排序了(每一次都排好一個元素的順序)
這種情況時間複雜度就好計算了,就是氣泡排序的時間複雜度:T[n] = n * (n-1) = n^2 + n;
綜上所述:快速排序最差的情況下時間複雜度為:O( n^2 )
空間複雜度
空間複雜度(Space Complexity)是對一個演算法在執行過程中臨時佔用儲存空間大小的量度,記做S(n)=O(f(n))。
簡單的講就是包括下面幾部分。
1.儲存演算法本身所佔用的儲存空間。
2.演算法的輸入輸出資料所佔用的儲存空間。
3.演算法在運算過程中臨時佔用的儲存空間這三個方面。
int a[] = new int[n];
這個例子的空間複雜度是多少呢?這個陣列開闢的空間是多少呢? O(n)。
總結
時間複雜度和空間複雜度本就是一個相互博弈的過程,一個多另一個就少,根據適當的問題,找到適當的解,這才是好辦法。
陣列的排序
- 排序,就是把一個亂序的陣列,通過我們的處理,讓他變成一個有序的陣列
- 今天我們講解兩種方式來排序一個數組 氣泡排序 和 選擇排序
氣泡排序
-
先遍歷陣列,讓挨著的兩個進行比較,如果前一個比後一個大,那麼就把兩個換個位置
-
陣列遍歷一遍以後,那麼最後一個數字就是最大的那個了
-
然後進行第二遍的遍歷,還是按照之前的規則,第二大的數字就會跑到倒數第二的位置
-
以此類推,最後就會按照順序把陣列排好了
-
我們先來準備一個亂序的陣列
var arr = [3, 1, 5, 6, 4, 9, 7, 2, 8]
- 接下來我們就會用程式碼讓陣列排序
-
先不著急迴圈,先來看數組裡面內容換個位置
// 假定我現在要讓陣列中的第 0 項和第 1 項換個位置 // 需要藉助第三個變數 var tmp = arr[0] arr[0] = arr[1] arr[1] = tmp
-
第一次遍歷陣列,把最大的放到最後面去
for (var i = 0; i < arr.length; i++) { // 判斷,如果陣列中的當前一個比後一個大,那麼兩個交換一下位置 if (arr[i] > arr[i + 1]) { var tmp = arr[i] arr[i] = arr[i + 1] arr[i + 1] = tmp } } // 遍歷完畢以後,陣列就會變成 [3, 1, 5, 6, 4, 7, 2, 8, 9]
- 第一次結束以後,陣列中的最後一個,就會是最大的那個數字
- 然後我們把上面的這段程式碼執行多次。陣列有多少項就執行多少次
-
按照陣列的長度來遍歷多少次
for (var j = 0; j < arr.length; j++) { for (var i = 0; i < arr.length; i++) { // 判斷,如果陣列中的當前一個比後一個大,那麼兩個交換一下位置 if (arr[i] > arr[i + 1]) { var tmp = arr[i] arr[i] = arr[i + 1] arr[i + 1] = tmp } } } // 結束以後,陣列就排序好了
-
給一些優化
-
想象一個問題,假設陣列長度是 9,第八次排完以後
-
後面八個數字已經按照順序排列好了,剩下的那個最小的一定是在最前面
-
那麼第九次就已經沒有意義了,因為最小的已經在最前面了,不會再有任何換位置出現了
-
那麼我們第九次遍歷就不需要了,所以我們可以減少一次
for (var j = 0; j < arr.length - 1; j++) { for (var i = 0; i < arr.length; i++) { // 判斷,如果陣列中的當前一個比後一個大,那麼兩個交換一下位置 if (arr[i] > arr[i + 1]) { var tmp = arr[i] arr[i] = arr[i + 1] arr[i + 1] = tmp } } }
-
第二個問題,第一次的時候,已經把最大的數字放在最後面了
-
那麼第二次的時候,其實倒數第二個和最後一個就不用比了
-
因為我們就是要把倒數第二大的放在倒數第二的位置,即使比較了,也不會換位置
-
第三次就要倒數第三個數字就不用再和後兩個比較了
-
以此類推,那麼其實每次遍歷的時候,就遍歷當前次數 - 1 次
for (var j = 0; j < arr.length - 1; j++) { for (var i = 0; i < arr.length - 1 - j; i++) { // 判斷,如果陣列中的當前一個比後一個大,那麼兩個交換一下位置 if (arr[i] > arr[i + 1]) { var tmp = arr[i] arr[i] = arr[i + 1] arr[i + 1] = tmp } } }
-
-
至此,一個氣泡排序就完成了
-
選擇排序
-
先假定陣列中的第 0 個就是最小的數字的索引
-
然後遍歷陣列,只要有一個數字比我小,那麼就替換之前記錄的索引
-
知道陣列遍歷結束後,就能找到最小的那個索引,然後讓最小的索引換到第 0 個的位置
-
再來第二趟遍歷,假定第 1 個是最小的數字的索引
-
在遍歷一次陣列,找到比我小的那個數字的索引
-
遍歷結束後換個位置
-
依次類推,也可以把陣列排序好
-
準備一個數組
var arr = [3, 1, 5, 6, 4, 9, 7, 2, 8]
-
假定陣列中的第 0 個是最小數字的索引
var minIndex = 0
-
遍歷陣列,判斷,只要數字比我小,那麼就替換掉原先記錄的索引
var minIndex = 0 for (var i = 0; i < arr.length; i++) { if (arr[i] < arr[minIndex]) { minIndex = i } } // 遍歷結束後找到最小的索引 // 讓第 minIndex 個和第 0 個交換 var tmp = arr[minIndex] arr[minIndex] = arr[0] arr[0] = tmp
-
按照陣列的長度重複執行上面的程式碼
for (var j = 0; j < arr.length; j++) { // 因為第一遍的時候假定第 0 個,第二遍的時候假定第 1 個 // 所以我們要假定第 j 個就行 var minIndex = j // 因為之前已經把最小的放在最前面了,後面的迴圈就不需要判斷前面的了 // 直接從 j + 1 開始 for (var i = j + 1; i < arr.length; i++) { if (arr[i] < arr[minIndex]) { minIndex = i } } // 遍歷結束後找到最小的索引 // 第一堂的時候是和第 0 個交換,第二趟的時候是和第 1 個交換 // 我們直接和第 j 個交換就行 var tmp = arr[minIndex] arr[minIndex] = arr[j] arr[j] = tmp }
-
一些優化
-
和之前一樣,倒數第二次排序完畢以後,就已經排好了,最後一次沒有必要了
for (var j = 0; j < arr.length - 1; j++) { var minIndex = j for (var i = j + 1; i < arr.length; i++) { if (arr[i] < arr[minIndex]) { minIndex = i } } var tmp = arr[minIndex] arr[minIndex] = arr[j] arr[j] = tmp }
-
在交換變數之前,可以判斷一下,如果我們遍歷後得到的索引和當前的索引一直
-
那麼就證明當前這個就是目前最小的,那麼就沒有必要交換
-
做一我們要判斷,最小作引和當前作引不一樣的時候,才交換
for (var j = 0; j < arr.length - 1; j++) { var minIndex = j for (var i = j + 1; i < arr.length; i++) { if (arr[i] < arr[minIndex]) { minIndex = i } } if (minIndex !== j) { var tmp = arr[minIndex] arr[minIndex] = arr[j] arr[j] = tmp } }
-
-
至此,選擇排序完成
-
快速排序
- 口訣:找中點,分左右
- 找中點:找到下標的中位數,分為左右陣列。再把左右作為新的陣列進行找中點分左右
- 遞迴實現
function quickSort(arr) {
/* 終止條件 */
if (arr.length <= 1) {
return arr;
}
/* 找到陣列的中間下標 */
var mid_index = arr.length / 2;
/* 找到中間值 */
var mid = arr[mid_index];
var left_arr = [];
var right_arr = [];
for (var i = 0; i < arr.length; i++) {
// 中點沒必要和中點進行比較
if (i === mid_index) continue;
if (arr[i] > mid) {
right_arr.push(arr[i]);
} else {
left_arr.push(arr[i]);
}
}
return quickSort(left_arr).concat([mid], quickSort(right_arr));
}
var arr = [4, 2, 5, 3, 1, 7, 6];
var res = quickSort(arr)
console.log(res);
越努力,越幸運