判断两个数组是否具有相同成员的算法

时间:2008-10-29 01:33:45

标签: algorithm

比较两个数组以查看它们是否具有相同成员的最佳算法是什么?

假设没有重复项,成员可以按任何顺序排列,并且两者都没有排序。

compare(
    [a, b, c, d],
    [b, a, d, c]
) ==> true

compare(
    [a, b, e],
    [a, b, c]
) ==> false

compare(
    [a, b, c],
    [a, b]
) ==> false

16 个答案:

答案 0 :(得分:17)

明显的答案是:

  1. 对两个列表进行排序,然后检查每个列表 元素,看看它们是否相同
  2. 将一个数组中的项添加到 哈希表,然后迭代 其他数组,检查每个项目 在哈希
  3. nickf的迭代搜索算法
  4. 您使用哪一个取决于您是否可以先对列表进行排序,以及是否有方便的哈希算法。

答案 1 :(得分:7)

您可以将一个加载到哈希表中,跟踪它有多少个元素。然后,循环遍历第二个,检查其每个元素是否都在哈希表中,并计算它有多少个元素。如果第二个数组中的每个元素都在哈希表中,并且两个长度匹配,则它们是相同的,否则它们不相同。这应该是O(N)。

要在存在重复项的情况下使其工作,请跟踪每个元素的数量。在第一个数组上循环时递增,在第二个数组上循环时递减。在第二个数组的循环期间,如果您在哈希表中找不到某些内容,或者计数器已经为零,则它们是不相等的。同时比较总计数。

在存在重复项的情况下工作的另一种方法是对两个数组进行排序并进行线性比较。这应该是O(N * log(N))。

答案 2 :(得分:5)

假设您不想打扰原始数组并考虑空间,另一个使用比排序两个数组更少空间的O(n.log(n))解决方案是:

  1. 如果数组大小不同,则返回FALSE
  2. 排序第一个数组--O(n.log(n))时间,所需的额外空间是一个数组的大小
  3. 对于第二个数组中的每个元素,检查它是否在排序副本中      第一个数组使用二进制搜索 - O(n.log(n))时间
  4. 如果您使用此方法,请使用库例程进行二进制搜索。二进制搜索出乎意料地容易出错。

    [在审核建议字典/集合/散列查找的解决方案后添加:]

    在实践中我会使用哈希。有几个人已经断言哈希的O(1)行为,导致他们得出一个基于哈希的解决方案是O(N)。典型的插入/查找可能接近于O(1),并且一些散列方案保证最坏情况下的O(1)查找,但是在构造散列时最坏情况插入不是O(1)。给定任何特定的散列数据结构,会有一些输入会产生病态行为。我怀疑存在哈希数据结构,其中包含O(N.log(N))时间和O(N)空间的[插入N元素然后查找-N元素]的最坏情况。

答案 3 :(得分:4)

您可以使用签名(对阵列成员进行交换操作),以便在阵列通常不同的情况下进一步优化,保存o(n log n)或内存分配。 签名可以是布隆过滤器的形式,甚至可以是加法或xor等简单的交换操作。

一个简单的例子(假设签名方长,gethashcode为良好的对象标识符;如果对象是,例如,int,那么它们的值是更好的标识符;并且一些签名将大于长)

public bool MatchArrays(object[] array1, object[] array2)
{
   if (array1.length != array2.length)
      return false;
   long signature1 = 0;
   long signature2 = 0;
   for (i=0;i<array1.length;i++) {
       signature1=CommutativeOperation(signature1,array1[i].getHashCode());
       signature2=CommutativeOperation(signature2,array2[i].getHashCode());
   }

   if (signature1 != signature2) 
       return false;

   return MatchArraysTheLongWay(array1, array2);
}

其中(使用加法运算;如果需要,使用不同的可交换操作,例如布隆过滤器)

public long CommutativeOperation(long oldValue, long newElement) {
    return oldValue + newElement;
}

答案 4 :(得分:3)

这可以通过不同的方式完成:

