【问题标题】:Fastest way to find duplicates between two arrays javascript在两个数组javascript之间查找重复项的最快方法
【发布时间】:2015-12-13 17:43:00
【问题描述】:

有很多关于在两个数组中查找重复项的最简单方法的帖子,但是绝对最快的方法是什么?有没有办法避免使用两个 for 循环并将函数从 O(n^2) 时间到 O(n) 时间?我拥有的数组每个包含约 1000 个项目。在运行函数之前,我会检查哪个数组更长,并将该数组用作toCheckAgainst 变量。

var containingBoth = [];
function checkArrays(toCheck, toCheckAgainst){
 for(var i=0;i<toCheck.length;i+=1){
  for(var j=0;j<toCheckAgainst.length;j+=1){
    if (toCheck[i] === toCheckAgainst[j]) {
      containingBoth.push(toCheck[i]);
    }
   }
  }
 }

【问题讨论】:

  • 数组只包含基元还是对象?如果只是原语,我相信这样的东西可以工作:jsfiddle.net/xm3w1d31。我相信它遵循O(n)
  • @Niles 在您的解决方案中,如果 toCheck Length 为 10 并且 checkAgainst len 为 5,那么您的 for 循环将运行 60 次,这在数组很大的情况下效率不高
  • @Ian 如果我运行你的小提琴它返回的空数组即使你声明的数组中有重复值
  • @Amit.S 不,它没有 - 它返回一个 [2, 3] 数组。你检查你的浏览器控制台了吗?你在什么浏览器上测试?
  • @Ian 对不起,伙计,我将 console.log 更改为 alert,但没有将括号中的“,”更改为“+”...我的错

标签: javascript arrays duplicates


【解决方案1】:

这可能是实现此目的的另一种方法。您当前的解决方案在两个数组上都有循环,并且在最长的数组上循环,这会减慢大型数组中的过程,例如,如果一个最长的数组有 1000 个 val,而较短的数组有只有 2 在这种情况下,您要循环 1000 个值以找出在这种情况下不能超过 2 的欺骗。

下面的解决方案循环仅在比另一个数组短的数组上,因为我们只对欺骗感兴趣,即使两个数组的长度相同,下面的解决方案也会更快,因为在您的解决方案中,您在两个数组上循环,而下面代码只循环一个。

在比较 Niles 代码和我的代码中的速度差异进行了一些测试,请参阅此处的结果 Niles Codemy code,每个 1000 个值的数组的速度大约快 50%

var arr1 = ["Test1", "test2", "test3","test5","test6","test4"];
var arr2 = ["test1", "test4","test5","test6","test2","Test1"];
var result = [];
(arr1.length>arr2.length?arr2:arr1).forEach(function(key) {
    if (-1 != (arr1.length>arr2.length?arr1:arr2).indexOf(key) && -1 == result.indexOf(key)) 
        result.push(key);
}, this);
alert(result); //duplicate values arr

【讨论】:

  • 这很棒。只是为了我的理解,这是什么 Big-O 复杂性?根据@deamentiaemundi 在下面的回答,它是 O(n log n) 吗?
  • 它是 O(n^2) 与 O(n^2) 比较加上 O(n) 用于在统一结果中的搜索(尚未要求?)。 Array.indexOf() 是线性搜索,因此将较大的数组排序为违反直觉的排序,这会使整个事情变得更快,因为您可以进行 O(log n) 的二进制搜索并将比较次数降低到 O( n log n) 如果您想在两个单独的运行中进行排序和比较。它有一些开销,但它会在更大的数组中获胜,而且您正在谈论的数组 >10k 条目。
【解决方案2】:

编辑添加示例代码 如果两个数组(长度分别为“k”和“l”)已经排序,则可以在 O(n) 中执行此操作。您可以通过合并两个数组(当然,不是在内存中,只是在算法上)来做到这一点,就像在合并排序和 k+l 比较中一样。

如果两个数组都没有排序,你可以在 O(n log n) 中进行(例如,使用上面提到的合并排序)进行 O(n²) 比较,因为你可以在 O 中用蛮力完成你的任务(n²) 已经,在这里对数组进行排序是多余的。

但并非所有集合都是完全未排序的,尤其是当它们很大的时候。如果我们以快速排序为例,您可以预期平均 O(n log n) 与 O(n log n) 比较。但请注意,当集合已经排序时,快速排序的最坏情况是 O(n²)!

如果您的数组已排序且不包含重复项,这是一种非常简单的方法: 获取较小数组的各个条目并在较大数组中对它们进行二进制搜索 -> O(n log n) (二进制搜索是 O(log n) 并且您执行 n 次)。如果保证不包含重复项,则甚至不必对较小的数组进行排序。

