JavaScript中的排序算法

62 阅读6分钟

JavaScript中的排序算法

排序可以被称为以某种特定顺序排列文件。所执行的排列可以基于每个文件的存在价值。该特定顺序可以是升序或降序的方式。排序算法是给计算机的指令,以特定顺序排列元素。

前提条件

要跟上这篇文章,具备以下条件将很有帮助。

  • 一些基本的JavaScript知识。

  • 在你的计算机上安装了[Node.js]。

排序算法的分类

排序算法可分为。

  1. 内部排序算法:这些是应用于少量数据的排序算法。只使用主内存。例如,泡点排序、插入排序和quicksort。

  2. 外部排序算法:这些是可以应用于大量数据的排序算法。因此,要使用外部存储设备,如硬盘和闪存盘。一个例子是合并排序。

分拣算法的效率

有些排序算法比其他算法更有效率。一种排序算法的效率通常由以下性能指标来定义。

  • 时间复杂度:这是计算机根据一种算法进行排序所需的时间量。

  • 内存复杂度:这是计算机根据算法执行排序所需的计算机内存数量。

基于上述因素,一个算法有四种性能情况。

  1. 最坏情况下的时间复杂度:它是一个定义为对任何大小为n的实例所采取的最大步骤数的结果的函数,它通常用Big O符号表示。

  2. 平均情况下的时间复杂度:它是一个定义为在任何大小为n的实例上所采取的平均步骤数的结果的函数,通常用Big Theta符号表示。

  3. 最佳情况下的时间复杂度:这是一个定义为在任何大小为n的实例上所采取的最小步骤数的结果的函数。

  4. 空间复杂度:它是一个定义为执行算法所需的额外内存空间的函数。它通常用大O符号表示。

分拣过程中应用的策略

  • 递归:递归是一种编程方法,你用函数本身来定义一个函数。该函数一般用稍加修改的参数来调用自己(以达到收敛的目的)。

  • 分割与征服:该算法通过将问题划分为较小的子问题,并解决这些子问题,从而得出总体解决方案,完成其任务。

分拣算法

对于下面讨论的每一种排序算法,都有一个关于该算法如何工作的逐步解释,图像表示,以及使用JavaScript实现该算法的方法。

泡状排序

泡沫排序遵循递归技术。

逐步指导。

  • 从比较一个数组中的前两个元素开始。

  • 如果需要的话,将它们交换。

  • 继续下去,直到数组的末端。在这一点上,你已经进行了一系列的内部传递并完成了外部传递。

  • 重复这个过程,直到整个数组被排序完毕。

图片展示

bubble_sort

JavaScript实现

function bubbleSort(arr){

    //Outer pass
    for(let i = 0; i < arr.length; i++){

        //Inner pass
        for(let j = 0; j < arr.length - i - 1; j++){

            //Value comparison using ascending order

            if(arr[j + 1] < arr[j]){

                //Swapping
                [arr[j + 1],arr[j]] = [arr[j],arr[j + 1]]
            }
        }
    };
    return arr;
};

console.log(bubbleSort([5,3,8,4,6]));

输出。

[ 3, 4, 5, 6, 8 ]

泡沫排序有以下性能情况。

  • 最坏情况下的时间复杂度。大O(n^2)。

  • 平均情况下的时间复杂度。大theta (n^2)。

  • 最佳情况下的时间复杂度。大欧米茄(n)。

  • 空间复杂度。大O(1)。

插入式排序

插入式排序使用递归技术。数组中有一部分是被排序的,另一部分是未排序的。所以你必须逐一比较未排序部分的元素,并以正确的顺序将它们插入已排序部分。在下面的指南中,我们使用的是升序法。

循序渐进的指南

  • 首先比较数组的第二个元素和第一个元素,假设第一个元素是排序的部分。如果第二个元素比第一个元素小,就交换。

  • 遍历比较第一个元素和未排序部分的每个元素。如果未排序部分的元素比第一个元素小,则交换。

  • 遍历整个数组后,将排序部分递增到下一个索引,并递归地将排序部分的最后一个索引的值与未排序部分的每个值进行比较。在未排序部分的值较小的地方进行交换。

  • 被排序的部分应增加,直到覆盖整个数组,产生一个被排序的数组。

图像表示

insertion_sort

JavaScript实现

function insertionSort(arr){
    //Start from the second element.
    for(let i = 1; i < arr.length;i++){

        //Go through the elements behind it.
        for(let j = i - 1; j > -1; j--){
            
            //value comparison using ascending order.
            if(arr[j + 1] < arr[j]){

                //swap
                [arr[j+1],arr[j]] = [arr[j],arr[j + 1]];

            }
        }
    };

  return arr;
}

