返回

JS算法之常规排序算法

前端

前言

大家好,我是柒八九。最近在看Vue3源码分析,发现无论React还是Vue,在框架层面,为了实现特定的场景,它们为我们封装了很多比较复杂的逻辑。

比如,针对Virtual Dom的Diff算法,针对Promise实现异步编程等等。

当然,封装这些复杂逻辑的最终目的,其实是想让开发者的代码变的更简洁,便于管理和维护。

但是,作为一个对技术原理非常感兴趣的人,就想了解一下框架的底层到底是怎么实现的。

于是我,还是决定先从JavaScript的基础入手,比如,学习一些算法原理。

常规排序算法

今天我们聊一聊,排序算法。

排序算法,其实就是将一个无序的序列,通过某种算法转化为有序的序列,比如从小到大,或者从大到小。

排序算法,根据算法的时间复杂度,一般分为两大类:

  • 时间复杂度为O(nlogn)的排序算法 :冒泡排序、选择排序、插入排序、希尔排序、归并排序、堆排序。
  • 时间复杂度为O(n^2)的排序算法 :计数排序、桶排序、基数排序。

冒泡排序

冒泡排序,是一种最简单、最直观的排序算法。

冒泡排序的思路是:

  1. 从数组的第一个元素开始,依次与后面的元素比较,如果前一个元素大于后一个元素,则交换这两个元素的位置。
  2. 继续比较下一个元素,直到数组的最后一个元素。
  3. 然后,从数组的倒数第二个元素开始,依次与前面的元素比较,如果前一个元素大于后一个元素,则交换这两个元素的位置。
  4. 重复步骤2和步骤3,直到数组中的所有元素都按从小到大排列好。

冒泡排序的代码实现如下:

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

选择排序

选择排序,也是一种简单的排序算法。

选择排序的思路是:

  1. 从数组的第一个元素开始,找出数组中最小(或最大)的元素,并将其与第一个元素交换位置。
  2. 然后,从数组的第二个元素开始,找出数组中剩余元素的最小(或最大)的元素,并将其与第二个元素交换位置。
  3. 重复步骤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;
      }
    }
    let temp = arr[i];
    arr[i] = arr[minIndex];
    arr[minIndex] = temp;
  }
  return arr;
}

插入排序

插入排序,也是一种简单的排序算法。

插入排序的思路是:

  1. 从数组的第二个元素开始,依次与前面的元素比较,如果前一个元素大于后一个元素,则将后一个元素插入到前一个元素的前面。
  2. 重复步骤1,直到数组中的所有元素都按从小到大排列好。

插入排序的代码实现如下:

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

希尔排序

希尔排序,也是一种简单的排序算法。

希尔排序的思路是:

  1. 将数组分成若干个子数组,每个子数组的元素个数为h。
  2. 对每个子数组进行插入排序。
  3. 减少h的值,重复步骤1和步骤2,直到h=1。

希尔排序的代码实现如下:

function shellSort(arr) {
  let h = 1;
  while (h < arr.length / 3) {
    h = 3 * h + 1;
  }
  while (h >= 1) {
    for (let i = h; i < arr.length; i++) {
      let j = i - h;
      let temp = arr[i];
      while (j >= 0 && arr[j] > temp) {
        arr[j + h] = arr[j];
        j -= h;
      }
      arr[j + h] = temp;
    }
    h = Math.floor(h / 3);
  }
  return arr;
}

归并排序

归并排序,是一种高效的排序算法。

归并排序的思路是:

  1. 将数组分成两半,分别对两半进行归并排序。
  2. 合并两个排好序的数组,得到一个排好序的数组。

归并排序的代码实现如下:

function mergeSort(arr) {
  if (arr.length <= 1) {
    return arr;
  }
  let mid = Math.floor(arr.length / 2);
  let left = mergeSort(arr.slice(0, mid));
  let right = mergeSort(arr.slice(mid));
  return merge(left, right);
}

function merge(left, right) {
  let result = [];
  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++;
    }
  }
  while (i < left.length) {
    result.push(left[i]);
    i++;
  }
  while (j < right.length) {
    result.push(right[j]);
    j++;
  }
  return result;
}

堆排序

堆排序,也是一种高效的排序算法。

堆排序的思路是:

  1. 将数组构建成一个堆。
  2. 从堆顶开始,依次与堆的最后一个元素交换位置,并调整堆的结构,使之仍然是一个堆。
  3. 重复步骤2,直到堆中只剩下一个元素。

堆排序的代码实现如下:

function heapSort(arr) {
  buildMaxHeap(arr);
  for (let i = arr.length - 1; i > 0; i--) {
    let temp = arr[0];
    arr[0] = arr[i];
    arr[i] = temp;
    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, n) {
  let largest = i;
  let left = 2 * i + 1;
  let 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;
    maxHeapify(arr, largest, n);