为什么要在数组上使用排序映射。在某些情况下如何更好

时间:2013-01-24 19:49:53

标签: javascript

我正在尝试了解数组排序。看起来很简单。但是在mozilla网站上,我遇到了section discussing sorting maps(大约四分之三的页面)。

  

compareFunction可以在每个元素中多次调用   数组。根据compareFunction的性质,这可能会产生一个   高开销。 compareFunction做的工作越多,越多   要排序的元素,可以考虑使用a更明智   地图进行排序。

给出的例子是:

// the array to be sorted
var list = ["Delta", "alpha", "CHARLIE", "bravo"];
// temporary holder of position and sort-value
var map = [];
// container for the resulting order
var result = [];

// walk original array to map values and positions
for (var i=0, length = list.length; i < length; i++) {
  map.push({    
    // remember the index within the original array
    index: i, 
    // evaluate the value to sort
    value: list[i].toLowerCase()
  });
}

// sorting the map containing the reduced values
map.sort(function(a, b) {
  return a.value > b.value ? 1 : -1;
});

// copy values in right order
for (var i=0, length = map.length; i < length; i++) {
  result.push(list[map[i].index]);
}

// print sorted list
print(result);

我不明白一些事情。也就是说:这是什么意思,“compareFunction可以在数组中的每个元素多次调用”?有人能告诉我一个例子。其次,我理解在示例中正在做什么,但我不理解compareFunction的潜在“高[er]开销”。这里显示的示例看起来非常简单,将数组映射到一个对象,对其值进行排序,然后将其放回到一个数组中,我认为乍看之下会花费更多的开销。我理解这是一个简单的例子,除了显示程序之外,可能不是用于其他任何事情。但有人可以给出一个例子,说明这样的地图开销会降低吗?这似乎还有很多工作要做。

谢谢!

5 个答案:

答案 0 :(得分:3)

通过避免在比较函数中调用toLowerCase()来获得该示例中的主要时间节省。每次需要比较一对元素时,排序代码会调用比较函数,这样可以节省大量函数调用。构建和取消构建地图的成本对于大型阵列来说是值得的。

每个元素可以多次调用比较函数是排序工作的自然含义。如果每个元素只需要进行一次比较,那将是一个线性时间过程。

编辑 - 将要进行的比较次数大致与数组的长度乘以长度的base-2 log相对应。然后,对于1000个元素的数组,这与10,000个比较成比例(可能接近15,000,具体取决于实际的排序算法)。保存20,000个不必要的函数调用值得构建和取消构建排序映射所需的2000个操作。

答案 1 :(得分:3)

对列表进行排序时,项目不仅与其他项目进行比较,还可能需要将其与其他几项进行比较。有些项目甚至可能需要与所有其他项目进行比较。

让我们看看排序数组时实际有多少比较:

var list = ["Delta", "alpha", "CHARLIE", "bravo", "orch", "worm", "tower"];

var o = [];
for (var i = 0; i < list.length; i++) {
    o.push({
        value: list[i],
        cnt: 0
    });
}

o.sort(function(x, y){
    x.cnt++;
    y.cnt++;
    return x.value == y.value ? 0 : x.value < y.value ? -1 : 1;
});

console.log(o);

结果:

[
 { value="CHARLIE",  cnt=3},
 { value="Delta",  cnt=3},
 { value="alpha",  cnt=4},
 { value="bravo",  cnt=3},
 { value="orch",  cnt=3},
 { value="tower",  cnt=7},
 { value="worm",  cnt=3}
]

(小提琴:http://jsfiddle.net/Guffa/hC6rV/

如您所见,每个项目都与其他项目进行了比较。字符串"tower"甚至比其他字符串有更多的比较,这意味着它至少与另一个字符串进行了比较至少两次。

如果比较需要进行一些计算才能比较值(如示例中的toLowerCase方法),那么该计算将进行多次。通过在计算之后缓存值,每个项目只会执行一次。

答案 2 :(得分:2)

这称为“decorate - sort - undecorate”模式(你可以在Wikipedia找到一个很好的解释)。

这个想法是基于比较的排序必须至少调用比较函数n次(其中n是列表中的项目数),因为这是你比较的数量只需要检查数组是否已经排序。通常,比较的数量将大于(O(n ln n),如果您使用的是良好的算法),并且根据针脚原则,至少有一个值将被传递两次到比较函数。< / p>

如果您的比较函数在比较这两个值之前进行了一些昂贵的处理,那么您可以通过首先执行昂贵的部分并存储每个值的结果来降低成本(因为您知道即使在最佳情况下您也会有做那个处理)。然后,在排序时,使用更便宜的比较函数,只比较那些缓存的输出。

在此示例中,“昂贵”部分将字符串转换为小写。

答案 3 :(得分:1)

将此视为缓存。它只是说你不应该在比较函数中做很多计算,因为你将反复计算相同的值。

  

这是什么意思,“在数组中每个元素可以多次调用compareFunction”?

这正是它所说的。允许您有三个项目,A,B和C.它们需要按比较函数的结果进行排序。比较可以这样做:

compare(A) to compare(B)
compare(A) to compare(C)
compare(B) to compare(C)

所以在这里,我们有3个值,但compare()函数执行了6次。使用临时数组来缓存事物可确保我们每个项目只进行一次计算,并可以比较这些结果。

  

其次,我理解示例中正在做什么,但我不理解compareFunction潜在的“高[er]开销”。

如果compare()执行数据库提取(比较匹配行的计数),该怎么办?或者是一个复杂的数学计算(阶乘,递归的fibbinocci,或对大量项目的迭代)这些类型的东西你不想做多次。

我想说大部分时间都可以保留简单/快速的内联计算。不要过度优化。但是如果你需要在比较中做任何复杂或缓慢的事情,你必须更加聪明。

答案 4 :(得分:0)

要回答您的第一个问题,为什么数组中每个元素会多次调用compareFunction

对数组进行排序几乎总是需要N次以上,其中N是数组的大小(除非数组已经排序)。因此,对于数组中的每个元素,它可以与数组中的另一个元素进行比较,最多N次(冒泡排序最多需要N ^ 2次比较)。每次都会使用您提供的compareFunction来确定两个元素是否小于/等于/大,因此将在数组中每个元素调用多次。

对您的第二个问题的简单回答,为什么compareFunction可能会有更高的开销?

在比较数组的两个元素时,说你的compareFunction做了很多不必要的工作。这可能导致排序变慢,因此使用compareFunction可能会导致更高的开销。