排序算法复杂度分析

常见排序算法的时间复杂度、空间复杂度对比

问题

分析常见排序算法的时间复杂度与空间复杂度,理解各算法的适用场景。

解答

复杂度对比表

算法最好时间最坏时间平均时间空间稳定性
冒泡排序O(n)O(n²)O(n²)O(1)稳定
选择排序O(n²)O(n²)O(n²)O(1)不稳定
插入排序O(n)O(n²)O(n²)O(1)稳定
归并排序O(n log n)O(n log n)O(n log n)O(n)稳定
快速排序O(n log n)O(n²)O(n log n)O(log n)不稳定
堆排序O(n log n)O(n log n)O(n log n)O(1)不稳定

冒泡排序

function bubbleSort(arr) {
  const len = arr.length;
  // 外层控制轮数
  for (let i = 0; i < len - 1; i++) {
    let swapped = false;
    // 内层比较相邻元素
    for (let j = 0; j < len - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
        swapped = true;
      }
    }
    // 没有交换说明已有序,提前退出
    if (!swapped) break;
  }
  return arr;
}

选择排序

function selectionSort(arr) {
  const len = arr.length;
  for (let i = 0; i < len - 1; i++) {
    let minIndex = i;
    // 找到未排序部分的最小值
    for (let j = i + 1; j < len; j++) {
      if (arr[j] < arr[minIndex]) {
        minIndex = j;
      }
    }
    // 交换到已排序部分末尾
    if (minIndex !== i) {
      [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
    }
  }
  return arr;
}

插入排序

function insertionSort(arr) {
  const len = arr.length;
  for (let i = 1; i < len; i++) {
    const current = arr[i];
    let j = i - 1;
    // 将比 current 大的元素后移
    while (j >= 0 && arr[j] > current) {
      arr[j + 1] = arr[j];
      j--;
    }
    // 插入到正确位置
    arr[j + 1] = current;
  }
  return arr;
}

归并排序

function mergeSort(arr) {
  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, right) {
  const result = [];
  let i = 0, j = 0;

  // 比较两个数组元素,按序放入结果
  while (i < left.length && j < right.length) {
    if (left[i] <= right[j]) {
      result.push(left[i++]);
    } else {
      result.push(right[j++]);
    }
  }

  // 拼接剩余元素
  return result.concat(left.slice(i)).concat(right.slice(j));
}

快速排序

function quickSort(arr, left = 0, right = arr.length - 1) {
  if (left < right) {
    const pivotIndex = partition(arr, left, right);
    quickSort(arr, left, pivotIndex - 1);
    quickSort(arr, pivotIndex + 1, right);
  }
  return arr;
}

function partition(arr, left, right) {
  const pivot = arr[right]; // 选择最右元素为基准
  let i = left;

  for (let j = left; j < right; j++) {
    if (arr[j] < pivot) {
      [arr[i], arr[j]] = [arr[j], arr[i]];
      i++;
    }
  }

  // 基准放到正确位置
  [arr[i], arr[right]] = [arr[right], arr[i]];
  return i;
}

堆排序

function heapSort(arr) {
  const len = arr.length;

  // 构建最大堆
  for (let i = Math.floor(len / 2) - 1; i >= 0; i--) {
    heapify(arr, len, i);
  }

  // 逐个取出堆顶元素
  for (let i = len - 1; i > 0; i--) {
    [arr[0], arr[i]] = [arr[i], arr[0]];
    heapify(arr, i, 0);
  }

  return arr;
}

function heapify(arr, size, root) {
  let largest = root;
  const left = 2 * root + 1;
  const right = 2 * root + 2;

  if (left < size && arr[left] > arr[largest]) {
    largest = left;
  }
  if (right < size && arr[right] > arr[largest]) {
    largest = right;
  }

  if (largest !== root) {
    [arr[root], arr[largest]] = [arr[largest], arr[root]];
    heapify(arr, size, largest);
  }
}

关键点

  • O(n²) 算法:冒泡、选择、插入适合小数据量或基本有序的数据
  • O(n log n) 算法:归并、快排、堆排序适合大数据量
  • 稳定性:相等元素排序后相对位置不变为稳定,归并和插入排序是稳定的
  • 空间换时间:归并排序需要 O(n) 额外空间,但时间复杂度稳定
  • 快排最坏情况:数组已有序时退化为 O(n²),可用随机选择基准优化