Skip to content

排序

  • 重点排序:快速排序 (不稳定),归并排序 (稳定),堆排序 (不稳定),希尔排序 (不稳定)
  • 简单排序:冒泡排序 (稳定),选择排序 (不稳定),插入排序 (稳定)
  • 有限数据范围,空间换时间:计数排序 (稳定),桶排序 (稳定),基数排序 (稳定)

快速排序

  • 每次确定一个 pivot 的位置,左侧都比它小,右侧都比它大
  • 从两边向中间遍历,直到不满足要求,则交换元素
  • 稳定性:不稳定,两侧元素交换可能破坏稳定性
ts
function partition(nums: number[], lo: number, hi: number): number {
  const randIdx = Math.floor(Math.random() * (hi - lo + 1)) + lo;
  [nums[lo], nums[randIdx]] = [nums[randIdx], nums[lo]];
  const pivot = nums[lo];
  while (lo < hi) {
    while (lo < hi && pivot <= nums[hi]) {
      --hi;
    }
    nums[lo] = nums[hi];
    while (lo < hi && nums[lo] <= pivot) {
      ++lo;
    }
    nums[hi] = nums[lo];
  }
  nums[lo] = pivot;
  return lo;
}

function quickSort(nums: number[], lo: number, hi: number) {
  if (lo >= hi) {
    return;
  }
  const mi = partition(nums, lo, hi);
  quickSort(nums, lo, mi - 1);
  quickSort(nums, mi + 1, hi);
}

function sortArray(nums: number[]): number[] {
  quickSort(nums, 0, nums.length - 1);
  return nums;
}

归并排序

  • 合并两个有序数组只需要双指针、线性复杂度
  • 不断分解子问题,直到数组只有一个元素、无需排序
  • 稳定性:稳定,从左到右依次排列
ts
function merge(nums: number[], lo: number, mi: number, hi: number) {
  const temp = new Array(hi - lo);
  let i = lo;
  let j = mi;
  let k = 0;
  while (i < mi && j < hi) {
    if (nums[i] <= nums[j]) {
      temp[k++] = nums[i++];
    } else {
      temp[k++] = nums[j++];
    }
  }
  while (i < mi) {
    temp[k++] = nums[i++];
  }
  while (j < hi) {
    temp[k++] = nums[j++];
  }
  for (i = lo, k = 0; i < hi; ++i, ++k) {
    nums[i] = temp[k];
  }
}

function mergeSort(nums: number[], lo: number, hi: number) {
  if (lo + 1 >= hi) {
    return;
  }
  const mi = Math.floor((lo + hi) / 2);
  mergeSort(nums, lo, mi);
  mergeSort(nums, mi, hi);
  merge(nums, lo, mi, hi);
}

function sortArray(nums: number[]): number[] {
  mergeSort(nums, 0, nums.length);
  return nums;
}

堆排序

  • 本质是堆上的选择排序
  • 根据大顶堆的特性,最大的元素始终是第一个元素
  • 构建大顶堆,从中间元素 (最后一个非叶节点) 到第一个元素 (根节点),依次向下调整
  • 将最大的元素交换到末尾并排除出堆,然后向下调整,维持大顶堆的特性
  • 稳定性:不稳定,首尾元素交换可能破坏稳定性
ts
function siftDown(nums: number[], lo: number, hi: number): void {
  let parent = lo;
  let child = parent * 2 + 1;
  while (child <= hi) {
    if (child + 1 <= hi && nums[child] < nums[child + 1]) {
      ++child;
    }
    if (nums[parent] >= nums[child]) {
      return;
    }
    [nums[parent], nums[child]] = [nums[child], nums[parent]];
    parent = child;
    child = parent * 2 + 1;
  }
}

function heapSort(nums: number[]): void {
  const n = nums.length;
  for (let i = Math.floor((n - 2) / 2); i >= 0; --i) {
    siftDown(nums, i, n - 1);
  }
  for (let i = n - 1; i >= 0; --i) {
    [nums[0], nums[i]] = [nums[i], nums[0]];
    siftDown(nums, 0, i - 1);
  }
}

function sortArray(nums: number[]): number[] {
  heapSort(nums);
  return nums;
}

希尔排序

  • 划定一个间隔,从 n / 2 到 1
  • 每次排序只排序符合间隔的子序列,随着间隔缩小,数组逐渐有序
  • 子序列内排序通常采用插入排序
  • 稳定性:不稳定,不同子序列内的排序可能破坏稳定性
