Javascript的sort()如何工作?


94

以下代码如何将该数组按数字顺序排序?

var array=[25, 8, 7, 41]

array.sort(function(a,b){
  return a - b
})

我知道如果计算结果是...

小于0:“ a”被排序为比“ b”低的索引。
零: “ a”和“ b”被视为相等,并且不执行排序。
大于0: “ b”被排序为比“ a”低的索引。

在排序过程中是否多次调用了数组排序回调函数?

如果是这样,我想知道每次将两个数字传递给函数。我假设它首先使用“ 25”(a)和“ 8”(b),然后是“ 7”(a)和“ 41”(b),所以:

25(a)-8(b)= 17(大于零,因此将“ b”排序为比“ a”低的索引):8、25

7(a)-41(b)= -34(小于零,因此将“ a”排序为比“ b”低的索引:7、41

然后如何将两组数字彼此相对排序?

请帮助陷入困境的新手!


5
我希望这有一定的道理!
cw84

Answers:


51

在排序过程中是否多次调用了数组排序回调函数?

如果是这样,我想知道每次将两个数字传递给函数

您可以通过以下方式了解自己:

array.sort((a,b) => {
  console.log(`comparing ${a},${b}`);
  return a > b ? 1
               : a === b ? 0 
                         : -1;
});

编辑

这是我得到的输出:

25,8
25,7
8,7
25,41

8
宁可使用console.log来调试或调试HTML DIV元素的.innerHTML + =“比较” + a +“,” + b +“ \ n”;
约书亚

2
请记住,这是一个类似Wiki的网站,我们可以编辑其他答案以使它们变得更好:)
Pablo Fernandez

7
只是有关新ES6语法的注释:array.sort((a,b) => a - b);是有效的语法
Sterling Archer

1
如果sort函数返回-ve,则它交换,而+ ve,则不交换?
马希

2
@ShekharReddy,您仍然可以使用运算符进行比较。我已经更新了答案。
OscarRyz

44

JavaScript解释器内置了某种排序算法实现。它在排序操作中多次调用比较函数。比较函数被调用的次数取决于特定的算法,要排序的数据以及排序之前的顺序。

某些排序算法在已排序列表上的执行效果较差,因为它使它们进行的比较比典型情况多得多。其他人则可以很好地应对预先排序的列表,但在其他情况下,它们可能会“被诱使”以表现不佳。

由于没有一种单独的算法可以完美地满足所有目的,因此有许多常用的排序算法。最常用于常规排序的两个是Quicksortmerge sort。快速排序通常是两者中比较快的一种,但是合并排序具有一些不错的属性,可以使其成为更好的整体选择。合并排序是稳定的,而Quicksort则不是。两种算法都是可并行化的,但是在其他所有条件相同的情况下,合并排序的工作方式会使并行实现更加高效。

您的特定JavaScript解释器可能会使用其中一种算法或完全使用其他某种算法。在ECMAScript的标准没有规定的算法一个符合标准的实现必须使用。它甚至明确拒绝了对稳定性的需求。


1
FWIW,基本的Quicksort是可被“诱使”表现不佳的算法之一。以简单的形式,它对已排序或​​完全相反的列表具有O(N ^ 2)性能。大多数库Quicksort算法都有许多非显而易见的优化,可帮助避免这些常见的最坏情况。
Adisak

3
JavaScriptCore实际上使用AVL树进行排序,因为有必要在比较器函数面前进行确定性行为,以修改要排序的数组。
olliej

ECMAScript标准现在需要稳定性
ggorlen

11

比较一对值,一次一对。比较的对是实现细节-不要假设它们在每个浏览器上都是相同的。回调可以是任何东西(因此,您可以对字符串或罗马数字进行排序,也可以对其他可以返回1,0,-1的函数进行排序)。

JavaScript的排序要记住的一件事是,不能保证它是稳定的。


5

在排序过程中是否多次调用了数组排序回调函数?

是的,就是这样。回调用于根据需要比较数组中的元素对,以确定它们应采用的顺序。在处理数字排序时,比较函数的实现不是典型的。在详细的规范一些其他更具有可读性的网站。


4

在排序过程中是否多次调用了数组排序回调函数?

由于这是一个比较排序,因此给定N个项目,对于像Quicksort这样的快速排序,应平均调用(N * Lg N)次回调函数。如果使用的算法类似于冒泡排序,则回调函数将平均调用(N * N)次。

比较排序的最小调用次数为(N-1),仅用于检测已排序的列表(即,如果不发生交换,则在气泡排序中尽早)。


3

知识渊博

如果结果是否定的,则a排在b之前。

如果结果为正,则b在a之前排序。

如果结果为0,则不会更改两个值的排序顺序。

注意:

此代码是逐步排序方法内部的视图

输出:

let arr = [90, 1, 20, 14, 3, 55];
var sortRes = [];
var copy = arr.slice();		//create duplicate array
var inc = 0;	//inc meant increment
copy.sort((a, b) => {
	sortRes[inc] = [ a, b, a-b ];
	inc += 1;
	return a - b;
});
var p = 0;
for (var i = 0; i < inc; i++) {
	copy = arr.slice();
	copy.sort((a, b) => {
		p += 1;
		if (p <= i ) {
			return a - b;
		}
		else{
			return false;
		}
	});
	p = 0;
	console.log(copy +' \t a: '+ sortRes[i][0] +' \tb: '+ sortRes[i][1] +'\tTotal: '+ sortRes[i][2]);
}


