确定两个列表是否包含相同的数字项而不进行排序

时间:2010-10-08 00:58:08

标签: performance

我有两个列表,我需要确定它们是否包含相同的值而不进行排序(即,值的顺序无关紧要)。 我知道排序可行,但这是性能关键部分的一部分。

项目值在[-2,63]范围内,我们总是在比较相同大小的列表,但列表大小的范围是[1,8]。

示例列表:

A = (0,   0, 4, 23, 10)
B = (23, 10, 0,  4,  0)
C = (0,   0, 4, 27, 10)

A == B is true
A == C is false

我认为可能的解决方案是比较两个列表的乘积(将所有值相乘),但此解决方案存在问题。如何处理零和负数。解决方法是在乘法之前为每个值添加4。这是我到目前为止的代码。

bool equal(int A[], int B[], int size)
{
    int sumA = 1;
    int sumB = 1;

    for (int i = 0; i < size; i++) {
        sumA *= A[i] + 4;
        sumB *= B[i] + 4;
    }
    return (sumA == sumB)
}

但是,无论列表的顺序/内容是什么,这总是有效吗?换句话说,以下数学上是真的吗?所以我真正要问的是以下(除非有另一种方法来解决问题):

给出2个相同大小的列表。如果列表中的乘积(将所有值相乘)相等,则列表包含相同的值,只要这些值是大于0的整数。

7 个答案:

答案 0 :(得分:7)

假设您提前知道范围,则可以使用计数排序的变体。只需扫描每个数组并跟踪每个整数的出现次数。

Procedure Compare-Lists(A, B, min, max)
  domain := max - min
  Count := new int[domain]
  for i in A:
    Count[i - min] += 1
  for i in B:
    Count[i - min] -= 1
    if Count[i - min] < 0:
      // Something was in B but not A
      return "Different"
  for i in A:
    if Count[i - min] > 0:
      // Something was in A but not B
      return "Different"
  return "Same"

这在O(len(A) + len(B))

中是线性的

答案 1 :(得分:3)

你可以用素数做到这一点。保留前66个素数的素数表,并使用数组的元素(偏移+2)来索引素数表。

数组的标识就是数组中元素所代表的素数的乘积。

不幸的是,产品必须至少包含67位:

  • 66 th prime是317,3177 8 = 101,970,394,089,246,452,641
  • log 2 (101,970,394,089,246,452,641)= 66.47(向上舍入) 67位

用于执行此操作的示例伪代码(假设存在int128数据类型):

int primes[] = 
{
      2,   3,   5,   7,  11,  13,  17,  19,  23,  29,
     31,  37,  41,  43,  47,  53,  59,  61,  67,  71,
     73,  79,  83,  89,  97, 101, 103, 107, 109, 113,
    127, 131, 137, 139, 149, 151, 157, 163, 167, 173,
    179, 181, 191, 193, 197, 199, 211, 223, 227, 229,
    233, 239, 241, 251, 257, 263, 269, 271, 277, 281,
    283, 293, 307, 311, 313, 317
};

// Assumes:
// Each xs[i] is [-2, 63]
// length is [1, 8]
int128 identity(int xs[], int length)
{
    int128 product = 1;

    for (int i = 0; i < length; ++i)
    {
        product *= primes[xs[i] + 2];
    }

    return product;
}

bool equal(int a[], int b[], int size)
{
    return identity(a, size) == identity(b, size);
}

您可以在GCC上使用long double来存储产品,因为它被定义为80位数据类型,但我不确定浮点乘法错误是否会导致产品之间的冲突名单。我还没有证实这一点。


我之前的解决方案不起作用,请参阅下面的评论。

对于每个清单:

  • 计算所有元素的总和
  • 计算所有元素的产品
  • 存储列表的长度(在您的情况下,因为两个列表的长度保证相同,您可以完全忽略它)

在计算总和和乘积时,每个元素需要调整+3,所以你的范围现在是[1,66]。

(sum,product,length)元组是列表的标识。任何具有相同身份的列表都是相同的。

您可以将此(总和,产品,长度)元组合并为一个64位数字:

  • 对于产品:66 8 = 360,040,606,269,696,log 2 (360,040,606,269,696)= 48.36(向上舍入) 49位
  • 总和:66 * 8 = 528,log 2 (528)= 9.04(向上舍入) 10位
  • 长度在[1,8]范围内,log 2 (8)= 3位
  • 49 + 10 + 3 = 62位用于表示身份

然后,您可以直接进行64位比较以确定相等性。

运行时间是数组的大小是线性的,每次都有一次传递。内存使用量为O(1)

示例代码:

#include <cstdint>
#include <stdlib.h>

// Assumes:
// Each xs[i] is [-2, 63]
// length is [1, 8]
uint64_t identity(int xs[], int length)
{
    uint64_t product = 1;
    uint64_t sum = 0;

    for (int i = 0; i < length; ++i)
    {
        int element = xs[i] + 3;
        product *= element;
        sum += element;
    }

    return (uint64_t)length << 59 | (sum << 49) | product;
}

bool equal(int a[], int b[], int size)
{
    return identity(a, size) == identity(b, size);
}

void main()
{
    int a[] = { 23, 0, -2,  6,  3, 23, -1 };
    int b[] = { 0, -1,  6, 23, 23, -2,  3 };

    printf("%d\n", equal(a, b, _countof(a)));
}

答案 2 :(得分:2)

由于您只有66个可能的数字,因此可以创建一个位向量(3个32位字或2个64位字)并进行比较。只需轮班和添加即可完成所有操作。由于在结束之前不需要进行比较(以确定它们是否相等),因此它可以快速运行,因为不会有很多分支。

答案 3 :(得分:0)

制作第一个清单的副本。然后遍历第二个,并从副本中删除每个项目。如果您完成第二个列表并找到副本中的所有元素,则列表具有相同的元素。这是很多循环,但是列表中只有最多8个元素,使用不同类型的集合不会获得性能提升。

如果您有更多项目,则为该副本提供Dictionary / Hashtable。保留唯一的值键,并计算在第一个列表中找到它们的次数。这将使您在更大的列表上获得性能提升。

答案 4 :(得分:0)

  

给出2个相同大小的列表。如果列表中的乘积(将所有值相乘)相等,则列表包含相同的值,只要这些值是大于0的整数。

没有。请考虑以下列表

(9, 9)
(3, 27)

它们的大小相同,元素的乘积相同。

答案 5 :(得分:0)

您需要多快处理8个整数?在任何现代处理器中排序8件事几乎不需要时间。

简单的方法是使用大小为66的数组,其中索引0表示值-2。然后你只需在两个数组中递增计数,然后你就可以在之后迭代它们。

答案 6 :(得分:0)

如果您的列表只有8个项目,那么排序几乎不会影响性能。如果你想在没有排序的情况下这样做,你可以使用hashmap这样做。

  1. 迭代第一个数组,并对数组中的每个值N(N)= 1。
  2. 迭代第二个数组,对于每个值M,Hash(M)= Hash(M)+ 1.
  3. 迭代哈希并找到哈希(K)= 2的所有密钥K。