ts
function shellSort(nums: number[]) {
  const n = nums.length;
  for (let gap = Math.floor(n / 2); gap >= 1; gap = Math.floor(gap / 2)) {
    for (let i = 0; i < gap; ++i) {
      for (let j = i + gap; j < n; j += gap) {
        const target = nums[j];
        let k = j - gap;
        while (k >= 0 && target < nums[k]) {
          nums[k + gap] = nums[k];
          k -= gap;
        }
        nums[k + gap] = target;
      }
    }
  }
}

function sortArray(nums: number[]): number[] {
  shellSort(nums);
  return nums;
}

冒泡排序

  • 稳定性:稳定,从左到右依次冒泡
ts
function bubbleSort(nums: number[]) {
  const n = nums.length;
  for (let i = n; i >= 2; --i) {
    let noSwap = true;
    for (let j = 0; j < i - 1; ++j) {
      if (nums[j] > nums[j + 1]) {
        [nums[j], nums[j + 1]] = [nums[j + 1], nums[j]];
        noSwap = false;
      }
    }
    if (noSwap) {
      break;
    }
  }
}

function sortArray(nums: number[]): number[] {
  bubbleSort(nums);
  return nums;
}

选择排序

  • 稳定性:不稳定,元素交换可能破坏稳定性
ts
function selectionSort(nums: number[]) {
  const n = nums.length;
  for (let i = n; i >= 2; --i) {
    let maxIdx = 0;
    for (let j = 0; j < i; ++j) {
      if (nums[j] > nums[maxIdx]) {
        maxIdx = j;
      }
    }
    [nums[maxIdx], nums[i - 1]] = [nums[i - 1], nums[maxIdx]];
  }
}

function sortArray(nums: number[]): number[] {
  selectionSort(nums);
  return nums;
}

插入排序

  • 稳定性:稳定,从左到右依次插入
ts
function insertionSort(nums: number[]) {
  const n = nums.length;
  for (let i = 1; i < n; ++i) {
    const target = nums[i];
    let j = i - 1;
    while (j >= 0 && nums[j] > target) {
      nums[j + 1] = nums[j];
      --j;
    }
    nums[j + 1] = target;
  }
}

function sortArray(nums: number[]): number[] {
  insertionSort(nums);
  return nums;
}

计数排序

  • 稳定性:稳定,从左到右依次处理
ts
function countingSort(nums: number[], minVal: number, maxVal: number) {
  const count: number[] = new Array(maxVal - minVal + 1).fill(0);
  for (const num of nums) {
    count[num - minVal] += 1;
  }
  let idx = 0;
  for (let i = minVal; i <= maxVal; ++i) {
    while (count[i - minVal] > 0) {
      nums[idx++] = i;
      count[i - minVal] -= 1;
    }
  }
}

function sortArray(nums: number[]): number[] {
  countingSort(nums, -50000, 50000);
  return nums;
}

桶排序

  • 桶内排序算法:由于元素不多,可以使用简单的插入排序 (稳定),也可以使用高效的快速排序 (不稳定)
  • 稳定性:如果按顺序放入桶内,且桶内排序是稳定的,则桶排序是稳定的
ts
function bucketSort(nums: number[]): void {
  const n = nums.length;
  if (n === 0) {
    return;
  }
  const minVal = Math.min(...nums);
  const maxVal = Math.max(...nums);
  const bucketCount = Math.ceil((maxVal - minVal + 1) / n);
  const buckets: number[][] = new Array(bucketCount).fill(0).map(() => []);
  for (const num of nums) {
    buckets[Math.floor((num - minVal) / n)].push(num);
  }
  let i = 0;
  for (const bucket of buckets) {
    bucket.sort((a, b) => a - b);
    for (const num of bucket) {
      nums[i++] = num;
    }
  }
}

function sortArray(nums: number[]): number[] {
  bucketSort(nums);
  return nums;
}

基数排序

  • 稳定性:稳定,按顺序放入桶中
ts
function radixSort(nums: number[], minVal: number, maxVal: number) {
  let newMaxVal = maxVal - minVal;
  let maxDigit = 0;
  while (newMaxVal) {
    maxDigit += 1;
    newMaxVal = Math.floor(newMaxVal / 10);
  }
  const buckets: number[][] = new Array(10);
  let div = 1;
  for (let i = 0; i < maxDigit; ++i) {
    for (let j = 0; j < 10; ++j) {
      buckets[j] = [];
    }
    for (const num of nums) {
      buckets[Math.floor((num - minVal) / div) % 10].push(num);
    }
    let idx = 0;
    for (const bucket of buckets) {
      for (const num of bucket) {
        nums[idx++] = num;
      }
    }
    div *= 10;
  }
}

function sortArray(nums: number[]): number[] {
  radixSort(nums, -50000, 50000);
  return nums;
}