【问题标题】:Seek for a bijective function maps sets to integers寻找一个双射函数将集合映射到整数
【发布时间】:2013-11-17 18:30:23
【问题描述】:

对于任意两个序列 a, b, 其中 a = [a1,a2,...,an] 和 b = [b1,b2,...,bn] (0a, 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)。

【问题讨论】:

标签: algorithm math


【解决方案1】:

这适用于足够小的 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

【讨论】:

  • 这不是很难看到那里发生了什么,但可能只是因为我以前看过这个想法几次,而且我知道 C。你应该考虑添加一些伪代码或高级描述。
  • 为什么要添加伪代码?整个事情中最复杂的构造是一个for循环,它存在于任何编程语言中。函数调用也是如此。将它与下面的 python 碎石进行比较,然后选择你认为最容易阅读的那个。
  • @wildplasser:for 循环并不难。困难的是(对于以前从未见过的人)质数突然在这里做什么?
  • 鉴于 OP 描绘问题的数学方式,他应该能够找到它。这个例子足够小,可以手动进行计算。 (或在关键位置添加一些 printf())无需侧轮。
  • +1 为优雅的解决方案。我只是想支持其他建议在代码中添加注释的人。例如。 /* Map each array element to a prime number and take the product. */
【解决方案2】:

哇,@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 索引),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 函数只有在与相同的基础一起使用时才会是真正的哈希,所以质数如果您使用不同长度和组成的数组,分解可能是安全的,因为它总是会为每袋数字返回相同的唯一整数。

【讨论】:

  • 我相信 Eratosthenes 筛算法是 O(n log log n) 来找到 n 的素数。由于k-th 素数是渐近的k log k,因此找到前n个素数的复杂度应该类似于O(n log n log log n)
  • @rici:你说的完全正确,我在这里搞糊涂了。已更正。
猜你喜欢
  • 2021-07-12
  • 1970-01-01
  • 2021-03-11
  • 2011-09-16
  • 1970-01-01
  • 1970-01-01
  • 2016-01-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多