console.log(insertionSort([23, 1, 10, 5, 2]));

输出。

[ 1, 2, 5, 10, 23 ]

插入式排序有以下性能情况。

  • 最坏情况下的时间复杂度。大O(n^2)。

  • 平均情况下的时间复杂度。大theta (n^2)。

  • 最佳情况下的时间复杂度。大欧米茄(n)。

  • 空间复杂度。大O(1)。

选择排序

选择排序使用递归技术。在下面的指南中,我们使用的是升序。对于降序,你做相反的事情。

分步指南

  • 给定一个数组,假设数组中的第一个元素是最小的。

  • 从数组的其他部分,找到最小值,并将其与第一个元素交换。这时,你已经完成了第一遍。

  • 对数组的其他部分重复同样的程序,比较右边的元素,而不是左边的。

图像表示

selection-sort-algorithm

JavaScript实现

function selectionSort(arr) {
  let min;

  //start passes.
  for (let i = 0; i < arr.length; i++) {
    //index of the smallest element to be the ith element.
    min = i;

    //Check through the rest of the array for a lesser element
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[j] < arr[min]) {
        min = j;
      }
    }

    //compare the indexes
    if (min !== i) {
      //swap
      [arr[i], arr[min]] = [arr[min], arr[i]];
    }
  }

  return arr;
}

console.log(selectionSort([29, 72, 98, 13, 87, 66, 52, 51, 36]));

输出

[ 13, 29, 36, 51, 52, 66, 72, 87, 98 ]

选择排序有以下性能情况。

  • 最坏情况下的时间复杂度。大O(n^2)。

  • 平均情况下的时间复杂度。大theta (n^2)。

  • 最佳情况下的时间复杂度。大欧米茄(n)。

  • 空间复杂度。大O(1)。

合并排序

合并排序使用分而治之的技术。合并排序的主要概念是对一个长度为1的数组进行排序。因此,任务在于将数组分割成大小为1的子数组,然后适当地合并它们,这样就得到了排序后的数组。

分步指南

  • 将数组元素分割成单个元素。

  • 比较各个元素并按顺序排列。这样就得到了长度为1或2的子数组。

  • 做一个空数组。

  • 比较子数组中的元素,将其中较小的值推到空数组中。

  • 如果你已经从其中一个数组中提取了所有的值,就把剩下的数组推到新的数组中。

  • 继续下去,直到所有的子数组都被覆盖,你有一个排序的数组。

图像表示

merge_sort

JavaScript实现

//merging two arrays appropriately.
function merge(arr1, arr2) {
  //make a new array and have two value pointers
  let res = [],
    i = 0,
    j = 0;

  //sorting the first array.
  if (arr1.length > 1) {
    let min = 0;
    for (let i = 0; i < arr1.length; i++) {
      if (i !== min) {
        if (arr1[i] < arr1[min]) {
          //also swap the elements
          [arr1[i], arr1[min]] = [arr1[min], arr1[i]];
          //change the minimum
          min = i;
        }
      }
    }
  }

  //sorting the second array.
  if (arr2.length > 1) {
    let min = 0;
    for (let i = 0; i < arr2.length; i++) {
      if (i !== min) {
        if (arr2[i] < arr2[min]) {
          //also swap the elements
          [arr2[i], arr2[min]] = [arr2[min], arr2[i]];
          //change the minimum
          min = i;
        }
      }
    }
  }

  //Value comparison.
  while (i < arr1.length && j < arr2.length) {
    if (arr1[i] < arr2[j]) {
      res.push(arr1[i]);
      i++;
    } else {
      res.push(arr2[j]);
      j++;
    }
  }

  //pushing the rest of arr1.
  while (i < arr1.length) {
    res.push(arr1[i]);
    i++;
  }

  //pushing the rest of arr2.
  while (j < arr2.length) {
    res.push(arr2[j]);
    j++;
  }

  return res;
}

