假设我们有一个字符串“ ABCD”,我们想从字符串的第n个排列中检索第i个位置的字母。
在此示例中,我知道存在阶乘(4)= 24个排列,并且可以使用itertools.permutations轻松检索列表,这将给出以下信息:
['ABCD','ABDC','ACBD','ACDB','ADBC','ADCB','BACD','BADC','BCAD','BCDA','BDAC','BDCA ','CABD','CADB','CBAD','CBDA','CDAB','CDBA','DABC','DACB','DBAC','DBCA','DCAB','DCBA']
因此我要查找的函数f应该返回:
f(0,n)== ['A','A','A','A','A','A','B','B','B','B ','B','B','C','C','C','C','C','C','D','D','D','D', 'D','D'] [n]
f(1,n)== ['B','B','C','C','D','D','A','A','C','C ','D','D','A','A','B','B','D','D','A','A','B','B', 'C','C'] [n]
f(2,n)== ['C','D','B','D','B','C','C','D','A','D ','A','C','B','D','A','D','A','B','B','C','A','C', 'A','B'] [n]
f(3,n)== ['D','C','D','B','C','B','D','C','D','A ','C','A','D','B','D','A','B','A','C','B','C','A', 'B','A'] [n]
对于i == 0来说,这很容易,我们有f(0,n)==“ ABCD” [n // 6],但是当i增大时找到模式越来越复杂。
我完全不在乎排列的顺序,因此也许可以轻松找到每个i值的通用模式...
我计划将其与一组256个元素和阶乘(256)排列一起使用,因此计算排列不是一个选择。
编辑:我已经有一个函数,但是它太慢了,我想知道是否可以使用简单的公式(例如按位运算)找到某些等效结果...
Edit-2:由于@rici,这是当前最好的解决方案:
f = [factorial(i) for i in range(256)]
def _getElt(k, i):
"""
Returns the <i>th element of the <k>th permutation of 0..255
"""
table = list(range(256))
for j in range(255, 254-i, -1):
r, k = divmod(k, f[j])
perm[j], perm[r] = perm[r], perm[j]
return perm[255 - i]
Edit-3:这是另一种使用多项式逼近来重新生成置换的方法,因此问题的另一种表述可能是“如何为第n个置换重新生成多项式的第i个系数?”。 >
这是n = 4的n个排列,多项式系数(最后是从多项式系数重建的排列)的列表:
0 [0,1,2,3] [分数(0,1),分数(1,1),分数(0,1),分数(0,1)] [0,1,2,3 ]
1 [0,1,3,2] [分数(0,1),分数(-3,4),分数(5,2),分数(-2,3)] [0,1,3 ,2]
2 [0,2,1,3] [分数(0,1),分数(11,2),分数(-9,2),分数(1,1)] [0,2,1, 3]
3 [0,2,3,1] [分数(0,1),分数(7,4),分数(1,2),分数(-1,3)] [0,2,3, 1]
4 [0,3,1,2] [分数(0,1),分数(33,4),分数(-13,2),分数(4,3)] [0,3,1, 2]
5 [0,3,2,1] [分数(0,1),分数(19,3),分数(-4,1),分数(2,3)] [0,3,2, 1]
6 [1,0,2,3] [分数(1,1),分数(-15,4),分数(7,2),分数(-2,3)] [1,0,2 ,3]
7 [1,0,3,2] [分数(1,1),分数(-17,3),分数(6,1),分数(-4,3)] [1,0,3 ,2]
8 [1、2、0、3] [分数(1、1),分数(21、4),分数(-11、2),分数(4、3)] [1、2、0, 3]
9 [1,2,3,0] [分数(1,1),分数(-1,3),分数(2,1),分数(-2,3)] [1,2,3 ,0]
10 [1,3,0,2] [分数(1,1),分数(31,4),分数(-15,2),分数(5,3)] [1,3,0, 2]
11 [1、3、2、0] [分数(1、1),分数(17、4),分数(-5、2),分数(1、3)] [1、3、2 0]
12 [2,0,1,3] [分数(2,1),分数(-17,4),分数(5,2),分数(-1,3)] [2,0,1 ,3]
13 [2,0,3,1] [分数(2,1),分数(-31,4),分数(15,2),分数(-5,3)] [2,0,3 ,1]
14 [2,1,0,3] [分数(2,1),分数(1,3),分数(-2,1),分数(2,3)] [2,1,0, 3]
15 [2,1,3,0] [分数(2,1),分数(-21,4),分数(11,2),分数(-4,3)] [2,1,3 ,0]
16 [2,3,0,1] [分数(2,1),分数(17,3),分数(-6,1),分数(4,3)] [2,3,0, 1]
17 [2,3,1,0] [分数(2,1),分数(15,4),分数(-7,2),分数(2,3)] [2,3,1, 0]
18 [3,0,1,2] [分数(3,1),分数(-19,3),分数(4,1),分数(-2,3)] [3,0,1 ,2]
19 [3,0,2,1] [分数(3,1),分数(-33,4),分数(13,2),分数(-4,3)] [3,0,2 ,1]
20 [3,1,0,2] [分数(3,1),分数(-7,4),分数(-1,2),分数(1,3)] [3,1,0 ,2]
21 [3,1,2,0] [分数(3,1),分数(-11,2),分数(9,2),分数(-1,1)] [3,1,2 ,0]
22 [3,2,0,1] [分数(3,1),分数(3,4),分数(-5,2),分数(2,3)] [3,2,0, 1]
23 [3,2,1,0] [分数(3,1),分数(-1,1),分数(0,1),分数(0,1)] [3,2,1, 0]
我们可以清楚地看到存在一个对称性:coefs [i] = [3-coefs [23-i] [0]] + [-c对于coefs [23-i] [1:]]这是一种探索的方式,但我不知道这是有可能的。
答案 0 :(得分:3)
通过进行重复的欧几里德除法(商和余数,又称为divmod)并跟踪选择的字母,可以找到第n
个排列。然后,您可以从该排列中选择第i
个字母。
让S
是初始字符串的副本,L
是该字符串的长度,P
是排列次数(L!
)。 T
将是逐步构建的n
的第S
个置换。
示例:S = "ABCD"
,L = 4
和P = 24
。让我们以n = 15
为例。 T
应该以{{1}}结尾。
设置"CBDA"
。计算P = P/L
,令divmod(n, P)
为商(q
),使n/P
为余数(r
)。从n%P
中删除第q
个字母,并将其附加到S
。设置T
,递减n = r
,然后重复进行直到L
。
示例:
1)L = 0
,P = 24/4 = 6
,q = 15/6 = 2
,r = 15%6 = 3
,S = "ABD"
,T = "C"
,n = r = 3
。
2)L = 3
,P = 6/3 = 2
,q = 3/2 = 1
,r = 3%2 = 1
,S = "AD"
,T = "CB"
,n = r = 1
。
3)L = 2
,P = 2/2 = 1
,q = 1/1 = 1
,r = 1%1 = 0
,S = "A"
,T = "CBD"
,n = r = 0
。
4)L = 1
,P = 1/1 = 1
,q = 0/1 = 0
,r = 0%1 = 0
,S = ""
,T = "CBDA"
,n = r = 0
。
由于只需要第L = 0
个字母,因此只要i
的长度等于T
,就可以停下来,取最后一个字母。
我不会尝试用Python编写代码,因为自从接触Python以来已经太久了,但是here is a demo in C++。
答案 1 :(得分:2)
对不起,对于我的Python。这个想法是每个位置的字母重复(number_of_pos - pos)!
次。
例如ABCD
,第一个位置的字母将重复3!
次。因此,通过将n
除以阶乘,我们现在可以得出哪个字母在第一位置。要在第二个位置查找字母,我们需要将该字母从集合中删除(改为分配0
),并用n
更新n - n % 3!
到现在第二个位置的字母索引。应用此索引时,我们需要注意不要计算在第一位置的字母。
复杂度为O(kPosCount * kPosCount)
。
#include <array>
#include <iostream>
const int kPosCount = 4;
const std::array<int, kPosCount> factorial = { 1,1,2,6 };
const std::array<int, kPosCount> kLetters = { 'A', 'B', 'C', 'D' };
char f(int pos, int n) {
assert(pos < kPosCount);
std::array<int, kPosCount> letters = kLetters;
int res = 0;
for (int i = 0; i <= pos; ++i) {
const int letter = n / factorial[kPosCount - i - 1];
int j = 0;
for (int stillThere = 0; j < kPosCount; ++j) {
if (letters[j] != 0) {
if (stillThere == letter) {
break;
}
++stillThere;
}
}
letters[j] = 0;
res = j;
n %= factorial[kPosCount - i - 1];
}
return kLetters[res];
}
int main() {
const int kMaxN = factorial.back() * kPosCount;
for (int i = 0; i < kPosCount; ++i) {
for (int j = 0; j < kMaxN; ++j) {
std::cout << f(i, j) << " ";
}
std::cout << std::endl;
}
}
答案 2 :(得分:1)
这是函数的版本,具有不同的排列顺序。在这里,我并没有强迫排列的大小为256,所以您必须将其作为第一个参数。
def elt(n,k,i):
"""Returns element i of permutation k from permutations of length n"""
perm = [*range(n)]
for j in range(i+1):
k, r = divmod(k, n-j)
perm[j], perm[j+r] = perm[j+r], perm[j]
return perm[i]
与您的代码有两个区别。
首先,数字从右到左从索引中剔除,这意味着bignum divmod都是具有少量股息的divmod。我以为那会比使用bignum股息更快,但事实证明它要慢得多。但是,它的确避免了必须预先计算阶乘。
第二,而不是从表的中间进行一系列pop
的操作,因为弹出操作是O(n),这将具有二次复杂性,我只是将每个元素与某些前向交换元件。尽管bignum算术在计算中起着主导作用,但这要快得多。
实际上,elt(n,k,i)
使用i
的混合基分解作为随机变量,在range(n)
的Fisher-Yates混洗中执行了最初的k
步骤随机洗牌中的值。由于F-Y随机播放对每个可能的随机值序列产生不同的结果,并且k
的混合基分解对k
的每个不同的值产生不同的序列,因此将生成所有排列。
这是n = 4时的顺序:
>>> print('\n'.join(' '.join(str(elt(4, k, i)) for i in range(4))
for k in range(factorial(4))))
0 1 2 3
1 0 2 3
2 1 0 3
3 1 2 0
0 2 1 3
1 2 0 3
2 0 1 3
3 2 1 0
0 3 2 1
1 3 2 0
2 3 0 1
3 0 2 1
0 1 3 2
1 0 3 2
2 1 3 0
3 1 0 2
0 2 3 1
1 2 3 0
2 0 3 1
3 2 0 1
0 3 1 2
1 3 0 2
2 3 1 0
3 0 1 2
为真正加速该功能,我重新引入了预先计算的阶乘(作为局部变量,可以根据需要扩展)。我还通过消除尽可能多的算法对内部循环进行了微优化,这意味着从后到前进行F-Y随机播放。这导致了另一个排列顺序。参见下面的示例。
最终的优化功能比问题版本快15%(也就是说,进行简单基准测试大约需要85%的时间)。
def elt_opt(n, k, i, fact=[1]):
"""Returns element i of permutation k from permutations of length n.
Let the last argument default.
"""
while len(fact) < n: fact.append(fact[-1]*len(fact))
perm = list(range(n))
for j in range(n-1, n-2-i, -1):
r, k = divmod(k, fact[j])
perm[j], perm[r] = perm[r], perm[j]
return perm[n-i-1]
排列顺序:
>>> print('\n'.join(' '.join(str(elt_opt(4, k, i)) for i in range(4))
for k in range(factorial(4))))
0 3 2 1
0 3 1 2
0 1 3 2
0 1 2 3
0 2 3 1
0 2 1 3
1 0 2 3
1 0 3 2
1 3 0 2
1 3 2 0
1 2 0 3
1 2 3 0
2 0 3 1
2 0 1 3
2 1 0 3
2 1 3 0
2 3 0 1
2 3 1 0
3 0 2 1
3 0 1 2
3 1 0 2
3 1 2 0
3 2 0 1
3 2 1 0