给定排列的词典编号,是否可以在O(1)中获取其中的任何项目

时间:2014-05-06 18:25:02

标签: algorithm encryption permutation combinatorics number-theory

我想知道下面解释的任务是否在理论上是可行的,如果是这样,我怎么能做到。

您将获得N元素的空格(即0N-1之间的所有数字。)让我们看一下该空间上所有排列的空间,并且称之为Si S成员S[i],可以标记为i,是字典编号为N的排列。

例如,如果S为3,那么S[0]: 0, 1, 2 S[1]: 0, 2, 1 S[2]: 1, 0, 2 S[3]: 1, 2, 0 S[4]: 2, 0, 1 S[5]: 2, 1, 0 就是这个排列列表:

N

(当然,在查看大N!时,此空间变得非常大,确切地说N=10^20。)

现在,我已经知道how to get the permutation by its index number i, and I already know how to do the reverse (get the lexicographic number of a given permutation.)但我想要更好的东西。

一些排列本身可能很大。例如,如果您正在查看S。 ((10^20)!的大小为N,我认为这是我在Stack Overflow问题中提到的最大数字:)

如果您只关注该空间的随机排列,那么它将会非常大,以至于您无法将整个事物存储在您的硬盘上,更不用说计算每个项目了按字典编号。我想要的是能够对该排列进行项目访问,并获得每个项目的索引。也就是说,给定iO(1)来指定排列,有一个函数接受索引号并找到该索引中的数字,另一个函数接受一个数字并找到哪个索引它驻留。我想在N中执行此操作,因此我不需要在排列中存储或迭代每个成员。

疯了,你说?不可能?那可能。但请考虑一下:像AES这样的分组密码本质上是一种排列,它几乎完成了我上面概述的任务。 AES的块大小为16字节,这意味着256^1610^38,大约为S。 ((256^16)!的大小,并不重要,是一个惊人的10^85070591730234615865843651857942052838,或大约N=256^16,这超过了我最近在Stack Overflow&#34上提到的最大数字#34; ;:)

每个AES加密密钥都在range(N)上指定单个排列。这种排列无法整体存储在您的计算机上,因为它的成员数量多于太阳系中的原子数。但是,它允许您访问项目。通过使用AES加密数据,您可以逐块查看数据,并为每个块(range(N)的成员)输出加密块,该块是O(1)的成员。置换中原始块的索引号。当你进行解密时,你正在反向(查找一个块的索引号。)我相信这是在N中完成的,我不确定但无论如何它非常快。

使用AES或任何其他分组密码的问题在于它将您限制为非常具体的N,它可能只捕获可能排列的一小部分,而我希望能够使用任何{ {1}}我喜欢,并对我喜欢的任何排列S[i]进行项目访问。

在给定大小O(1)和排列号N的情况下,是否可以对排列获取i项访问权限?如果是这样,怎么样?

(如果我有幸在这里获得代码答案,如果他们使用Python,我会很感激。)

更新

有些人指出了一个令人遗憾的事实,即排列数本身就是如此庞大,只要读取数字就会使任务变得不可行。然后,我想修改我的问题:如果能够访问排列的词典编号的factoradic representation,是否可以在O中排列任何项目(如此小)尽可能)?

5 个答案:

答案 0 :(得分:5)

这样做的秘诀是"计入基因因子"。

同样地,134 = 1 * 10 ^ 2 + 3 * 10 + 4,134 = 5! + 2 * 3! + 2! =>因子表示法10210(包括1!,排除0!)。如果你想代表N !,那么你将需要N ^ 2个十位数字。 (对于每个因子数字N,它可以容纳的最大数量是N)。关于你所谓的0的一些混淆,这个阶乘表示恰好是排列的词典编号。

您可以使用此洞察力手动解决欧拉问题24。所以我会在这里做,你会看到如何解决你的问题。我们希望百万分之一的排列为0-9。在阶乘表示中,我们取1000000 => 26625122.现在将其转换为排列,我取数字0,1,2,3,4,5,6,7,8,9,第一个数字是2,这是第三个(它可能是0 ),所以我选择2作为第一个数字,然后我有一个新的列表0,1,3,4,5,6,7,8,9我拿第七个数字是8等,我得到2783915604。

然而,这假设你开始你的字典排序为0,如果你实际上是从1开始,你必须从中减去1,这给出了2783915460.这确实是数字0-9的百万位排列。< / p>

你可以明显地改变这个程序,因此可以在lexiographic数字和它所代表的排列之间轻松地前后转换。

我不完全清楚你想在这里做什么,但理解上述程序应该有所帮助。例如,它清楚地表明,lexiographic数字表示可以用作哈希表中的键的顺序。而且你可以通过从左到右比较数字来订购数字,所以一旦你插入一个数字,你就不需要算出它的因子。

