TypeScript 中实现几种常见的比较类排序算法

294 阅读5分钟

好久没有写过算法了,快连小学生都不如,突然想复习一些比较类算法。开整

下面将冒泡排序、选择排序、插入排序、快速排序、归并排序和堆排序的 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 实现。每种算法都有其独特的特性和应用场景,在实际使用时需要根据数据的具体情况来选择最合适的算法。

在实际应用中,选择哪种排序算法应基于数据的大小、是否需要稳定排序、内存限制以及数据是否部分有序等因素。例如,对于大规模数据的排序,通常会选择快速排序、归并排序或堆排序;而对于小规模或部分有序的数据,则插入排序或冒泡排序可能是更好的选择。在需要维护数据相对位置的场景下,归并排序因其稳定性成为首选。

期待各位大佬早日成为算法高手!!!