快速排序
/*************當每次劃分爲 n-1 個元素和 0 個元素時,最壞情況發生*********/
//方法一
function quickSort(arr) {
if (arr.length <= 1) {
return arr
}
let mid = Math.floor(arr.length / 2)
let leftArr = [];
let rightArr = [];
let midArr = arr.splice(mid, 1);
for (let i = 0; i < arr.length; i++) {
if (arr[i] < midArr[0]) {
leftArr.push(arr[i])
} else {
rightArr.push(arr[i])
}
}
leftArr = quickSort(leftArr)
rightArr = quickSort(rightArr)
return leftArr.concat(midArr, rightArr)
}
// 方法二
var quickSort = function(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)); //鏈接左數組、基準數構成的數組、右數組
}
arr = quickSort(arr)
歸併排序
function merge(arr, p, q, r) {
let leftArr = arr.slice(p, q + 1)
let rightArr = arr.slice(q + 1, r + 1)
leftArr.push(Number.POSITIVE_INFINITY)
rightArr.push(Number.POSITIVE_INFINITY)
let i = 0;
let j = 0;
for (let k = p; k <= r; k++) {
if (leftArr[i] <= rightArr[j]) {
arr[k] = leftArr[i]
i++
} else {
arr[k] = rightArr[j]
j++
}
}
}
function mergeSort(arr, p, r) {
if (p < r) {
let q = Math.floor((p + r) / 2)
mergeSort(arr, p, q)
mergeSort(arr, q + 1, r)
merge(arr, p, q, r)
}
}
mergeSort(arr, 0, arr.length - 1)
歸併排序與快速排序都是分治策略的應用,二者的區別是:
- 快速排序生成新的數組而歸併排序是在原有數組上更改
- 二者時間複雜度不相同,快速排序最壞情況是 O(n^2)
- 二者區別的根本原因是快速排序的劃分是不穩定的,有可能劃分爲 n-1 個元素的子數組和 0 個元素的子數組
平均情況下快速排序的時間複雜度是 Θ(nlgn),最壞情況是 Θ(n2) :
當劃分產生的兩個子問題分別包含 n-1 和 0 個元素時,最壞情況發生。
劃分操作的時間複雜度爲 Θ(n),T(0)=Θ(1)
算法運行時間的遞歸式爲 T(n)=T(n−1)+T(0)+Θ(n)=T(n−1)+Θ(n)
解爲 T(n)=Θ(n2)當劃分產生的兩個子問題分別包含 ⌊n/2⌋ 和 ⌈n/2⌉−1 個元素時,最好情況發生。
算法運行時間遞歸式爲 T(n)=2T(n/2)+Θ(n)
解爲 T(n)=Θ(nlgn)事實上只要劃分是常數比例的,算法的運行時間總是O(nlgn)。 假設按照 9:1 劃分,每層代價最多爲 cn,遞歸深度爲 log9n/10=Θ(lgn),故排序的總代價爲 O(nlgn)
平均情況下,比如一次壞的劃分接着一次好的劃分,壞的劃分那一項可以合併到好的劃分裏,統計上來講平均情況下的時間複雜度仍然是Θ(nlgn)