【问题标题】:Is sorting or inserting more efficient with joining arrays of objects in javascript?在 javascript 中加入对象数组是否更有效地进行排序或插入?
【发布时间】:2015-05-01 04:52:08
【问题描述】:

我有多个对象,其中包含一个名为“num”的字段。 Num 可以是 1000000000 到 10000000005 之间的任何数字。我想确保如果我有 x 个列表,则所有列表都需要根据“num”属性按升序排列在 array1 中。

如果我从这样的数组开始"

array1": [{item:23532532, num:1000000520},{item:23523, num:1000000620},{item:346346432, num:1000000620}]

我有第二个数组

"array2": [{item:23532, num:....},{item:3623, num:....}]

假设array2按“num”排序,是否更有效:

1) Add Then Sort Whole - 循环遍历“array2”中的每个项目并将其添加到“array1”的末尾,然后对整个数组的“num”属性执行内置的“排序”函数?

2) Insert Into Right Place - 循环遍历“array2”中的每一项并使用“if”条件检查“num”值是否大于“array2”中的当前项,如果是,则插入该索引之前的元素通过“拼接”。 (未使用 javascript 内置数组排序)

或者有没有更有效的方法?伪代码或示例代码是一个加号。

【问题讨论】:

  • 示例中 num 的值(例如 1000000520)似乎超出了指定范围(1000000000 到 1000000005)。
  • 此外,您在#2 中描述的 一种:) en.wikipedia.org/wiki/Insertion_sort
  • splice 可能效率很低。我会先使用简单的解决方案(concat + sort),然后如果这还不够好,请使用 sort(small) + merge
  • 我将使用 splice(或 concat)连接两个数组,然后对整个数组进行排序。但是对jsperf 进行几分钟的测试会回答你的问题。我希望拼接单个成员只有在您不时添加一些成员时才有意义。
  • @RobG:您的意思可能是pushconcat,而不是splice

标签: javascript arrays sorting


【解决方案1】:

我在三种不同的浏览器中测量了三种不同算法的结果。

关于所有与性能相关的问题,有两点是正确的:

  1. 如果您真的想知道答案,您必须在多个浏览器中测试您的特定算法才能真正回答问题。

  2. 许多与性能相关的问题实际上并不重要在使用它们的给定上下文中,因此在您知道需要担心它们之前担心它们只不过是浪费时间关于过早优化甚至不必要的优化。因此,在处理特定的性能领域之前,您应该知道它很重要,并且值得花时间在上面。

也就是说,这里是三种算法的一些测量值。这假设您从两个对象数组开始,每个对象都按每个对象中存在的一个特定数字属性独立排序。

这是 jsperf:http://jsperf.com/concat-sort-vs-insert-sort/5,其中包含三种算法中的每一种算法的代码。

算法1是拼接,然后对拼接后的数组进行排序。在JS中,无非就是:

var result = arr1.concat(arr2);
result.sort(sortByNum);

算法 2 是一种插入排序的尝试。 基本思想是遍历第二个数组,并针对该数组中的每个项目,找到将其插入第一个数组的位置。由于两个数组都已排序,因此我们只需在插入最后一项之后的位置开始寻找将下一项插入第一个数组的位置。

算法 3 是一种归并排序。这里的想法是创建一个空结果数组和两个索引,一个用于两个源数组中的每一个。对于每个源索引处的值,您将两项中较低的一项推入结果中,然后增加其源索引。当任一源索引用尽时,您将推入另一个数组的其余部分。我猜它会比插入排序更有效,因为它不必将项目插入到数组的中间,只需添加到末尾,这可能是一个更快的操作。


为了运行测试,我创建了两个数组,每个数组包含 100 个对象。每个对象都有一个数字属性,该属性分配了一个介于 0 和 100,000 之间的随机数。然后对两个源数组中的每一个进行预排序。然后在这两个源数组上测试每个算法。

而且,结果如下:

这是归并排序算法的代码:

function mergeSort(arr1, arr2) {
    var result = [];
    var index1 = 0;
    var index2 = 0;
    if (!arr1.length) {
        return arr2.slice(0);
    } else if (!arr2.length) {
        return arr1.slice(0);
    }
    while (true) {
        if (arr1[index1].num <= arr2[index2].num) {
            result.push(arr1[index1]);
            ++index1;
            // see if we reached the end of the array
            if (index1 >= arr1.length) {
                result.push.apply(result, arr2.slice(index2));
                break;
            }
        } else {
            result.push(arr2[index2]);
            ++index2;
            // see if we reached the end of the array
            if (index2 >= arr2.length) {
                result.push.apply(result, arr1.slice(index1));
                break;
            }
        }
    }
    return result;
}

工作演示:http://jsfiddle.net/jfriend00/mja1c13d/