答案 1 :(得分:4)

您的问题有点没有实际意义,因为任意排列索引的输入大小都有大小日志(N!)(假设您想要表示所有可能的排列),即Theta(N log N),所以如果N真的是大,然后只读取排列索引的输入将花费太长时间,肯定比O(1)长得多。可以以这样的方式存储置换索引:如果已经存储了它,那么您可以在O(1)时间内访问元素。但是可能任何这样的方法都只相当于将排列存储在连续的内存中(也有Theta(N log N)大小),如果你将排列直接存储在内存中,那么假设你可以做O(1)那么问题就变得微不足道了。 )内存访问。 (但是你仍然需要考虑元素的位编码的大小,即O(log N))。

根据加密类比的精神,您可能应该根据某些属性指定一个小的SUBSET排列,并询问是否可以对该小子集进行O(1)或O(log N)元素访问。

答案 2 :(得分:2)

修改

我误解了这个问题,但这不是浪费。我的算法让我理解:排列的词典编号的事实表示几乎与排列本身相同。实际上,事实表示的第一个数字与第一个元素相同相应的排列(假设您的空间由0到N-1之间的数字组成)。知道这一点,存储索引而不是排列本身并不是真正的重点。要了解如何将词典编号转换为排列,请阅读以下内容。 另请参阅this wikipedia link about Lehmer code

原帖:

在S空间中有N个元素可以填充第一个插槽,这意味着有(N-1)!以0开头的元素。所以i /(N-1)!是第一个元素(让我们称之为&#39; a&#39;)。以0开头的S子集由(N-1)组成!元素。这些是集合N {a}的可能排列。现在你可以得到第二个元素:它是i(%((N-1)!)/(N-2)!)。重复这个过程,你得到了排列。

反向就是这么简单。从i = 0开始。获得排列的第二个元素。制作一组最后两个元素,并找到元素在其中的位置(它的第0个元素或第1个元素),让我们调用这个位置j。然后我+ = j * 2!。重复这个过程(你可以从最后一个元素开始,但它始终是可能性的第0个元素。)

Java-ish pesudo代码:

find_by_index(List N, int i){
    String str = "";
    for(int l = N.length-1; i >= 0; i--){
        int pos = i/fact(l);
        str += N.get(pos);
        N.remove(pos);
        i %= fact(l);
    }
    return str;
}

find_index(String str){
    OrderedList N;
    int i = 0;
    for(int l = str.length-1; l >= 0; l--){
        String item = str.charAt(l);
        int pos = N.add(item);
        i += pos*fact(str.length-l)
    }
    return i;
}

find_by_index应该在O(n)中运行,假设N是预先排序的,而find_index是O(n * log(n))(其中n是N空间的大小)

答案 3 :(得分:0)

经过Wikipedia的一些研究,我设计了这个算法:

def getPick(fact_num_list):
    """fact_num_list should be a list with the factorial number representation, 
    getPick will return a tuple"""
    result = [] #Desired pick
    #This will hold all the numbers pickable; not actually a set, but a list
    #instead
    inputset = range(len(fact_num_list)) 
    for fnl in fact_num_list:
        result.append(inputset[fnl])
        del inputset[fnl] #Make sure we can't pick the number again
    return tuple(result)

显然,由于我们需要“挑选”每个数字的因素,这不会达到O(1)。由于我们执行for循环,因此,假设所有操作都是O(1),getPick将在O(n)中运行。

如果我们需要从基数10转换为阶乘基数,这是一个辅助函数:

import math

def base10_baseFactorial(number):
    """Converts a base10 number into a factorial base number. Output is a list
    for better handle of units over 36! (after using all 0-9 and A-Z)"""
    loop = 1
    #Make sure n! <= number
    while math.factorial(loop) <= number:
        loop += 1
    result = []
    if not math.factorial(loop) == number:
        loop -= 1 #Prevent dividing over a smaller number than denominator
    while loop > 0:
        denominator = math.factorial(loop)
        number, rem = divmod(number, denominator)
        result.append(rem)
        loop -= 1
    result.append(0) #Don't forget to divide to 0! as well!
    return result

同样,由于while s,这将在O(n)中运行。

总结一下,我们能找到的最佳时间是 O(n)

PS:我不是母语为英语的人,因此可能会出现拼写和短语错误。提前道歉,如果你无法解决问题,请告诉我。

答案 4 :(得分:0)

用于访问以事实形式存储的排列的第k项的所有正确算法必须读取前k个数字。这是因为,无论第一个k中的其他数字的值如何,它都会使未读数字是0还是取其最大值。通过在两个并行执行中跟踪规范正确的解码程序,可以看出这种情况。

例如,如果我们要解码排列1的第三个数字,那么对于100,该数字为0,对于110,该数字为2。