js sort中的排序猜想

294 阅读5分钟

在看数组乱序时,看到下面的实现方法不是很理解

场景

场景是用 Math.random 对数组乱序

var values = [1,2,3,4,5,6,7,8]

values.sort(function(){
  return Math.random() - .5
})

console.log(values)

刚看到时,能看懂这样的做法,但并不理解这样做的原理

于是由此场景展开,分析 sort 内部排序的机制

实践

改动内部代码,打印内部参数,修改如下

values.sort(function(first, second){
  var calc = Math.random() - .5
  console.log(calc > 0 ? "+" : calc < 0 ? "-" : "=", first, second) 
  return calc
})
console.log(values)

测试环境是浏览器 chrome

打印结果为

 - 2 1
 + 3 2
 - 3 1
 - 3 2
 - 4 2
 - 4 3
 + 5 2
 - 5 1
 + 6 2
 + 6 1
 - 7 5
 - 7 3
 - 7 4
 - 8 2
 + 8 4
 + 8 3
 [7, 4, 3, 8, 2, 5, 1, 6]

打印结果看的也是一头雾水,不知从哪分析,究其原因,是对数组 sort 方法不够了解。在看过相关文章后,理解了一些。于是做出下面的分析,属于我自己的猜想

数组 sort 方法

Array.prototype.sort()

方法可以接收一个函数

arr.sort(function(first, second){
	return first - second
})

做出猜想

每次传入两个值做比较, first - second 如果大于 0 的话,函数返回一个正值,代表 first 大于 second,将 first 排在后面;小于 0 则返回一个负值,first 排在前面;等于 0 则不改变顺序

最终,数组呈现成升序 。如果内部是 second - first,则整体为倒序

规律:不管内部返回的是 first - second 还是 second - first,只要返回的值是正,在该轮比较中,first 就排在后面, 返回的值是负,first 就排在前面

当然,如何传两个值和怎么比较是 js 引擎的内部机制,我们根据猜想简要分析这一过程

分析

开始分析,原数组序列为 1 2 3 4 5 6 7 8

第一次排序,比较 2 和 1 ,返回值为负,说明是倒序,即 2 在 1 前面,此时 values 暂时排序为 2 1 3 4 5 6 7 8 ,已经确定的排序为 2 1

第二次排序,比较 3 和 2 ,返回值为正,说明是正序,即 3 在 2 后面,此时暂时排序为 2 1 3 ,问题是 1 也在 2 后面,3 也在 2 后面,不能确定 3 和 1 的位置顺序,所以,下一步继续确定 1 和 3 的顺序

第三次排序,比较 3 和 1 ,返回值为负,说明是倒序,即 3 在 1 前面,而 2 也在 1 的前面,三者的顺序并不能确定,所以,下一步继续比较 3 和 2 的位置顺序

第四次排序,比较 3 和 2 ,返回值为负,倒序,3 在 2 前面,之前确定了 2 在 1 的前面,所以三者的顺序确定,即 3 2 1,此时已确定的排序为 3 2 1

第五次排序,确定 4 的位置,即确定 4 插入序列 3 2 1 的哪一位置,在比较之前得先确定 4 和谁比较 。这里选取序列的中间位置值和待比较值比较(中间位置值的选取是 v8 中插入排序的实现,没有进一步研究,这里直接给出),比较 4 和 2 ,负 ,4 在 2 前面,此时也不知道 4 和 3 的位置顺序,所以下一步比较 4 和 3

第六次排序,比较 4 和 3 ,负,4 在 3 前面,4 3 2 1

第七次排序,确定 5 的位置,确定 5 在 4 3 2 1 的插入位置,选取中间位置值 2 和 5 比较,正,5 在 2 后面。暂时排序为 4 3 2 5 1 。此时 1 和 5 都在 2 的后面,所以需要继续确定 5 和 1 的顺序

第八次排序,比较 5 和 1,负 ,确定 5 在 1 的前面,确定排序为 4 3 2 5 1

第九次排序,确定 6 的位置,确定 6 在序列 4 3 2 5 1 插入的位置,选取中间位置值 2 和 6 比较,正,6 在 2 后面。暂时排序为 4 3 2 5 1 6。下一步继续确定 6 的位置

第十次排序,确定 6 的位置,选取 1 和 6 比较,如果比较结果是 6 在 1 的后面,则顺序确定,6 就无需跟 5 进行比较。 比较结果为正,6 在 1 的后面。已确定排序为 4 3 2 5 1 6

第十一次排序,确定 7 的位置,选取中间位置值 5 和 7 比较,负,7 在 5 的前面,所以得继续确定 7 在 4 3 2 中的位置

第十二次排序,继续确定 7 的位置,7 和 中间位置值 3 比较结果为负,7 在 3 的前面,还得比较 7 和 4 的顺序

第十三次排序,继续确定 7 的位置,7 和 4 比较 ,负,7 在 4 的前面。目前排序已确定为 7 4 3 2 5 1 6

第十四次排序,确定 8 的位置,比较 8 和 2 ,负,8 在 2 前面

第十五次排序,继续确定 8 的位置,比较 8 和 4,正,8 在 4 的后面。在 4 的 后面又在 2 的前面,所以剩一个 3 继续比较

第十六次排序,继续确定 8 的位置,比较 8 和 3,正,8 在 3 的后面。排序已确定为 7 4 3 8 2 5 1 6

所以,最终排序为 7 4 3 8 2 5 1 6

最后

到了这里,算是有些理解 sort 内部排序的机制和理解开头场景乱序做法的原理

以上分析,是结合打印的值做出的猜想。

这篇文章并没有深入研究 v8 在 sort 方法中使用的插入排序算法实现

相关文章链接

「前端进阶」数组乱序

最后放上权威的 MDN Array.prototype.sort()