总结:你可以比 O(n²) 更快地做到这一点。这取决于输入“更快”的实际速度有多快,但在最佳情况下您可以将其降低到 O(n)(加上一个小常数),平均为 O(n log n)。

'use strict'
var primesieve;
var buffer;
var primelimit;
function clear(where) {
  primesieve[where >>> 5] &= ~((1 << (31 - (where & 31))));
}
function get(where) {
  return ((primesieve[where >>> 5] >>> ((31 - (where & 31)))) &
  1);
}
function nextset(from) {
  while (from < primelimit && !get(from)) {
    from++;
  }
  if (from === primelimit && !get(from)) {
    return - 1;
  }
  return from;
}
function fillsieve(n) {
  var k,
  r,
  j;
  n = n + 1;
  primelimit = n - 1;
  k = Math.ceil(n / 32);
  if (typeof ArrayBuffer !== 'function') {
    buffer = new ArrayBuffer(k * 4);
  } else {
    buffer = k;
  }
  primesieve = new Uint32Array(buffer);
  while (k--) {
    primesieve[k] = 0xffffffff;
  }
  clear(0);
  clear(1);
  for (k = 4; k < n; k += 2) {
    clear(k);
  }
  r = Math.floor(Math.sqrt(n));
  k = 0;
  while (k < n) {
    k = nextset(k + 1);
    if (k > r || k < 0) {
      break;
    }
    for (j = k * k; j < n; j += 2 * k) {
      clear(j);
    }
  }
}
function approx_limit(prime_pi) {
  if (prime_pi < 10) {
    return 30;
  }
  // see first term of expansion of li(x)-li(2)

  return Math.ceil(prime_pi * (Math.log(prime_pi * Math.log(prime_pi))));
}
function primes(prime) {
  var ret,
  k,
  count,
  i;
  ret = [];
  k = 0;
  i = 0;
  count = prime;
  while (count--) {
    k = nextset(k + 1);
    if (k > primelimit || k < 0) {
      break;
    }
    ret[i++] = k;
  }
  return ret;
}
// very simple binary search
Array.prototype.bsearch = function(needle) {
  var mid, lo = 0;
  var hi = this.length - 1;

  while (lo <= hi) {
    mid = Math.floor((lo + hi) / 2);
    if (this[mid] > needle) {
      hi = mid - 1;
    } else if (this[mid] < needle) {
      lo = mid + 1;
    } else {
      return this[mid];
    }
  }
  // assumes no entry "-1", of course
  return -1;
};

var limit = 10 * 1000;
var a, b, b_sorted, u;
var a_length, b_length, u_length;

fillsieve(approx_limit(limit));

// the first array is filled with primes, sorted and unique
a = primes(limit);

// the second array gets filled with an unsorted amount of
// integers between the limits 0 (zero) and "limit".
b = [];
for(var i = 0;i < limit;i++){
  b[i] = Math.floor( Math.random() * (limit + 1));
}
a_length = a.length;
b_length = b.length;

console.log("Length of array a: " + a_length);
console.log("Length of array b: " + b_length);

var start, stop;
u = [];

// Brute-force
start = performance.now();
for(var i = 0; i < a_length; i++){
  for(var j = 0; j< b_length; j++){
    if(a[i] == b[j] && a[i] != u[u.length - 1]){
      u.push(a[i]);
    }
  }
}
stop = performance.now();
console.log("Brute force = " + (stop - start));
console.log("u-length = " + u.length);
console.log(u.join(","));

u = [];
b_sorted = [];
// work on copy
for(var i = 0; i < b_length; i++) b_sorted[i] = b[i];
var entry;

// Sort the unsorted array first, than do a binary search
start = performance.now();
// ECMA-script's arrays sort() function sorts lexically
b_sorted.sort(function(a,b){return a - b;});
for(var i = 0; i < a_length; i++){
  entry = b_sorted.bsearch(a[i])
  if( entry != -1 && entry != u[u.length - 1]){
    u.push(entry);
  }
}
stop = performance.now();
console.log("Binary search = " + (stop - start));
console.log("u-length = " + u.length);
console.log(u.join(","));

这在我的小型 AMD A8-6600K 上使用蛮力算法大约需要 56 秒,使用二分搜索大约需要 40 毫秒(是的,milli秒!),这个数字甚至包括数组的排序。

【讨论】:

    猜你喜欢
    • 2021-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-24
    • 2012-01-12
    • 2013-11-15
    • 2015-07-06
    • 1970-01-01
    相关资源
    最近更新 更多