好久没有写过算法了,快连小学生都不如,突然想复习一些比较类算法。开整
下面将冒泡排序、选择排序、插入排序、快速排序、归并排序和堆排序的 TypeScript 实现,并对每种算法进行简要说明。
1. 冒泡排序
冒泡排序是最简单的排序算法之一,它通过重复地遍历数组,比较相邻的元素并在必要时交换它们。
- 小规模数据集:由于其简单性,冒泡排序非常适合教学和理解排序的基本概念。
- 部分有序数据:如果数据集已经基本有序,冒泡排序可以较快地完成排序,因为它会在每轮遍历中逐渐减少需要比较的元素数量。
function bubbleSort(arr: number[]): number[] {
let n = arr.length;
for (let i = 0; i < n; i++) {
for (let j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
2. 选择排序
选择排序通过从未排序的部分找到最小(或最大)的元素,然后将其放置在已排序序列的末尾。
- 小规模数据集:与冒泡排序类似,选择排序在数据量不大时较为适用。
- 不需要维护元素相对顺序的场景:选择排序虽然不稳定,但它只需要一次遍历就能找到最小元素,这在某些不需要稳定性的场景下是有用的。
function selectionSort(arr: number[]): number[] {
let n = arr.length;
for (let i = 0; i < n - 1; i++) {
let minIndex = i;
for (let j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
if (minIndex !== i) {
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
}
return arr;
}
3. 插入排序
插入排序将每个元素插入到已排序序列的正确位置,类似于整理手中的扑克牌。
- 小规模数据集或部分有序数据:插入排序在数据集较小或接近有序时效率很高,因为它的平均和最差时间复杂度分别为 O(n) 和 O(n^2)。
- 在线排序:当数据持续到达,需要实时插入新元素并保持有序时,插入排序是很好的选择。
function insertionSort(arr: number[]): number[] {
for (let i = 1; i < arr.length; i++) {
let key = arr[i];
let j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
return arr;
}
4. 快速排序
快速排序采用分而治之的策略,选择一个基准元素,将数组分为小于和大于基准的两部分,然后递归排序这两部分。
- 大规模数据集:快速排序是平均情况下最快的排序算法之一,时间复杂度为 O(n log n),适用于大量数据的排序。
- 内存受限环境:由于快速排序是原地排序,不需要额外的内存空间,因此在内存资源有限的环境中很有优势。
function quickSort(arr: number[]): number[] {
function sort(low: number, high: number) {
if (low < high) {
const pivotIndex = partition(low, high);
sort(low, pivotIndex - 1);
sort(pivotIndex + 1, high);
}
}
function partition(low: number, high: number): number {
const pivot = arr[high];
let i = low - 1;
for (let j = low; j < high; j++) {
if (arr[j] < pivot) {
i++;
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
[arr[i + 1], arr[high]] = [arr[high], arr[i + 1]];
return i + 1;
}
sort(0, arr.length - 1);
return arr;
}
5. 归并排序
归并排序同样采用分而治之的策略,但它是稳定的排序算法,适用于大数据集。
- 大规模数据集:归并排序保证了 O(n log n) 的时间复杂度,无论在最好、平均还是最坏情况下,因此非常适合处理大量数据。
- 需要稳定排序的场景:归并排序是稳定的排序算法,能保持相同元素的原有顺序,这对于需要维护数据相对位置的场景非常重要。
- 外部排序:归并排序可以有效地处理那些不能完全放入内存的数据,通过分批读取和写入磁盘来完成排序。
function mergeSort(arr: number[]): number[] {
if (arr.length <= 1) {
return arr;
}
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
return merge(left, right);
function merge(left: number[], right: number[]): number[] {
let result: number[] = [];
let i = 0;
let j = 0;
while (i < left.length && j < right.length) {
if (left[i] < right[j]) {
result.push(left[i]);
i++;
} else {
result.push(right[j]);
j++;
}
}
return result.concat(left.slice(i)).concat(right.slice(j));
}
}
6. 堆排序(Heap Sort)
利用二叉堆的数据结构进行排序,先构建一个堆,然后不断移除最大(或最小)元素。
- 需要原地排序的场景:堆排序是原地排序算法,只需要很小的辅助空间,适用于内存敏感的应用。
- 大规模数据集:堆排序的时间复杂度为 O(n log n),对于大型数据集来说效率较高。
function heapSort(arr: number[]): number[] {
buildMaxHeap(arr);
for (let i = arr.length - 1; i > 0; i--) {
[arr[0], arr[i]] = [arr[i], arr[0]]; // 移动当前最大的元素到数组末尾
maxHeapify(arr, 0, i); // 重新调整剩余元素为最大堆
}
return arr;
function buildMaxHeap(arr: number[]) {
for (let i = Math.floor(arr.length / 2) - 1; i >= 0; i--) {
maxHeapify(arr, i, arr.length);
}
}
function maxHeapify(arr: number[], i: number, size: number) {
let largest = i;
const left = 2 * i + 1;
const right = 2 * i + 2;
if (left < size && arr[left] > arr[largest]) {
largest = left;
}
if (right < size && arr[right] > arr[largest]) {
largest = right;
}
if (largest !== i) {
[arr[i], arr[largest]] = [arr[largest], arr[i]];
maxHeapify(arr, largest, size);
}
}
}
以上就是六种常见比较类排序算法的 TypeScript 实现。每种算法都有其独特的特性和应用场景,在实际使用时需要根据数据的具体情况来选择最合适的算法。
在实际应用中,选择哪种排序算法应基于数据的大小、是否需要稳定排序、内存限制以及数据是否部分有序等因素。例如,对于大规模数据的排序,通常会选择快速排序、归并排序或堆排序;而对于小规模或部分有序的数据,则插入排序或冒泡排序可能是更好的选择。在需要维护数据相对位置的场景下,归并排序因其稳定性成为首选。
期待各位大佬早日成为算法高手!!!