我想知道下面解释的任务是否在理论上是可行的,如果是这样,我怎么能做到。
您将获得N
元素的空格(即0
和N-1
之间的所有数字。)让我们看一下该空间上所有排列的空间,并且称之为S
。 i
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问题中提到的最大数字:)
如果您只关注该空间的随机排列,那么它将会非常大,以至于您无法将整个事物存储在您的硬盘上,更不用说计算每个项目了按字典编号。我想要的是能够对该排列进行项目访问,并获得每个项目的索引。也就是说,给定i
和O(1)
来指定排列,有一个函数接受索引号并找到该索引中的数字,另一个函数接受一个数字并找到哪个索引它驻留。我想在N
中执行此操作,因此我不需要在排列中存储或迭代每个成员。
256^16
为10^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中排列任何项目(如此小)尽可能)?
答案 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。