1 - 暴力:对于array1中的每个元素,检查该元素是否存在于array2中。请注意,这需要记下位置/索引,以便可以正确处理重复项。这要求O(n ^ 2)代码复杂得多,甚至根本不考虑它......

2 - 对两个列表进行排序,然后检查每个元素以查看它们是否相同。 O(n log n)用于排序和O(n)检查所以基本上是O(n log n),排序可以就地完成如果搞乱阵列不是问题,如果不是你需要2n大小的内存复制已排序的列表。

3 - 将项目和计数从一个数组添加到哈希表中,然后遍历另一个数组,检查每个项目是否在哈希表中,在这种情况下,如果不是零则减量计数,否则从哈希表中删除它。 O(n)创建哈希表,而O(n)检查哈希表中的其他数组项,所以O(n)。这为n个元素引入了一个带有内存的哈希表。

4 - Best of Best(在上述中):减去或取两个数组的相同索引中每个元素的差异,最后总结相关的值。例如,对于例如A1 = {1,2,3},A2 = {3,1,2},Diff = { - 2,1,1}现在总结Diff = 0,这意味着它们具有相同的整数集。这种方法需要O(n)而没有额外的内存。 c#代码如下所示:

    public static bool ArrayEqual(int[] list1, int[] list2)
    {
        if (list1 == null || list2 == null)
        {
            throw new Exception("Invalid input");
        }

        if (list1.Length != list2.Length)
        {
            return false;
        }

        int diff = 0;

        for (int i = 0; i < list1.Length; i++)
        {
            diff += list1[i] - list2[i];
        }

        return (diff == 0);
    }

4根本不起作用,这是最糟糕的

答案 5 :(得分:2)

如果数组的元素是不同的,则XOR(按位异或)两个数组的所有元素,如果答案为零,则两个数组都具有相同的数字集。时间复杂度为O(n)

答案 6 :(得分:1)

我建议先使用排序并先排序。然后你将比较每个数组的第一个元素,然后比较第二个元素,依此类推。

如果发现不匹配,可以停止。

答案 7 :(得分:1)

如果先对两个数组进行排序,则得到O(N log(N))。

答案 8 :(得分:1)

什么是“最佳”解决方案显然取决于您的约束条件。如果它是一个小数据集,那么排序,散列或强力比较(如发布的nickf)都将非常相似。因为您知道要处理整数值,所以可以获得O(n)排序时间(例如基数排序),并且哈希表也将使用O(n)时间。与往常一样,每种方法都有缺点:如果要节省空间,排序将要求您复制数据或破坏性地对数组进行排序(丢失当前排序)。哈希表显然会产生用于创建哈希表的内存开销。如果你使用nickf的方法,你可以用很少甚至没有内存开销来完成它,但你必须处理O(n 2 )运行时。您可以选择最适合您的目的。

答案 9 :(得分:1)

在这里深水,但是:

排序列表 如所指出的那样,排序可以是O(nlogn)。只是为了澄清,有两个列表并不重要,因为:O(2*nlogn) == O(nlogn),然后比较每个元素是另一个O(n),所以排序然后比较每个元素是O(n)+ O(nlogn) ): O(nlogn)

<强>散列表: 将第一个列表转换为哈希表是用于读取的O(n)+在哈希表中存储的成本,我猜这可以估计为O(n),给出O(n)。然后你必须检查生成的哈希表中另一个列表中每个元素的存在,这是(至少?)O(n)(假设检查一个元素的存在,哈希表是常量)。总而言之,我们最终以 O(n) 进行检查。

Java List接口defines equals,因为每个对应的元素都相同。

有趣的是,Java Collection接口定义almost discourages implementing the equals()函数。

最后,Java Set interface per documentation implements这种行为。实现应该非常有效,但文档没有提到性能。 (无法找到源的链接,它可能是严格许可的。下载并自己查看。它附带JDK)查看源代码,HashSet(这是一个常用的Set实现)委托等于()实现到AbstractSet,它使用AbstractCollection的containsAll()函数再次从hashSet使用contains()函数。所以HashSet.equals()按预期在O(n)中运行。 (循环遍历所有元素并在哈希表中以恒定时间查找它们。)