//merge sort
function mergeSort(arr) {
  //Best case
  if (arr.length <= 1) return arr;

  //splitting into halves
  let mid = Math.ceil(arr.length / 2);

  let arr1 = arr.slice(0, mid);

  let arr2 = arr.slice(mid);

  let arr1_subarrays = [],
    sorted_arr1_subarrays = [];

  let arr2_subarrays = [],
    sorted_arr2_subarrays = [];

  //loop through array 1 making subarrays of two elements
  for (let i = 0; i < arr1.length; i += 2) {
    arr1_subarrays.push(arr1.slice(i, i + 2));
  }

  //loop through array 2 making subarrays of two elements.
  for (let i = 0; i < arr2.length; i += 2) {
    arr2_subarrays.push(arr2.slice(i, i + 2));
  }

  // sorting each subarray of arr1.
  for (let i = 0; i < arr1_subarrays.length; i += 2) {
    let result = merge(arr1_subarrays[i], arr1_subarrays[i + 1]);
    result.forEach((value) => sorted_arr1_subarrays.push(value));
  }

  // sorting each subarray of arr2.
  for (let i = 0; i < arr2_subarrays.length; i += 2) {
    let result = merge(arr2_subarrays[i], arr2_subarrays[i + 1]);
    result.forEach((value) => sorted_arr2_subarrays.push(value));
  }

  let result = merge(sorted_arr1_subarrays, sorted_arr2_subarrays);

  return result;
}

console.log(mergeSort([70, 50, 30, 10, 20, 40, 60]));

输出。

[ 10, 20, 30, 40, 50, 60, 70 ]

合并排序有以下性能情况。

  • 最坏情况下的时间复杂度。大O(n * log n)。

  • 平均情况下的时间复杂度。大theta (n * log n)。

  • 最佳情况下的时间复杂度。大欧米茄(n * log n)。

  • 空间复杂度。大O(n)。

Quicksort

Quicksort也应用了分而治之的技术。它的工作原理是有一个枢轴元素,使它左边的元素小于它,右边的元素大于它。枢轴元素可以是数组中的任何元素。

分步指南

  • 选择一个支点元素。

  • 将数组分成两个数组,左边是小于支点元素的,右边是大于支点元素的。

  • 递归地进行上述步骤,直到我们有长度为1的子数组。合并子数组,得到一个排序的数组。

图像表示

quick-sort

JavaScript实现

function partition(items, left, right) {
  //rem that left and right are pointers.

  let pivot = items[Math.floor((right + left) / 2)],
    i = left, //left pointer
    j = right; //right pointer

  while (i <= j) {
    //increment left pointer if the value is less than the pivot
    while (items[i] < pivot) {
      i++;
    }

    //decrement right pointer if the value is more than the pivot
    while (items[j] > pivot) {
      j--;
    }

    //else we swap.
    if (i <= j) {
      [items[i], items[j]] = [items[j], items[i]];
      i++;
      j--;
    }
  }

  //return the left pointer
  return i;
}

function quickSort(items, left, right) {
  let index;

  if (items.length > 1) {
    index = partition(items, left, right); //get the left pointer returned

    if (left < index - 1) {
      //more elements on the left side
      quickSort(items, left, index - 1);
    }

    if (index < right) {
      //more elements on the right side
      quickSort(items, index, right);
    }
  }

  return items; //return the sorted array
}

let items = [5, 3, 7, 6, 2, 9];

console.log(quickSort(items, 0, items.length - 1));

输出。

[ 2, 3, 5, 6, 7, 9 ]

在实现quicksort时要遵循以下步骤。

  • 开始时,左边的指针指向索引0,右边的指针指向索引0。

  • 将左边指针上的元素与枢轴元素进行比较。如果它小于,则增加左边的指针。递归地这样做,直到指针处的值不小于枢轴。在这一点上,停止迭代。

  • 将右边指针处的元素与枢轴元素进行比较。如果它大于,则递减右边的指针。递归地这样做,直到指针上的值不大于支点。在这一点上,停止迭代。

  • 当你停止了左边和右边指针的迭代后,交换被右边和左边指针所指向的值。

  • 递增左右指针。在这一点上,进一步的增量将导致左指针大于右指针,这将打破返回左指针的循环。

  • 递归地进行上述步骤,直到整个数组被排序。

Quicksort有以下性能情况。

  • 最坏情况下的时间复杂度。大O(n^2)。

  • 平均情况下的时间复杂度。大theta(n * log n)。

  • 最佳情况下的时间复杂度。大欧米茄(n * log n)。

  • 空间复杂度。大O(n * log n)。

主要启示

  • JavaScript默认使用插入式排序的sort() 方法。这意味着在对大型数据集进行排序时,它并不合适。当处理大型数据集时,应该考虑其他的排序算法,如合并排序。

  • 一般来说,基于划分和征服的排序算法要比基于递归的排序算法快。

总结

通过对排序算法的了解,软件开发者可以根据数据集、所需时间和可用空间,找出适当的排序算法。

在这篇文章中,我们涵盖了对排序和排序算法的介绍、排序算法的分类、排序算法的效率,并讨论了常用的排序算法。