返回

从经典排序算法探究稳定性和性能

前端

七种常见排序算法的JS实现及稳定性的讨论

算法实现及分析

1. 冒泡排序

function bubbleSort(arr) {
  let swapped;
  do {
    swapped = false;
    for (let i = 0; i < arr.length - 1; i++) {
      if (arr[i] > arr[i + 1]) {
        let temp = arr[i];
        arr[i] = arr[i + 1];
        arr[i + 1] = temp;
        swapped = true;
      }
    }
  } while (swapped);
  return arr;
}

冒泡排序通过不断比较相邻元素,将较大的元素依次“冒泡”到数组末尾,是稳定排序算法。

2. 选择排序

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

选择排序通过在每次循环中找出未排序部分的最小元素,将其与当前元素交换,是稳定排序算法。

3. 插入排序

function insertionSort(arr) {
  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. 希尔排序

function shellSort(arr) {
  let gap = Math.floor(arr.length / 2);
  while (gap > 0) {
    for (let i = gap; i < arr.length; i++) {
      let key = arr[i];
      let j = i - gap;
      while (j >= 0 && arr[j] > key) {
        arr[j + gap] = arr[j];
        j -= gap;
      }
      arr[j + gap] = key;
    }
    gap = Math.floor(gap / 2);
  }
  return arr;
}

希尔排序在插入排序的基础上引入增量间隔优化,是稳定排序算法。

5. 归并排序

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++;
    }
  }
  while (leftIndex < left.length) {
    merged.push(left[leftIndex]);
    leftIndex++;
  }
  while (rightIndex < right.length) {
    merged.push(right[rightIndex]);
    rightIndex++;
  }
  return merged;
}

归并排序采用分治策略,是稳定排序算法。

6. 快速排序

function quickSort(arr) {
  if (arr.length <= 1) {
    return arr;
  }
  const pivot = arr[0];
  const left = [];
  const right = [];
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  return quickSort(left).concat(pivot, quickSort(right));
}

快速排序采用随机化策略,是不稳定排序算法。

7. 堆排序

function heapSort(arr) {
  const n = arr.length;
  for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
    heapify(arr, n, i);
  }
  for (let i = n - 1; i >= 0; i--) {
    let temp = arr[0];
    arr[0] = arr[i];
    arr[i] = temp;
    heapify(arr, i, 0);
  }
  return arr;
}

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

堆排序采用优先级队列实现,是不稳定排序算法。

稳定性讨论

稳定性是指当排序算法遇到相同元素时,这些元素的相对顺序是否保持不变。稳定排序算法在排序过程中不会改变相同元素的相对顺序,而快速排序等不稳定排序算法则可能会改变相同元素的相对顺序。在某些应用场景中,稳定性非常重要,例如当需要对具有相同键值的记录进行排序时。

算法性能分析

在实际应用中,算法的性能是一个重要因素。以下是对这些排序算法的性能分析:

  • 时间复杂度:冒泡排序、选择排序和插入排序的时间复杂度都为O(n^2),这意味着随着输入规模的增加,算法的运行时间会呈平方级增长。希尔排序、归并排序和快速排序的时间复杂度为O(nlogn),这意味着算法的运行时间会随着输入规模的增加呈对数级增长。堆排序的时间复杂度为O(nlogn)在平均情况下,但最坏情况下的时间复杂度为O(n^2)。
  • 空间复杂度:冒泡排序、选择排序和插入排序的空间复杂度都为O(1),这意味着算法在运行过程中不会分配额外的内存空间。希尔排序、归并排序和快速排序的空间复杂度为O(n),这意味着算法在运行过程中需要分配额外的内存空间来存储临时数据。堆排序的空间复杂度也为O(n),但它不需要额外的内存空间来存储临时数据。

在实际应用中,选择合适的排序算法需要考虑具体场景的具体需求,例如数据的规模、是否需要稳定性、以及算法的性能等。

总结

本文介绍了七种常见的排序算法及其JavaScript实现,并对它们的稳定性进行了讨论。同时,我们也对这些算法的性能进行了分析。通过对这些算法的深入了解,读者可以根据具体场景的需求