如果你知道更好的话,请编辑我的尴尬。

答案 10 :(得分:1)

伪代码:

A:array
B:array
C:hashtable

if A.length != B.length then return false;

foreach objA in A
{
H = objA;
if H is not found in C.Keys then
C.add(H as key,1 as initial value);
else
C.Val[H as key]++;
}

foreach objB in B
{
H = objB;
if H is not found in C.Keys then
return false;
else
C.Val[H as key]--;
}

if(C contains non-zero value)
return false;
else
return true;

答案 11 :(得分:0)

最好的方法可能是使用散列图。由于插入到散列映射中的是O(1),因此从一个数组构建散列映射应该采用O(n)。然后你有n个查找,每个查找取O(1),所以另一个O(n)操作。总而言之,它是O(n)。

在python中:

def comparray(a, b): 
    sa = set(a)
    return len(sa)==len(b) and all(el in sa for el in b)

答案 12 :(得分:0)

忽略在C#中执行此操作的内置方法,您可以执行以下操作:

在最好的情况下,其O(1),在最坏的情况下为O(N)(每个列表)。

public bool MatchArrays(object[] array1, object[] array2)
{
   if (array1.length != array2.length)
      return false;

   bool retValue = true;

   HashTable ht = new HashTable();

   for (int i = 0; i < array1.length; i++)
   {
      ht.Add(array1[i]);
   }

   for (int i = 0; i < array2.length; i++)
   {
      if (ht.Contains(array2[i])
      {
         retValue = false;
         break;
      }
   }

    return retValue;
}

答案 13 :(得分:0)

在大多数情况下,在碰撞时,散列图是O(n),因为它使用链表来存储碰撞。但是,有更好的方法,你应该几乎没有碰撞,因为如果你这样做,hashmap将是无用的。在所有常规情况下,它只是O(1)。除此之外,在单个散列映射中不可能有超过一小部分的冲突,因此性能不会那么糟糕;你可以放心地说它是O(1)或几乎 O(1)因为n很小所以可以忽略它。

答案 14 :(得分:0)

这是另一种选择,让我知道你们的想法。在最坏的情况下应该是T(n)= 2n * log2n - > O(nLogn)。

private boolean compare(List listA, List listB){
    if (listA.size()==0||listA.size()==0) return true;
    List runner = new ArrayList();
    List maxList = listA.size()>listB.size()?listA:listB;
    List minList = listA.size()>listB.size()?listB:listA;
    int macthes = 0;
    List nextList = null;;
    int maxLength = maxList.size();
    for(int i=0;i<maxLength;i++){
        for (int j=0;j<2;j++) {
            nextList = (nextList==null)?maxList:(maxList==nextList)?minList:maList;
            if (i<= nextList.size()) {
                MatchingItem nextItem =new MatchingItem(nextList.get(i),nextList)
                int position = runner.indexOf(nextItem);
                if (position <0){
                    runner.add(nextItem);
                }else{
                    MatchingItem itemInBag = runner.get(position);
                    if (itemInBag.getList != nextList)   matches++;
                    runner.remove(position);
                }
            }
        }
    }
    return maxLength==macthes;
}

public Class MatchingItem{
private Object item;
private List itemList;
public MatchingItem(Object item,List itemList){
    this.item=item
    this.itemList = itemList
}
public boolean equals(object other){
    MatchingItem otheritem = (MatchingItem)other;
    return otheritem.item.equals(this.item) and otheritem.itemlist!=this.itemlist
}

public Object getItem(){ return this.item}
public Object getList(){ return this.itemList}

}

答案 15 :(得分:-2)

我能想到的最好的是O(n ^ 2),我想。

function compare($foo, $bar) {
    if (count($foo) != count($bar)) return false;

    foreach ($foo as $f) {
        foreach ($bar as $b) {
            if ($f == $b) {
                // $f exists in $bar, skip to the next $foo
                continue 2;
            }
        }
        return false;
    }
    return true;
}