0

运行此代码。您可以从头到尾看到确切的分步排序过程。

var array=[25, 8, 7, 41]
var count = 1;
array.sort( (a,b) => { 
console.log(`${count++}). a: ${a} | b: ${b}`);
return a-b;
});
console.log(array);

复制并粘贴到控制台,它只是返回未定义。
iPzard

0

为了帮助弄清Array#sort其及其比较器的行为,请考虑在入门编程课程中教授的这种天真的插入类型

const sort = arr => {
  for (let i = 1; i < arr.length; i++) {
    for (let j = i; j && arr[j-1] > arr[j]; j--) {
      [arr[j], arr[j-1]] = [arr[j-1], arr[j]];
    }
  }
};

const array = [3, 0, 4, 5, 2, 2, 2, 1, 2, 2, 0];
sort(array);
console.log("" + array);

忽略选择插入排序作为算法,将重点放在硬编码的比较器上:arr[j-1] > arr[j]。这有两个与讨论有关的问题:

  1. >操作符被调用上对数组元素,但你可能要进行排序,例如对象不应对很多事情的>一种合理的方式(如果我们使用了相同的情况是真实的-)。
  2. 即使您正在处理数字,通常情况下,您也需要其他安排,而不是这里升序排列的升序排序。

我们可以通过添加comparefn您熟悉的参数来解决这些问题:

const sort = (arr, comparefn) => {
  for (let i = 1; i < arr.length; i++) {
    for (let j = i; j && comparefn(arr[j-1], arr[j]) > 0; j--) {
      [arr[j], arr[j-1]] = [arr[j-1], arr[j]];
    }
  }
};

const array = [3, 0, 4, 5, 2, 2, 2, 1, 2, 2, 0];
sort(array, (a, b) => a - b);
console.log("" + array);

sort(array, (a, b) => b - a);
console.log("" + array);

const objArray = [{id: "c"}, {id: "a"}, {id: "d"}, {id: "b"}];
sort(objArray, (a, b) => a.id.localeCompare(b.id));
console.log(JSON.stringify(objArray, null, 2));

现在,朴素的排序例程已通用化。您可以确切看到何时调用此回调,从而回答了您的第一组问题:

在排序过程中是否多次调用了数组排序回调函数?如果是这样,我想知道每次将两个数字传递给函数

运行下面的代码表明,是的,该函数已调用多次,您可以console.log用来查看传入的数字:

const sort = (arr, comparefn) => {
  for (let i = 1; i < arr.length; i++) {
    for (let j = i; j && comparefn(arr[j-1], arr[j]) > 0; j--) {
      [arr[j], arr[j-1]] = [arr[j-1], arr[j]];
    }
  }
};

console.log("on our version:");
const array = [3, 0, 4, 5];
sort(array, (a, b) => console.log(a, b) || (a - b));
console.log("" + array);

console.log("on the builtin:");
console.log("" + 
  [3, 0, 4, 5].sort((a, b) => console.log(a, b) || (a - b))
);

你问:

然后如何将两组数字彼此相对排序?

为了精确,术语,ab不是数字-数组中不着痕迹的对象(在你的榜样,他们的数字)。

事实是,它并不重要如何他们排序,因为它的实现依赖。如果我使用与插入排序不同的排序算法,则比较器可能会在不同的数字对上被调用,但是在排序调用结束时,对JS程序员而言重要的不变式是结果数组根据比较器,假设比较器返回的值符合您指定的合同(<0时a < b,0时a === b和> 0时a > b)。

从同样的意义上讲,只要我不违反规范,我就可以自由更改排序的实现,ECMAScript的实现可以在语言规范的范围内自由选择排序实现,因此Array#sort可能会产生不同的比较器调用在不同的引擎上。如果逻辑依赖于某些特定的比较序列,则不会编写代码(比较器首先也不应该产生副作用)。

例如,当数组大于某个预先计算的元素数量时,V8引擎(在编写本文时)会调用Timsort,并对小的数组块使用二进制插入排序。但是,它曾经使用quicksort不稳定的,可能会给比较器提供一系列不同的参数和调用。

由于不同的排序实现以不同的方式使用比较器函数的返回值,因此当比较器不遵守合同时,这可能导致令人惊讶的行为。有关示例,请参见此线程


-2
var array=[25, 8, 7, 41]

array.sort(function(a,b){
  console.log(`a = ${a} , b = ${b}`);
  return a - b
});

输出值

  • a = 8,b = 25
  • a = 7,b = 8
  • a = 41,b = 7
  • a = 41,b = 8
  • a = 41,b = 25

在我的浏览器(Google Chrome版本70.0.3538.77(正式版本)(64位))中,第一次迭代中,参数a是数组中的Second元素,参数b是数组中的First元素

如果比较函数返回

  1. b的负值将移至a。
  2. 正值比a向前移动到b。
  3. 0(零),a和b都将保持原样。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.