返回

算法初探:揭秘经典排序算法背后的奥秘

前端

算法,计算机科学的基石,为解决复杂问题提供了高效的途径。在众多算法中,排序算法占据着举足轻重的地位,其重要性体现在对海量数据高效有序的管理。本文将深入浅出地探讨六种经典排序算法:选择排序、插入排序、冒泡排序、快速排序、归并排序和堆排序。这些算法各具特色,适应不同的数据规模和场景。

选择排序

选择排序基于这样的思想:从无序数组中找出最小的元素,将其与第一个元素交换,然后重复此过程,直至整个数组有序。这种算法直观易懂,实现简单,但效率较低,时间复杂度为 O(n^2)。

function selectionSort(arr) {
  for (let i = 0; i < arr.length; i++) {
    let minIndex = i;
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[j] < arr[minIndex]) {
        minIndex = j;
      }
    }
    [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
  }
  return arr;
}

插入排序

插入排序的原理是将数组看作一个已排序的子数组和一个无序子数组,不断从无序子数组中取出元素插入到已排序子数组中,使其保持有序。与选择排序相比,插入排序在部分有序的数据集上效率较高,时间复杂度为 O(n^2)。

function insertionSort(arr) {
  for (let i = 1; i < arr.length; i++) {
    const currentValue = arr[i];
    let j = i - 1;
    while (j >= 0 && arr[j] > currentValue) {
      arr[j + 1] = arr[j];
      j--;
    }
    arr[j + 1] = currentValue;
  }
  return arr;
}

冒泡排序

冒泡排序是一种简单而直观的算法,通过不断交换相邻元素,将较小的元素逐个“冒泡”到数组顶部。尽管算法易于实现,但其效率极低,时间复杂度为 O(n^2)。

function bubbleSort(arr) {
  for (let i = 0; i < arr.length; i++) {
    for (let j = 0; j < arr.length - i - 1; j++) {
      if (arr[j] > arr[j + 1]) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
    }
  }
  return arr;
}

快速排序

快速排序是一种分治算法,其效率远高于前面的排序算法。算法的核心思想是选择一个基准元素,将数组分为小于基准元素的子数组和大于基准元素的子数组,然后递归地对这两个子数组执行快速排序。快速排序的时间复杂度为 O(n log n),但其稳定性较差。

function quickSort(arr, left = 0, right = arr.length - 1) {
  if (left >= right) {
    return;
  }
  const pivot = arr[Math.floor((left + right) / 2)];
  const index = partition(arr, pivot, left, right);
  quickSort(arr, left, index - 1);
  quickSort(arr, index, right);
}

function partition(arr, pivot, left, right) {
  while (left < right) {
    while (arr[left] < pivot) {
      left++;
    }
    while (arr[right] > pivot) {
      right--;
    }
    if (left <= right) {
      [arr[left], arr[right]] = [arr[right], arr[left]];
      left++;
      right--;
    }
  }
  return left;
}

归并排序

归并排序同样是一种分治算法,其思想是将数组分成若干个较小的子数组,对每个子数组进行排序,然后再将排序后的子数组合并成一个有序数组。归并排序的时间复杂度为 O(n log n),并且稳定性好。

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 merged = [];
  let leftIndex = 0;
  let rightIndex = 0;
  while (leftIndex < left.length && rightIndex < right.length) {
    if (left[leftIndex] < right[rightIndex]) {
      merged.push(left[leftIndex]);
      leftIndex++;
    } else {
      merged.push(right[rightIndex]);
      rightIndex++;
    }
  }
  return merged.concat(left.slice(leftIndex)).concat(right.slice(rightIndex));
}

堆排序

堆排序是一种基于堆数据结构的排序算法。堆数据结构是一种完全二叉树,其满足堆性质:每个节点的值都大于或等于其子节点的值。堆排序先将数组构建成一个大根堆,然后依次从堆顶取出最大元素,堆排序的时间复杂度为 O(n log n)。

function heapSort(arr) {
  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) {
  for (let i = Math.floor(arr.length / 2) - 1; i >= 0; i--) {
    maxHeapify(arr, i, arr.length);
  }
}

function maxHeapify(arr, i, heapSize) {
  const left = 2 * i + 1;
  const right = 2 * i + 2;
  let largest = i;
  if (left < heapSize && arr[left] > arr[largest]) {
    largest = left;
  }
  if (right < heapSize && arr[right] > arr[largest]) {
    largest = right;
  }
  if (largest != i) {
    [arr[i], arr[largest]] = [arr[largest], arr[i]];
    maxHeapify(arr, largest, heapSize);
  }
}

总结

上述六种经典排序算法各有优缺点,适合不同的场景。对于小型数据集,选择排序和插入排序表现较好;对于中小型数据集,快速排序和归并排序效率较高;对于大型数据集,堆排序是一个不错的选择。算法的选择需要根据具体问题和数据规模进行权衡。