【讨论】:

  • 我在 insertionSort() 方法的 jsPerf 测试中发现了一个缺陷(它不是每次都从 arr1 的新副本开始)。该缺陷已得到修复,结果已更新。
  • 我通常看到的合并排序称为合并排序,如果两个源数组都已排序,则绝对是赢家。 OP对此并不清楚(仍在等待那里的答案)。很好的示范。
  • 这不是所谓的“组合排序”,而是一个简单的merge 步骤。
  • 请注意,您的 jsperf 已损坏,因为它在 sortByNum 中有拼写错误。 I've fixed it 还发布了version with different array lengths
  • @Bergi - 我编辑了 jsperf 以修复拼写错误并更新了结果图以显示新结果。我还没有弄清楚的一件事是如何验证您的 jsperf 实际上正在执行您期望它执行的操作。您无法分析/测试结果或影响时间。我使用特殊的测试代码验证了结果,以在单独的 jsFiddle 中分析结果,但随后在将其转移到 jsperf 时出现了拼写错误。还将术语更改为mergeSort()
【解决方案2】:

大多数情况下,内置排序可能比编写自己的插入排序要高效得多。如果不是这样,那么将编写本机 Array.sort() 来自行执行此操作。

但是,您可能会看到其中一个例外,具体取决于两个数组的大小。如果您知道两个数组中的一个已经排序,并且另一个(未排序的)数组很短,并且两个数组的总大小很大,那么遍历这个短数组并插入到大的、已排序的数组中可能是更高效。

插入(假设您知道一个列表已经排序)将按照N1 * N2 的顺序进行;连接和排序可能类似于(N1 + N2) log (N1 + N2)。根据这两个相对大小,它可以是任何一种方式。

但除非您的列表非常大,否则差异将低于人类可察觉的阈值,因此代码可维护性主张“连接和排序”。实际的真实性能测量总是胜过猜测,并且性能非常受数据细节的影响 - 因此,如果您真的担心,请编写两者并使用真实数据对其进行测试。

【讨论】:

  • +1 尤其是最后一段,怎么强调都不够
  • 插入一个大的排序数组可能只有在保留索引的情况下更快和数组。否则,我希望内置排序更快。但是当然,只有测试才能证明这一点(并且在不同的浏览器中可能会有所不同,并且取决于数组的大小)。
  • @RobG - 基本同意。如果排序后的数组很大并且要插入的项目数量很少,我认为它可能会比重新排序整个列表更好......取决于Array.sort() 的特定本机实现是否是一个受益于工作的一个主要排序的列表。这属于“未经测试基本上不可知”的类别,这实际上只是让我们回到“如果你真的需要知道,请在你的实际条件下测试它。”
  • @SMcCrohan——我认为排序算法已经被彻底完成,因此得到了难以置信的优化。对大部分排序的列表进行排序应该非常快,因此几乎不值得努力找出索引插入是否更快。有趣的话题要跟进。我似乎记得树索引(在早期的 GIS 时代对四叉树做了一些工作)需要大量维护工作,因此只有在它们提供切实的好处时才有用(它们对大型地理数据集有巨大的好处)。
【解决方案3】:

虽然我认为这不会与您的数据完全匹配,因此可能对您没有用处,但值得一提的是 counting sort 在线性时间内执行。如果您事先知道需要对数据中没有间隙的整数进行排序,以便 1000000000 和 10000000005 之间的每个整数都存在一个对象,并且所有整数都是唯一的,那么您可以通过数学计算来利用这一点,将 1000000000 减少到零位置, 1000000001 到位置 1,等等...您可以通过从每个数字中减去 1000000000 来获得从零开始的数组中的索引。

你最终会得到类似的东西

var array = new Array(9000000005); //10000000005 - 1000000000
var obj = {"item":23532532, "num":1000000520};
var array[1000000520 - 1000000000] = obj; //goes into index 520

减法运算使您的线性时间复杂度达到 2n 而不仅仅是 n。内置的 Javascript 排序可能是 n log n 时间复杂度。在 x-y 坐标图上从 0 到 100,n log n 更好,但在 x > 100 时,2n 的性能会有所提高。对于 90 亿个项目,尽管这实际上对于 Javascript 来说是不切实际的,但您可以看到随着时间的推移,差异将得到显着改善。

很难捕捉到这张图的比例,但这给出了比较的好主意...y=x 表示计数排序,y=2x 表示带有减法运算的计数排序。 y = x log x 可能代表内置的 Javascript 数组排序。

【讨论】:

  • 计数排序是一个有价值的答案,但是您对复杂性类的解释没有添加任何有用的东西。顺便说一句,O(2n) == O(n)
  • @Bergi 我更新了图表以删除大 O 表示法。
  • 样本数据有重复的数字,但我不知道这是不是故意的。无论如何,这是一个很好的记录技术。
猜你喜欢
  • 1970-01-01
  • 2019-07-15
  • 1970-01-01
  • 1970-01-01
  • 2016-05-05
  • 1970-01-01
  • 2015-10-30
  • 1970-01-01
相关资源
最近更新 更多