寻求双射函数映射集到整数

时间:2013-11-06 13:32:25

标签: algorithm math

对于任何两个序列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)。

2 个答案:

答案 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)

哇,@ wildplasser的答案实际上超级聪明。扩大一点:

任何数字都可以在素数中以唯一方式分解(这称为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^13为'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函数只有在与相同基础一起使用时才是真正的哈希值,因此如果使用不同长度和组成的数组,素数分解可能是安全的,因为它总是会为每个数字包返回相同的唯一整数。