文章目录
-
- [1. 快速排序简介](#1. 快速排序简介)
-
- [1.1 快速排序定义](#1.1 快速排序定义)
- [1.2 快速排序特点](#1.2 快速排序特点)
- [2. 快速排序步骤过程拆解](#2. 快速排序步骤过程拆解)
-
- [2.1 选择基准元素](#2.1 选择基准元素)
- [2.2 划分数组](#2.2 划分数组)
- [2.3 递归排序](#2.3 递归排序)
- [3. 快速排序的优化](#3. 快速排序的优化)
-
- [3.1 三数取中法选择基准](#3.1 三数取中法选择基准)
- [3.2 插入排序与快速排序结合](#3.2 插入排序与快速排序结合)
- 案例代码和动态图
- [4. 快速排序的优点](#4. 快速排序的优点)
- [5. 快速排序的缺点](#5. 快速排序的缺点)
- 总结
【 已更新完 TypeScript 设计模式 专栏,感兴趣可以关注一下,一起学习交流🔥🔥🔥 】
1. 快速排序简介
1.1 快速排序定义
快速排序是一种高效的、使用分治策略的排序算法。它的核心思想是"选择基准,分而治之"。想象一下,你是一位园丁,需要整理一大片杂乱的花园。你采用这样的策略:首先随机选择一株花作为参考,然后将其他花分成两组,一组是比这株花"矮"的,另一组是比这株花"高"的。你对这两组花分别重复这个过程,直到每组只剩下一株花。将所有的花按高度排列,就得到了一个整齐有序的花园。这就是快速排序的基本思想。
用TypeScript代码表示一个简单的快速排序:
typescript
function quickSort(arr: number[]): number[] {
if (arr.length <= 1) return arr;
const pivot = arr[Math.floor(arr.length / 2)];
const left = arr.filter(x => x < pivot);
const middle = arr.filter(x => x === pivot);
const right = arr.filter(x => x > pivot);
return [...quickSort(left), ...middle, ...quickSort(right)];
}
1.2 快速排序特点
- 分治思想:快速排序采用分治策略,将复杂问题分解为简单子问题
- 原地排序:快速排序可以在原数组上进行,不需要额外的存储空间
- 时间复杂度:平均情况下为O(nlogn),最坏情况下为O(n^2)
- 不稳定性:快速排序是不稳定的排序算法
2. 快速排序步骤过程拆解
2.1 选择基准元素
typescript
const pivot = arr[Math.floor(arr.length / 2)];
这就像园丁从花园中间随机选择一株花作为参考,这株花将成为我们划分其他花的标准。
2.2 划分数组
typescript
const left = arr.filter(x => x < pivot);
const middle = arr.filter(x => x === pivot);
const right = arr.filter(x => x > pivot);
这个步骤就像园丁将其他花分成三组:比参考花"矮"的、与参考花一样高的、比参考花"高"的。在实际的快速排序实现中,我们通常会使用双指针法在原地完成这个过程,就像园丁在花园里来回走动,将花朵移到合适的位置。
2.3 递归排序
typescript
return [...quickSort(left), ...middle, ...quickSort(right)];
这个步骤就像园丁对"矮"组和"高"组的花朵重复之前的过程,直到所有的花都被正确地排列在花园里。
3. 快速排序的优化
3.1 三数取中法选择基准
typescript
function medianOfThree(arr: number[], left: number, right: number): number {
const mid = Math.floor((left + right) / 2);
if (arr[left] > arr[mid]) [arr[left], arr[mid]] = [arr[mid], arr[left]];
if (arr[left] > arr[right]) [arr[left], arr[right]] = [arr[right], arr[left]];
if (arr[mid] > arr[right]) [arr[mid], arr[right]] = [arr[right], arr[mid]];
return mid;
}
function quickSortOptimized(arr: number[], left: number = 0, right: number = arr.length - 1): number[] {
if (left < right) {
const pivotIndex = medianOfThree(arr, left, right);
const pivotNewIndex = partition(arr, left, right, pivotIndex);
quickSortOptimized(arr, left, pivotNewIndex - 1);
quickSortOptimized(arr, pivotNewIndex + 1, right);
}
return arr;
}
function partition(arr: number[], left: number, right: number, pivotIndex: number): number {
const pivot = arr[pivotIndex];
[arr[pivotIndex], arr[right]] = [arr[right], arr[pivotIndex]];
let storeIndex = left;
for (let i = left; i < right; i++) {
if (arr[i] < pivot) {
[arr[i], arr[storeIndex]] = [arr[storeIndex], arr[i]];
storeIndex++;
}
}
[arr[storeIndex], arr[right]] = [arr[right], arr[storeIndex]];
return storeIndex;
}
这个优化版本就像园丁不是随机选择参考花,而是从花园的开始、中间和结尾各选一朵花,然后选择这三朵花中"中间"高度的那朵作为参考,这样可以减少选到"最高"或"最矮"的花作为参考的概率,从而提高排序效率。
3.2 插入排序与快速排序结合
typescript
function hybridQuickSort(arr: number[], left: number = 0, right: number = arr.length - 1, threshold: number = 10): number[] {
if (right - left + 1 <= threshold) {
return insertionSort(arr, left, right);
}
if (left < right) {
const pivotIndex = medianOfThree(arr, left, right);
const pivotNewIndex = partition(arr, left, right, pivotIndex);
hybridQuickSort(arr, left, pivotNewIndex - 1, threshold);
hybridQuickSort(arr, pivotNewIndex + 1, right, threshold);
}
return arr;
}
function insertionSort(arr: number[], left: number, right: number): number[] {
for (let i = left + 1; i <= right; i++) {
let current = arr[i];
let j = i - 1;
while (j >= left && arr[j] > current) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = current;
}
return arr;
}
这个优化版本就像园丁在整理花园时,发现某一小片区域的花朵数量少于某个阈值(比如10朵)时,直接用更简单的方法来排列这些花。这样可以减少整理的复杂度,提高整体的效率。
案例代码和动态图
typescript
const array = [38, 27, 43, 3, 9, 48, 10];
const sortedArray = quickSort(array);
console.log(sortedArray); // [3, 9, 10, 27, 38, 43, 48]
4. 快速排序的优点
- 高效性:在平均情况下,快速排序是所有同数量级(O(nlogn))排序算法中最快的
- 原地排序:快速排序可以在原数组上进行,不需要额外的存储空间
- 缓存友好:快速排序的数据访问模式对缓存友好,这使得它在实际应用中表现优秀
5. 快速排序的缺点
- 不稳定性:快速排序是不稳定的排序算法
- 最坏情况性能:在最坏情况下(已经排序的数组),时间复杂度退化到O(n^2)
- 对小规模数据,不如插入排序等简单算法效率高
总结
快速排序就像是园丁整理一大片杂乱的花园。它告诉我们,面对一大片混乱的花朵,可以通过选择一株参考花,将花园分成两部分,然后逐步整理。这种"分而治之"的思想不仅在整理花园中有用,在我们日常解决复杂问题时也常常能派上用场。
快速排序的高效性和原地排序的特性,使它在实际应用中表现出色。特别是在处理大规模数据时,快速排序常常是首选的算法。然而,它的不稳定性和在某些特殊情况下的性能退化,也提醒我们在选择算法时需要考虑具体的应用场景。
喜欢的话就点个赞 ❤️,关注一下吧,有问题也欢迎讨论指教。感谢大家!!!
下期预告: TypeScript 算法手册 - 计数排序