对于任何两个序列a,b,其中a = [a1,a2,...,an]和b = [b1,b2,...,bn](0 <= ai,bi <= m) ,我想找到一个整数函数f,f(a)= f(b)当且仅当a, b
具有相同的元素,而不考虑它们的顺序。例如,如果a = [1,1, 2,3],b = [2,1,3,1],c = [3,2,1,3],则f(a)= f(b),f(a)≠f(b)。
我知道有一种天真的算法首先对序列进行排序,然后将其映射到整数。 例如,在排序之后,我们得到a = [1,1,2,3],b = [1,1,2,3],c = [1,2,3,3],并假设m = 9 ,使用十进制转换,我们最终将得到f(a)= f(b)= 1123≠f(c)= 1233.但是这将使用某种排序算法花费O(nlog(n))时间(不要使用非比较排序算法)。
有更好的方法吗?哈希之类的东西? O(n)算法?
请注意,我还需要易于反转的函数,这意味着我们可以将整数映射回序列(或更简洁的集合)。
更新:原谅我糟糕的描述。这里m,n都可以非常大(100万或更大)。而且我还希望f的上限非常小,最好是O(m ^ n)。
答案 0 :(得分:4)
这适用于m
的足够小的值,以及足够小的数组大小:
#include <stdio.h>
unsigned primes [] = { 2,3,5,7,11,13,17, 19, 23, 29};
unsigned value(unsigned array[], unsigned count);
int main(void)
{
unsigned one[] = { 1,2,2,3,5};
unsigned two[] = { 2,3,1,5,2};
unsigned val1, val2;
val1 = value(one, 5);
val2 = value(two, 5);
fprintf(stdout, "Val1=%u, Val2=%u\n", val1, val2 );
return 0;
}
unsigned value(unsigned array[], unsigned count)
{
unsigned val, idx;
val = 1;
for (idx = 0; idx < count; idx++) {
val *= primes [ array[idx]];
}
return val;
}
要获得解释,请see my description here。
答案 1 :(得分:3)
任何数字都可以在素数中以唯一方式分解(这称为fundamental theorem of arithmetic)。他的答案依赖于此,通过构建一个数字,输入数组是素数因子分解的表示。由于乘法是可交换的,因此数组中元素的确切顺序并不重要,但给定的数字与一个(且只有一个)元素序列相关联。
他的解决方案可以扩展到任意大小,例如在Python中:
import operator
import itertools
import math
class primes(object):
def __init__(self):
self.primes = [2,3,5,7,11]
self.stream = itertools.count(13, 2)
def __getitem__(self, i):
sq = int(math.sqrt(i))
while i >= len(self.primes):
n = self.stream.next()
while any(n % p == 0 for p in self.primes if p <= sq):
n = self.stream.next()
self.primes.append(n)
return self.primes[i]
def prod(itr):
return reduce(operator.mul, itr, 1)
p = primes()
def hash(array):
return prod(p[i] for i in array)
预期结果:
>>> hash([1,2,2,3,5])
6825
>>> hash([5,3,2,2,1])
6825
此处6825 = 3^1 x 5^2 x 7^1 x 13^1
,3
为'1'素数(0-indexed),5
为'2'等...
>>> 3**1 * 5**2 * 7**1 * 13**1
6825
构建数字本身就是O(n)乘法,只要最终结果保留在您正在使用的int
域中(不幸的是,我怀疑它可能会很快失控)。像我一样用Eratosthenes Sieve建立素数系列是渐近O(N * log log N),其中N是第m个最大素数。渐近地,N~m log m,这给出了总体复杂度O(n + m * log m * loglog(m * log m))
使用类似的方法,我们也可以将数组视为基数中数字分解的表示,而不是采用素数分解。为了保持一致,这个基数必须大于相似元素的数量(例如对于[5, 3, 3, 2, 1]
,基数必须> 2,因为有两个3
)。为了安全起见,您可以写下:
def hash2(array):
n = len(array)
return sum(n**i for i in array)
>>> hash2([1,5,3,2,2])
8070
>>> hash2([2,1,5,2,3])
8070
您可以通过首先计算数组中最大数量的相似元素来改进这一点,但hash2
函数只有在与相同基础一起使用时才是真正的哈希值,因此如果使用不同长度和组成的数组,素数分解可能是安全的,因为它总是会为每个数字包返回相同的唯一整数。