快速排序
快速排序是冒泡排序的優化,與冒泡排序不同的是,使用了分治法,進行優化。會隨機選取一個值pivot(基準元素),與其它值進行比較,將小于這個值的值全部放到一邊,大于這個值的值放到另一邊。然后再對兩邊的序列區間重復進行此操作,直至成為有序隊列為止。
- 時間復雜度為:nlogn
- 空間復雜度為:logn
- 是不穩定的排序方法
快排的實現
單邊循環法
比如對于初始序列:[8, 9, 6, 3, 2, 7]
實現過程思路:
- 使用一個mark指針,用來表示小于基準元素的區域邊界,初始時,指向pivot(基準元素,一般我們可以取第1位),也就是mark指向8,[8(mark), 9, 6, 3, 2, 7]
- 從基準元素下一位x開始遍歷,如果x > pivot,也就是9 > 8,不做任何操作,,[8(mark), 9(x), 6, 3, 2, 7],繼續遍歷
- 遍歷的下一位, [8(mark), 9, 6(x), 3, 2, 7],此時6 < 8,代表著這個數要移動到pivot的左側,所以此時mark往右移一位,用來放置這個數,然后x和mark指向的數進行位置交換,也就是6和9交換,得到[8, 6(mark), 9(x), 3, 2, 7]
- 繼續[8, 6(mark), 9, 3(x), 2, 7],x = 3,x < pivot,mark向右移一位指向9,3和mark指向的數9交換,交換后得到 [8, 6, 3(mark), 9(x), 2, 7]
- 繼續,[8, 6, 3(mark), 9, 2(x), 7], x = 2, 2 < 8,mark+1指向9,9與2交換,得到[8, 6, 3, 2(mark), 9(x), 7]
- 繼續[8, 6, 3, 2(mark), 9, 7(x)],7 < 8,mark+1指向9,9與7交換,得到[8(pivot), 6, 3, 2, 7(mark), 9(x)],此時x已是遍歷的最后一位,所以這時將pivot與mark位置進行交換, [7, 6, 3, 2, 8, 9],這樣就完成了第一輪的快排,此時8左邊的數都是小于8的,右邊的數都是大于8的
- 再遞歸對8左右兩邊序列[7,6,3,2]、[9]進行1-6步驟進行快速,直至成為整個數組變成有序隊列
/*
* 快速排序
* 單邊循環法的遞歸實現
*/
function singleQuickSort(arr, startIndex, endIndex) {
// 健壯性判斷
if(!Array.isArray(arr)) throw new Error("請輸入一個數組")
if(!arr.length) return []
if(startIndex >= endIndex) return;
// 基準元素
let pivot = arr[startIndex];
// 標記指針,用于表示小于基準元素的區域邊界
let mark = startIndex;
for(let i = startIndex + 1; i <= endIndex; i++) {
if(arr[i] < pivot) {
mark++;
[ arr[mark], arr[i] ] = [ arr[i], arr[mark] ]
}
}
[ arr[startIndex], arr[mark] ] = [ arr[mark], arr[startIndex] ]
singleQuickSort(arr, startIndex, mark - 1)
singleQuickSort(arr, mark + 1, endIndex)
return arr
}
const arr = singleQuickSort([8, 9, 6, 3, 2, 7], 0, 5)
console.log(arr) // [ 2, 3, 6, 7, 8, 9 ]
雙邊循環法
雙邊循環法,顧名思義,就是用雙指針的方法來遍歷元素
實現過程:
如:[8, 2, 6, 3, 9, 7]
- 假如隨機值定基準元素pivot為第一個數8,定義一個左指針(指向第一個數),和一個右指針(指向最后一個),則8先與最后一個值進行比較,8比7大,所以兩值交換位置[7, 2, 6, 3, 9, 8]
- 此時左指針往右移一位,2與8比,2比8小,位置不變,左指針繼續往右移動一位
- 此時左指針往右移一位,6與8比,6比8小,位置不變,左指針繼續往右移動一位
- 3和8比,左指針繼續往右移動一位
- 9和8比,交換位置[7, 2, 6, 3, 8, 9],此時左指針不變,右指針向左移一位,至此左右指針相同,第一輪比較結束,注意,結束的條件是左指針位置大于等于右指針;比8小的數都在8的左邊,比8大的數都在8的右邊
- 對左邊[7, 2, 6, 3]和右邊的序列[9]分別再重復1-4步,直至都成為有序序列
/*
* 快速排序
* 雙邊循環法的遞歸實現
*/
function doubleQuickSort(arr, startIndex, endIndex) {
// 健壯性判斷
if(!Array.isArray(arr)) throw new Error("請輸入一個數組")
if(!arr.length) return []
if(startIndex >= endIndex) return arr;
// 基準元素
const pivot = arr[startIndex]
// 左指針
let left = startIndex
// 右指針
let right = endIndex
// 只要左右指針不重合,就會繼續循環
while(left != right) {
// 從最右邊開始比較,如果right元素大于基準元素,則右指針向左移一位
while(left < right && pivot <= arr[right]) {
right--;
}
// 如果右邊元素小于基準元素,則交換位置,然后左指針向右移一位
if(arr[right] < pivot) {
[ arr[left], arr[right] ] = [ arr[right], arr[left] ]
left++;
}
// 然后開始比較基準元素和左邊元素,如果左邊元素小于基準元素,左指針向右移一位
while(left < right && pivot >= arr[left]) {
left++;
}
// 如果左邊元素大于基準元素,則交換位置,然后右指針向左移一位
if(arr[left] > pivot) {
[ arr[left], arr[right] ] = [ arr[right], arr[left] ]
right--;
}
}
// 當left===right時,就是基準元素所在的最終位置,此時 arr[left] === arr[right] === pivot;
// 遞歸遍歷左邊和右邊的無序隊列
doubleQuickSort(arr, startIndex, left-1)
doubleQuickSort(arr, left + 1, endIndex)
return arr;
}
const arr2 = doubleQuickSort([8, 9, 6, 3, 2, 7], 0, 5)
console.log(arr2) // [ 2, 3, 6, 7, 8, 9 ]
排序算法系列文章傳送門(未完,持續更新中):
排序算法-1(javascript) 冒泡、選擇、插入、希爾排序的實現
排序算法-2(javascript) 快速排序的實現
排序算法-3(javascript) 堆排序的實現
排序算法-4(javascript) 歸并排序的實現