JavaScript Array.prototype.sort用法及底层原理解析

Array.prototype.sort()方法

定义

sort() 方法按照某种规则就地对数组的元素进行排序,并返回对相同数组的引用。(不会产生新数组,但是会更改原数组)

默认排序是将元素转换为字符串,然后按照它们的 UTF-16 码元值升序排序。

由于它取决于具体实现,因此无法保证排序的时间和空间复杂度。

如果想要不改变原数组的排序方法,可以使用 toSorted()。

参数

  • compareFn(可选):定义排序顺序的函数。返回值应该是一个数字,其正负性表示两个元素的相对顺序。该函数使用以下参数调用
    • curItem:当前元素(从第1个元素开始)
    • preItem:上一个元素(从第0个元素开始)

如果省略该函数,数组元素会被转换为字符串,然后根据每个字符的 Unicode 码位值进行排序。

compareFn(curItem, preItem) 返回值排序顺序
> 0不替换顺序
< 0替换顺序 [preItem, curItem] = [curItem, preItem]
=== 0不替换顺序
var arr = [2, 3, 1];
arr.sort((cur, pre) => {
	console.log(cur, pre);
	return cur - pre;
});
console.log(arr); // Array [1, 2, 3]
/*
	3 2
	1 3
	1 3
	1 2
*/

返回值

排序后的数组。

注意数组是就地排序的,不会进行复制。

稀疏数组的处理

sort() 方法保留空槽。如果原数组是稀疏的,则空槽会被移动到数组的末尾,并始终排在所有 undefined 元素的后面。

var arr = [2, 3, , 1];
arr.sort((cur, pre) => {
	console.log(cur, pre);
	return cur - pre;
});
console.log(arr); // Array [1, 2, 3, <1 Empty>]
/*
	3 2
	1 3
	1 3
	1 2
*/

如何保证正确的排序行为

为了确保正确的排序行为,比较函数应具有以下属性:

  • 纯函数:比较函数不会改变被比较的对象或任何外部状态。(这很重要,因为无法保证比较函数将在何时以及如何调用,因此任何特定的调用都不应对外部产生可见的效果。)
  • 稳定性:比较函数对于相同的输入对应始终返回相同的结果。
  • 自反性compareFn(a, a) === 0
  • 反对称性compareFn(a, b)compareFn(b, a) 必须都是 0 或者具有相反的符号。
  • 传递性:如果 compareFn(a, b)compareFn(b, c) 都是正数、零或负数,则 compareFn(a, c) 的符号与前面两个相同。

sort 底层原理

sort 底层采用冒泡排序,时间复杂度为 O(n2)。

冒泡排序的原理:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
function sort(arr, fn) {
  for(let i = 0; i < arr.length - 1; i++) {
    for(let j = 0; j < arr.length - 1 - i; j++) {
      if(fn(arr[j], arr[j + 1]) > 0) {
        const temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
  }
  return arr;
};

V8中的sort

V8 引擎 sort 函数只给出了两种排序 InsertionSort 和 QuickSort,数组长度小于等于 22 的用插入排序 InsertionSort,比22大的数组则使用快速排序 QuickSort。

插入排序InsertionSort

插入排序是指在待排序的元素中,假设前面 n-1(其中 n>=2)个数已经是排好顺序的,现将第n个数插到前面已经排好的序列中,然后找到合适自己的位置,使得插入第n个数的这个序列也是排好顺序的。按照此法对所有元素进行插入,直到整个序列排为有序的过程,称为插入排序

时间复杂度为 O(n2),是稳定的排序。

插入排序的原理:

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置后
  6. 重复步骤2~5
/*
	实现方式:迭代
	arr:原数组
*/
function Insertion(arr) {
  // preIndex:已经排好序的数组的最后一个索引
  // current:当前要插入的数字
  let preIndex, current;
  // 循环,从第二个数开始插入
  for (let i = 1; i < arr.length; i++) {
    preIndex = i - 1;
    current = arr[i];
    // 循环,将当前要插入的数字与已经排好序的数组从后往前进行比较
    while (preIndex >= 0 && current < arr[preIndex]) {
      // 数组元素右移
      arr[preIndex + 1] = arr[preIndex];
      preIndex--;
    }
    // 找到插入位置
    arr[preIndex + 1] = current;
  }
  return arr;
}

快速排序QuickSort

快速排序是对冒泡排序的一种改进。

快速排序:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

时间复杂度为 O(n2),是不稳定的排序。

快速排序算法通过多次比较和交换来实现排序,其排序流程如下:

  1. 首先设定一个分界值,通过该分界值将数组分成左右两部分
  2. 将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值
  3. 然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理
  4. 重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了

概括来说为 挖坑填数 + 分治法。

/*
	实现方式:递归
	arr:原数组
	start:左边界
	end:右边界
*/
function qsort(arr, start, end) {
  // pivot:分界值
  const pivot = arr[start];
  // left:左边界
  let left = start;
  // right:右边界
  let right = end;
  while (left < right) {
    // 遍历右边界,直到找到小于分界值的值
    while (left < right && arr[right] > pivot) right--;
    // 遍历左边界,直到找到大于分界值的值
    while (left < right && arr[left] < pivot) left++;
    // 判断两个值是否相对,不相等则互换
    if (arr[left] === arr[right] && left < right) left++;
    else {
      [arr[left], arr[right]] = [arr[right], arr[left]];
    }
  }
  // 将原数组分为左、右两个数组,进一步递归
  if (left - 1 > start) arr = qsort(arr, start, left - 1);
  if (right + 1 < end) arr = qsort(arr, right + 1, end);
  return arr;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jackson Mseven

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值