不是科班生的我,第一次看見時間複雜度之類的名詞表示很懵逼,因此就找了網上的資料補習了下:html
時間複雜度:是指執行算法所須要的計算工做量git
空間複雜度:是指算法在計算機內執行時所需存儲空間的度量github
排序算法穩定性: 假定在待排序的記錄序列中,存在多個具備相同的關鍵字的記錄,若通過排序,這些記錄的相對次序保持不變,即在原序列中,ri=rj,且ri在rj以前,而在排序後的序列中,ri仍在rj以前,則稱這種排序算法是穩定的;不然稱爲不穩定的。算法
這裏不詳細說 shell
參考:算法的時間複雜度和空間複雜度-總結、理解排序算法的穩定性、算法和算法的複雜度segmentfault
名詞解釋: 數組
n:數據規模函數
k:「桶」的個數ui
In-place:佔用常數內存,不佔用額外內存spa
Out-place:佔用額外內存
下面的算法實現升序
顧名思義,從第一個開始比較相鄰的兩個,小的數網上冒。
function bubleSort (arr) { var len = arr.length; var temp; for (var i=0; i<len-1; i++) { for(var j=0; j<len-1-i; j++) { //前一個大於後一個則交換位置 if (arr[j] > arr[j+1]) { temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } return arr }
假設第一個最小, 日後尋找比它小的,記下其index,一輪結束後將index上的數與開始假定的數交換位置。
function selectionSort (arr) { var len = arr.length; var minIndex, temp; for (var i=0; i<len-1; i++) { minIndex = i; for (var j=i+1; j<len; j++) { if (arr[minIndex] > arr[j]) { minIndex = j } } temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp; } return arr; }
打撲克的同志應該比較好理解。假設第一個元素是已經排序好的,將後一個元素提取出來,往前依次比較,比本身大的數日後挪,插入到第一次碰見比本身小的元素後面,組成一個新的序列。
function insertionSort (arr) { var len = arr.length; var current, preIndex; for (var i=1; i<len; i++) { preIndex = i - 1; current = arr[i]; while (preIndex>=0 && current < arr[preIndex]) { arr[preIndex+1] = arr[preIndex]; preIndex--; } arr[preIndex+1] = current; } return arr }
實質爲分組插入排序。爲了方便理解,借用網上某哥的圖,參考連接在下文。
由於是在已經分組排序過的基礎上進行插入排序,因此效率高。
//由於核心是插入排序,因此咱們改造直接插入排序 function directInsertionSort(arr, gap) { var len = arr.length; var current, preIndex; for (var i=gap; i<len; i++) { current = arr[gap]; preIndex = i - gap; while (preIndex>=0 && arr[preIndex] > current) { arr[preIndex+gap] = arr[preIndex]; preIndex -= gap; } arr[preIndex+gap] = current; } return arr; } //編寫希爾排序函數 function shellSort (arr) { var len = arr.length; var gap = 1; //設置gap(希爾增量),這裏咱們給出比較經常使用的h值爲h = 3 * h + 1 while (gap < len/3) { gap = gap * 3 + 1; } for (gap; gap>0; gap = Math.floor(gap/3)) { directInsertSort(arr, gap); } return arr; }
碰見的問題,關於參數的傳遞:函數參數的傳遞能夠分爲按值傳遞和引用傳遞。
步長序列能夠看一下wiki
相似直接插入,後一個元素(拿來比較的元素)與已排序的中間值m = (i-1) >> 1
(位移運算,至關於Math.floor((i-1)/2)
)進行比較,若是i上的值大於m上的值,則與高半區折半比較,最終將比i上值高的區域日後移,將i上的值插入。如
arr = [2, 6, 7, 6, 8] //前三個是已經排好的。 //range = [low, high] = [0, 2], i = 3, current = arr[i] // m = 1, arr[i] >= arr[m], rang = [2, 2] // m = 2, arr[i] < arr[m] // 變換位置 ==> arr = [2, 6, 6, 7, 8] ... ...
function binaryInsertionSort (arr) { var len = arr.length; var low, height, current, m; for (var i=1; i<len; i++) { current = arr[i]; low = 0; height = i-1; while (low <= height) { m = (low + height) >> 1; if (current >= arr[m]) {// = 是爲了保證穩定性 low = m + 1; }else { height = m - 1; } } for (var j=i; j>low; j--) { arr[j] = arr[j-1]; } arr[low] = current; } return arr; }
採起分而治之的思想。遞歸分組、比較產生兩個已排序序列,再依次比較兩組開頭元素,較小的元素放入申請的新數組中。
歸併函數能夠經過遞歸、迭代實現。
主要作的兩件事就是分解、合併(下面並非按照執行順序,只是思路):
[3, 5, 6, 2, 9] -------------------------------------- 分: [3, 5] [6, 2, 9] [3] [5] [6] [2, 9] [2] [9] -------------------------------------- 合: [2, 9] [3, 5] [2, 6, 9] [2, 3, 5, 6, 9]
function merge (left, right) { var result = []; while (left.length && right.length) { var item = left[0] <= right[0] ? left.shift() : right.shift(); result.push(item); } return result.concat(left.length ? left : right); } function mergeSort (arr) { var len = arr.length; if (len === 1) { return arr; } var m = len >> 1; var left = arr.slice(0, m); var right = arr.slice(m); return merge(mergeSort(left), mergeSort(right)) }
遞歸可能會形成堆棧溢出的問題。
主要作的兩件事就是分解、合併(下面並非按照執行順序,只是思路):
[3, 5, 6, 2, 9] -------------------------------------- 分: [3] [5] [6] [2] [9] -------------------------------------- 合: [3, 5] [2, 6] [9] [2, 3, 5, 6] [9] [2, 3, 5, 6, 9]
function merge (left, right) { var result = []; while (left.length && right.length) { var item = left[0] <= right[0] ? left.shift() : right.shift(); result.push(item); } return result.concat(left.length ? left : right); } function mergeSort (arr) { var len = arr.length; var result = []; //分組,每組有i個元素 for (var i=1; i<=len; i*=2) { //比較相鄰兩組,有一組剩餘就退出 for (var j=0; j+i<len; j+=2*i) { left = arr.slice(j, j+i); right = arr.slice(j+i, j+2*i); result = merge(left, right); arr.splice(j, 2*i, ...result) } } return arr }
快速排序是一種分而治之思想在排序算法上的典型應用。本質上來看,快速排序應該算是在冒泡排序基礎上的遞歸分治法。
步驟:選擇一個元素做爲基準,下面選擇第一個,依次比較後面的元素,將小於基準的元素放在基準的左邊,剩餘放右邊。造成左右兩個分區,再遞歸按以前的步驟排序。
function swap (arr, i, j) { var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } function partition (arr, left, right) { var pivot = left; var index = left + 1; for (var i=index; i<=right; i++) { if (arr[i] < arr[pivot]) { swap(arr, index, i); index++; } } swap(arr, index-1, pivot); return index-1 } function quickSort (arr, left, right) { var len = arr.length; var partitionIndex; left = typeof left === 'number' ? left : 0; right = typeof right === 'number' ? right : len-1; if (left < right) { partitionIndex = partition (arr, left, right); quickSort(arr, left, partitionIndex-1); quickSort(arr, partitionIndex+1, right); } return arr; }
快速排序排序效率很是高. 雖然它運行最糟糕時將達到O(n²)的時間複雜度, 但一般, 平均來看, 它的時間複雜爲O(nlogn), 比一樣爲O(nlogn)時間複雜度的歸併排序還要快. 快速排序彷佛更偏心亂序的數列, 越是亂序的數列, 它相比其餘排序而言, 相對效率更高.
Chrome的v8引擎爲了高效排序, 在排序數據超過了10條時, 便會採用快速排序. 對於10條及如下的數據採用的即是插入排序.