快速排列 -> 数字 -> 排列映射算法
Posted
技术标签:
【中文标题】快速排列 -> 数字 -> 排列映射算法【英文标题】:Fast permutation -> number -> permutation mapping algorithms 【发布时间】:2010-12-03 03:03:26 【问题描述】:我有 n 个元素。举个例子,假设有 7 个元素,1234567。我知道有 7 个! = 这 7 个元素可能有 5040 种排列。
我想要一个包含两个函数的快速算法:
f(number) 将 0 到 5039 之间的数字映射到唯一的排列,并且
f'(permutation) 将排列映射回生成它的数字。
我不关心数字和排列之间的对应关系,只要每个排列都有自己唯一的数字。
所以,例如,我可能有函数
f(0) = '1234567'
f'('1234567') = 0
想到的最快算法是枚举所有排列并在两个方向上创建一个查找表,这样,一旦创建表,f(0) 将是 O(1) 和 f('1234567')将是对字符串的查找。但是,这会占用大量内存,尤其是当 n 变大时。
谁能提出另一种可以快速运行且没有内存缺点的算法?
【问题讨论】:
虽然下面的算法很全面,但你正确指出最快的算法是查找表。您真的不是在谈论“那么多”内存,尽管这当然取决于您的系统和平台。但是,如果查找表就足够了,并且如果这是一个真实世界的应用程序,那就使用它。快速简单! 你这么说,但 n 不一定要变得非常大才会很傻。对于 12 个元素,12 个!是 479,001,600 个排列。这是一个很大的查找表! 不要被不同的帖子混淆,使用 n 表示不同的含义。有些 n 代表字符串长度,有些 n 代表可能排列的计数。不要盲目比较大 O 的概念。 -- 迟到者请注意 -- -- 【参考方案1】:要描述 n 个元素的排列,您会看到对于第一个元素结束的位置,您有 n 个可能性,因此您可以用 0 到 n-1 之间的数字来描述它。对于下一个元素结束的位置,您有 n-1 个剩余可能性,因此您可以用 0 到 n-2 之间的数字来描述它。 等等,直到你有 n 个数字。
以 n = 5 为例,考虑将abcde
带到caebd
的排列。
a
,第一个元素,在第二个位置结束,所以我们为其分配索引1。
b
最终位于第四个位置,即索引 3,但它是剩下的第三个,因此我们将其分配为 2。
c
最终位于第一个剩余位置,始终为 0。
d
结束于最后一个剩余位置,(仅剩下的两个位置中)是 1。
e
结束于唯一剩余的位置,索引为 0。
所以我们有索引序列1, 2, 0, 1, 0。
现在您知道,例如在二进制数中,“xyz”表示 z + 2y + 4x。对于十进制数, 它是 z + 10y + 100x。每个数字乘以某个权重,然后将结果相加。权重中明显的模式当然是权重是 w = b^k,其中 b 是数字的基数,k 是数字的索引。 (我总是从最右边开始计算数字,从索引 0 开始计算最右边的数字。同样,当我谈到“第一个”数字时,我指的是最右边的数字。)
数字的权重遵循这种模式的原因是,从 0 到 k 的数字可以表示的最大数字必须恰好比可以表示的最小数字低 1仅使用数字 k+1。在二进制中,0111 必须比 1000 小 1。在十进制中,099999 必须比 100000 小 1。
编码为可变基数 后续数字之间的间距恰好为 1 是重要规则。意识到这一点,我们可以用一个可变基数来表示我们的索引序列。每个数字的基数是该数字的不同可能性的数量。对于十进制,每个数字都有 10 种可能性,对于我们的系统,最右边的数字有 1 种可能性,最左边的数字有 n 种可能性。但由于最右边的数字(我们序列中的最后一个数字)始终为 0,因此我们将其省略。这意味着我们剩下以 2 到 n 为基数。一般来说,第 k 个数字的底数为 b[k] = k + 2。数字 k 允许的最大值是 h[k] = b[k] - 1 = k + 1。
我们关于数字权重 w[k] 的规则要求 h[i] * w[i] 的总和,其中 i 从 i = 0 到 i = k,等于 1 * w[k+ 1]。反复声明,w[k+1] = w[k] + h[k] * w[k] = w[k]*(h[k] + 1)。第一个权重 w[0] 应该始终为 1。从那里开始,我们有以下值:
k h[k] w[k]
0 1 1
1 2 2
2 3 6
3 4 24
... ... ...
n-1 n n!
(一般关系 w[k-1] = k! 很容易通过归纳证明。)
我们通过转换序列得到的数字将是 s[k] * w[k] 的总和,其中 k 从 0 到 n-1。这里 s[k] 是序列的第 k 个(最右边,从 0 开始)元素。以我们的 1, 2, 0, 1, 0 为例,最右边的元素如前所述被剥离:1, 2, 0, 1。我们的总和是 1 * 1 + 0 * 2 + 2 * 6 + 1 * 24 = 37。
请注意,如果我们为每个索引取最大位置,我们将得到 4, 3, 2, 1, 0,然后转换为 119。由于我们选择了数字编码中的权重,因此我们不会'不要跳过任何数字,所有数字 0 到 119 都有效。其中正好有 120 个,即 n!对于我们示例中的 n = 5,恰好是不同排列的数量。所以你可以看到我们的编码数字完全指定了所有可能的排列。
从变量库解码 解码类似于转换为二进制或十进制。常用算法是这样的:
int number = 42;
int base = 2;
int[] bits = new int[n];
for (int k = 0; k < bits.Length; k++)
bits[k] = number % base;
number = number / base;
对于我们的可变基数:
int n = 5;
int number = 37;
int[] sequence = new int[n - 1];
int base = 2;
for (int k = 0; k < sequence.Length; k++)
sequence[k] = number % base;
number = number / base;
base++; // b[k+1] = b[k] + 1
这会将我们的 37 正确解码为 1, 2, 0, 1(在此代码示例中,sequence
将是 1, 0, 2, 1
,但无论如何...只要您正确索引)。我们只需要在右端添加 0(请记住,最后一个元素的新位置总是只有一种可能性)来恢复我们的原始序列 1,2,0,1,0。
使用索引序列排列列表 您可以使用以下算法根据特定的索引序列排列列表。不幸的是,这是一个 O(n²) 算法。
int n = 5;
int[] sequence = new int[] 1, 2, 0, 1, 0 ;
char[] list = new char[] 'a', 'b', 'c', 'd', 'e' ;
char[] permuted = new char[n];
bool[] set = new bool[n];
for (int i = 0; i < n; i++)
int s = sequence[i];
int remainingPosition = 0;
int index;
// Find the s'th position in the permuted list that has not been set yet.
for (index = 0; index < n; index++)
if (!set[index])
if (remainingPosition == s)
break;
remainingPosition++;
permuted[index] = list[i];
set[index] = true;
排列的常见表示
通常,您不会像我们所做的那样不直观地表示排列,而只是通过应用排列后每个元素的绝对位置来表示。对于abcde
到caebd
,我们的示例 1, 2, 0, 1, 0 通常由 1, 3, 0, 4, 2 表示。从 0 到 4(或通常为 0 到 n-1)的每个索引在此表示中只出现一次。
以这种形式应用排列很容易:
int[] permutation = new int[] 1, 3, 0, 4, 2 ;
char[] list = new char[] 'a', 'b', 'c', 'd', 'e' ;
char[] permuted = new char[n];
for (int i = 0; i < n; i++)
permuted[permutation[i]] = list[i];
反转它非常相似:
for (int i = 0; i < n; i++)
list[i] = permuted[permutation[i]];
从我们的表示转换为通用表示 请注意,如果我们使用我们的算法使用索引序列排列列表,并将其应用于恒等排列 0, 1, 2, ..., n-1,我们将得到 inverse排列,以常见的形式表示。 (在我们的示例中为 2, 0, 4, 1, 3)。
为了得到不倒置的前置换,我们应用我刚刚展示的置换算法:
int[] identity = new int[] 0, 1, 2, 3, 4 ;
int[] inverted = 2, 0, 4, 1, 3 ;
int[] normal = new int[n];
for (int i = 0; i < n; i++)
normal[identity[i]] = list[i];
或者你可以直接应用排列,通过使用逆排列算法:
char[] list = new char[] 'a', 'b', 'c', 'd', 'e' ;
char[] permuted = new char[n];
int[] inverted = 2, 0, 4, 1, 3 ;
for (int i = 0; i < n; i++)
permuted[i] = list[inverted[i]];
请注意,所有处理普通形式排列的算法都是 O(n),而以我们的形式应用排列是 O(n²)。如果您需要多次应用排列,请先将其转换为通用表示。
【讨论】:
在“使用索引序列排列列表”中,您提到了二次算法。这当然很好,因为 n 可能会非常小。不过,这可以通过订单统计树(pine.cs.yale.edu/pinewiki/OrderStatisticsTree)“轻松”减少到 O(nlogn),即最初将包含值 0、1、2、...、n-1 的红黑树,并且每个节点都包含它下面的后代数量。有了这个,可以在 O(logn) 时间内找到/删除第 k 个元素。 这些被称为莱默码。这个链接也很好地解释了它们,keithschwarz.com/interesting/code/?dir=factoradic-permutation 这个算法很棒,但我发现有几个案例是错误的。取字符串“123”;第 4 次排列应该是 231,但根据这个算法,它会是 312。比如说 1234,第 4 次排列应该是 1342,但会被误认为是“1423”。如果我观察到错误,请纠正我。谢谢。 @IsaacLi,如果我是正确的,f(4) = 2, 0, 0 = 231。而 f'(312) = 1, 1, 0 = 3。对于@ 987654342@, f(4) = 0, 2, 0, 0 = 1342。而 f'(1423) = 0, 1 1, 0 = 3。这个算法真的很鼓舞人心。我想知道这是OP的原创作品。我研究和分析了一段时间。我相信这是正确的:) 如何将“我们的代表”转换为“共同的代表”,1, 2, 0, 1, 0
--> 1, 3, 0, 4, 2
?反之亦然?可能吗? (通过 not 在 1, 2, 0, 1, 0
C, A, E, B, D
之间转换,这需要 O(n^2)。)如果“我们的风格”和“普通风格”不可转换,它们在实际上是两个不同的独立事物,不是吗?谢谢x【参考方案2】:
我找到了一个 O(n) 算法,这里有一个简短的解释http://antoinecomeau.blogspot.ca/2014/07/mapping-between-permutations-and.html
public static int[] perm(int n, int k)
int i, ind, m=k;
int[] permuted = new int[n];
int[] elems = new int[n];
for(i=0;i<n;i++) elems[i]=i;
for(i=0;i<n;i++)
ind=m%(n-i);
m=m/(n-i);
permuted[i]=elems[ind];
elems[ind]=elems[n-i-1];
return permuted;
public static int inv(int[] perm)
int i, k=0, m=1;
int n=perm.length;
int[] pos = new int[n];
int[] elems = new int[n];
for(i=0;i<n;i++) pos[i]=i; elems[i]=i;
for(i=0;i<n-1;i++)
k+=m*pos[perm[i]];
m=m*(n-i);
pos[elems[n-i-1]]=pos[perm[i]];
elems[pos[perm[i]]]=elems[n-i-1];
return k;
【讨论】:
如果我非常了解您的算法。您正在寻找编码的所有可能性(在这种情况下,它应该是 n! 可能性)。然后根据编码项映射数字。 我在我的博客上添加了一个简短的解释。 这非常整洁。我今天自己想出了同样的方法,但我错过了你可以在逆向中省略两个作业。 不要盲目比较大 O 的概念,因为这个答案中的 n 代表与其他一些答案不同 - 正如@user3378649 指出的那样 - 表示复杂度与字符串长度的阶乘成比例.这个答案确实效率较低。 这可以适应字典顺序吗?【参考方案3】:复杂度可以降低到 n*log(n),见第 10.1.1 节 ("The Lehmer code (inversion table)", p.232ff) 的 fxtbook: http://www.jjj.de/fxt/#fxtbook 对于快速方法,请跳至第 10.1.1.1 节(“使用大型数组计算”第 235 页)。 (GPLed, C++) 代码在同一个网页上。
【讨论】:
【参考方案4】:问题解决了。但是,我不确定这些年后您是否仍然需要该解决方案。大声笑,我刚加入这个网站,所以...... 检查我的 Java 排列类。您可以根据索引获取符号排列,或者给出符号排列然后获取索引。
这是我的 Premutation 类
/**
****************************************************************************************************************
* Copyright 2015 Fred Pang fred@pnode.com
****************************************************************************************************************
* A complete list of Permutation base on an index.
* Algorithm is invented and implemented by Fred Pang fred@pnode.com
* Created by Fred Pang on 18/11/2015.
****************************************************************************************************************
* LOL this is my first Java project. Therefore, my code is very much like C/C++. The coding itself is not
* very professional. but...
*
* This Permutation Class can be use to generate a complete list of all different permutation of a set of symbols.
* nPr will be n!/(n-r)!
* the user can input n = the number of items,
* r = the number of slots for the items,
* provided n >= r
* and a string of single character symbols
*
* the program will generate all possible permutation for the condition.
*
* Say if n = 5, r = 3, and the string is "12345", it will generate sll 60 different permutation of the set
* of 3 character strings.
*
* The algorithm I used is base on a bin slot.
* Just like a human or simply myself to generate a permutation.
*
* if there are 5 symbols to chose from, I'll have 5 bin slot to indicate which symbol is taken.
*
* Note that, once the Permutation object is initialized, or after the constructor is called, the permutation
* table and all entries are defined, including an index.
*
* eg. if pass in value is 5 chose 3, and say the symbol string is "12345"
* then all permutation table is logically defined (not physically to save memory).
* It will be a table as follows
* index output
* 0 123
* 1 124
* 2 125
* 3 132
* 4 134
* 5 135
* 6 143
* 7 145
* : :
* 58 542
* 59 543
*
* all you need to do is call the "String PermGetString(int iIndex)" or the "int[] PermGetIntArray(int iIndex)"
* function or method with an increasing iIndex, starting from 0 to getiMaxIndex() - 1. It will return the string
* or the integer array corresponding to the index.
*
* Also notice that in the input string is "12345" of position 01234, and the output is always in accenting order
* this is how the permutation is generated.
*
* ***************************************************************************************************************
* ==== W a r n i n g ====
* ***************************************************************************************************************
*
* There is very limited error checking in this class
*
* Especially the int PermGetIndex(int[] iInputArray) method
* if the input integer array contains invalid index, it WILL crash the system
*
* the other is the string of symbol pass in when the object is created, not sure what will happen if the
* string is invalid.
* ***************************************************************************************************************
*
*/
public class Permutation
private boolean bGoodToGo = false; // object status
private boolean bNoSymbol = true;
private BinSlot slot; // a bin slot of size n (input)
private int nTotal; // n number for permutation
private int rChose; // r position to chose
private String sSymbol; // character string for symbol of each choice
private String sOutStr;
private int iMaxIndex; // maximum index allowed in the Get index function
private int[] iOutPosition; // output array
private int[] iDivisorArray; // array to do calculation
public Permutation(int inCount, int irCount, String symbol)
if (inCount >= irCount)
// save all input values passed in
this.nTotal = inCount;
this.rChose = irCount;
this.sSymbol = symbol;
// some error checking
if (inCount < irCount || irCount <= 0)
return; // do nothing will not set the bGoodToGo flag
if (this.sSymbol.length() >= inCount)
bNoSymbol = false;
// allocate output storage
this.iOutPosition = new int[this.rChose];
// initialize the bin slot with the right size
this.slot = new BinSlot(this.nTotal);
// allocate and initialize divid array
this.iDivisorArray = new int[this.rChose];
// calculate default values base on n & r
this.iMaxIndex = CalPremFormula(this.nTotal, this.rChose);
int i;
int j = this.nTotal - 1;
int k = this.rChose - 1;
for (i = 0; i < this.rChose; i++)
this.iDivisorArray[i] = CalPremFormula(j--, k--);
bGoodToGo = true; // we are ready to go
public String PermGetString(int iIndex)
if (!this.bGoodToGo) return "Error: Object not initialized Correctly";
if (this.bNoSymbol) return "Error: Invalid symbol string";
if (!this.PermEvaluate(iIndex)) return "Invalid Index";
sOutStr = "";
// convert string back to String output
for (int i = 0; i < this.rChose; i++)
String sTempStr = this.sSymbol.substring(this.iOutPosition[i], iOutPosition[i] + 1);
this.sOutStr = this.sOutStr.concat(sTempStr);
return this.sOutStr;
public int[] PermGetIntArray(int iIndex)
if (!this.bGoodToGo) return null;
if (!this.PermEvaluate(iIndex)) return null ;
return this.iOutPosition;
// given an int array, and get the index back.
//
// ====== W A R N I N G ======
//
// there is no error check in the array that pass in
// if any invalid value in the input array, it can cause system crash or other unexpected result
//
// function pass in an int array generated by the PermGetIntArray() method
// then return the index value.
//
// this is the reverse of the PermGetIntArray()
//
public int PermGetIndex(int[] iInputArray)
if (!this.bGoodToGo) return -1;
return PermDoReverse(iInputArray);
public int getiMaxIndex()
return iMaxIndex;
// function to evaluate nPr = n!/(n-r)!
public int CalPremFormula(int n, int r)
int j = n;
int k = 1;
for (int i = 0; i < r; i++, j--)
k *= j;
return k;
// PermEvaluate function (method) base on an index input, evaluate the correspond permuted symbol location
// then output it to the iOutPosition array.
//
// In the iOutPosition[], each array element corresponding to the symbol location in the input string symbol.
// from location 0 to length of string - 1.
private boolean PermEvaluate(int iIndex)
int iCurrentIndex;
int iCurrentRemainder;
int iCurrentValue = iIndex;
int iCurrentOutSlot;
int iLoopCount;
if (iIndex >= iMaxIndex)
return false;
this.slot.binReset(); // clear bin content
iLoopCount = 0;
do
// evaluate the table position
iCurrentIndex = iCurrentValue / this.iDivisorArray[iLoopCount];
iCurrentRemainder = iCurrentValue % this.iDivisorArray[iLoopCount];
iCurrentOutSlot = this.slot.FindFreeBin(iCurrentIndex); // find an available slot
if (iCurrentOutSlot >= 0)
this.iOutPosition[iLoopCount] = iCurrentOutSlot;
else return false; // fail to find a slot, quit now
this.slot.setStatus(iCurrentOutSlot); // set the slot to be taken
iCurrentValue = iCurrentRemainder; // set new value for current value.
iLoopCount++; // increase counter
while (iLoopCount < this.rChose);
// the output is ready in iOutPosition[]
return true;
//
// this function is doing the reverse of the permutation
// the input is a permutation and will find the correspond index value for that entry
// which is doing the opposit of the PermEvaluate() method
//
private int PermDoReverse(int[] iInputArray)
int iReturnValue = 0;
int iLoopIndex;
int iCurrentValue;
int iBinLocation;
this.slot.binReset(); // clear bin content
for (iLoopIndex = 0; iLoopIndex < this.rChose; iLoopIndex++)
iCurrentValue = iInputArray[iLoopIndex];
iBinLocation = this.slot.BinCountFree(iCurrentValue);
this.slot.setStatus(iCurrentValue); // set the slot to be taken
iReturnValue = iReturnValue + iBinLocation * this.iDivisorArray[iLoopIndex];
return iReturnValue;
/*******************************************************************************************************************
*******************************************************************************************************************
* Created by Fred on 18/11/2015. fred@pnode.com
*
* *****************************************************************************************************************
*/
private static class BinSlot
private int iBinSize; // size of array
private short[] eStatus; // the status array must have length iBinSize
private BinSlot(int iBinSize)
this.iBinSize = iBinSize; // save bin size
this.eStatus = new short[iBinSize]; // llocate status array
// reset the bin content. no symbol is in use
private void binReset()
// reset the bin's content
for (int i = 0; i < this.iBinSize; i++) this.eStatus[i] = 0;
// set the bin position as taken or the number is already used, cannot be use again.
private void setStatus(int iIndex) this.eStatus[iIndex]= 1;
//
// to search for the iIndex th unused symbol
// this is important to search through the iindex th symbol
// because this is how the table is setup. (or the remainder means)
// note: iIndex is the remainder of the calculation
//
// for example:
// in a 5 choose 3 permutation symbols "12345",
// the index 7 item (count starting from 0) element is "1 4 3"
// then comes the index 8, 8/12 result 0 -> 0th symbol in symbol string = '1'
// remainder 8. then 8/3 = 2, now we need to scan the Bin and skip 2 unused bins
// current the bin looks 0 1 2 3 4
// x o o o o x -> in use; o -> free only 0 is being used
// s s ^ skipped 2 bins (bin 1 and 2), we get to bin 3
// and bin 3 is the bin needed. Thus symbol "4" is pick
// in 8/3, there is a remainder 2 comes in this function as 2/1 = 2, now we have to pick the empty slot
// for the new 2.
// the bin now looks 0 1 2 3 4
// x 0 0 x 0 as bin 3 was used by the last value
// s s ^ we skip 2 free bins and the next free bin is bin 4
// therefor the symbol "5" at the symbol array is pick.
//
// Thus, for index 8 "1 4 5" is the symbols.
//
//
private int FindFreeBin(int iIndex)
int j = iIndex;
if (j < 0 || j > this.iBinSize) return -1; // invalid index
for (int i = 0; i < this.iBinSize; i++)
if (this.eStatus[i] == 0) // is it used
// found an empty slot
if (j == 0) // this is a free one we want?
return i; // yes, found and return it.
else // we have to skip this one
j--; // else, keep looking and count the skipped one
assert(true); // something is wrong
return -1; // fail to find the bin we wanted
//
// this function is to help the PermDoReverse() to find out what is the corresponding
// value during should be added to the index value.
//
// it is doing the opposite of int FindFreeBin(int iIndex) method. You need to know how this
// FindFreeBin() works before looking into this function.
//
private int BinCountFree(int iIndex)
int iRetVal = 0;
for (int i = iIndex; i > 0; i--)
if (this.eStatus[i-1] == 0) // it is free
iRetVal++;
return iRetVal;
// End of file - Permutation.java
这是我的主类,用于展示如何使用该类。
/*
* copyright 2015 Fred Pang
*
* This is the main test program for testing the Permutation Class I created.
* It can be use to demonstrate how to use the Permutation Class and its methods to generate a complete
* list of a permutation. It also support function to get back the index value as pass in a permutation.
*
* As you can see my Java is not very good. :)
* This is my 1st Java project I created. As I am a C/C++ programmer for years.
*
* I still have problem with the Scanner class and the System class.
* Note that there is only very limited error checking
*
*
*/
import java.util.Scanner;
public class Main
private static Scanner scanner = new Scanner(System.in);
public static void main(String[] args)
Permutation perm; // declear the object
String sOutString = "";
int nCount;
int rCount;
int iMaxIndex;
// Get user input
System.out.println("Enter n: ");
nCount = scanner.nextInt();
System.out.println("Enter r: ");
rCount = scanner.nextInt();
System.out.println("Enter Symbol: ");
sOutString = scanner.next();
if (sOutString.length() < rCount)
System.out.println("String too short, default to numbers");
sOutString = "";
// create object with user requirement
perm = new Permutation(nCount, rCount, sOutString);
// and print the maximum count
iMaxIndex = perm.getiMaxIndex();
System.out.println("Max count is:" + iMaxIndex);
if (!sOutString.isEmpty())
for (int i = 0; i < iMaxIndex; i++)
// print out the return permutation symbol string
System.out.println(i + " " + perm.PermGetString(i));
else
for (int i = 0; i < iMaxIndex; i++)
System.out.print(i + " ->");
// Get the permutation array
int[] iTemp = perm.PermGetIntArray(i);
// print out the permutation
for (int j = 0; j < rCount; j++)
System.out.print(' ');
System.out.print(iTemp[j]);
// to verify my PermGetIndex() works. :)
if (perm.PermGetIndex(iTemp)== i)
System.out.println(" .");
else
// oops something is wrong :(
System.out.println(" ***************** F A I L E D *************************");
assert(true);
break;
//
// End of file - Main.java
玩得开心。 :)
【讨论】:
【参考方案5】:每个元素都可以位于七个位置之一。要描述一个元素的位置,您需要三个位。这意味着您可以将所有元素的位置存储在 32 位值中。这远非有效,因为这种表示甚至允许所有元素位于同一位置,但我相信位掩码应该相当快。
但是,如果有超过 8 个职位,您需要一些更漂亮的东西。
【讨论】:
这假设 OP 不在乎枚举是否真的从 0 变为 5039,对吧?如果没问题,那么这似乎是一个很好的解决方案。【参考方案6】:这恰好是J中的内置函数:
A. 1 2 3 4 5 6 7
0
0 A. 1 2 3 4 5 6 7
1 2 3 4 5 6 7
?!7
5011
5011 A. 1 2 3 4 5 6 7
7 6 4 5 1 3 2
A. 7 6 4 5 1 3 2
5011
【讨论】:
【参考方案7】:您可以使用递归算法对排列进行编码。如果 N 排列(数字 0,..,N-1 的某种排序)的形式为 x, ...,则将其编码为 x + N * (N-1) 的编码-在数字 0, N-1 - x 上用“...”表示的排列。听起来很拗口,这里有一些代码:
// perm[0]..perm[n-1] must contain the numbers in 0,..,n-1 in any order.
int permToNumber(int *perm, int n)
// base case
if (n == 1) return 0;
// fix up perm[1]..perm[n-1] to be a permutation on 0,..,n-2.
for (int i = 1; i < n; i++)
if (perm[i] > perm[0]) perm[i]--;
// recursively compute
return perm[0] + n * permToNumber(perm + 1, n - 1);
// number must be >=0, < n!
void numberToPerm(int number, int *perm, int n)
if (n == 1)
perm[0] = 0;
return;
perm[0] = number % n;
numberToPerm(number / n, perm + 1, n - 1);
// fix up perm[1] .. perm[n-1]
for (int i = 1; i < n; i++)
if (perm[i] >= perm[0]) perm[i]++;
这个算法是 O(n^2)。如果有人有 O(n) 算法,则可以加分。
【讨论】:
【参考方案8】:多么有趣的问题!
如果您的所有元素都是数字,您可能需要考虑将它们从字符串转换为实际数字。然后,您可以通过将它们按顺序排列来对所有排列进行排序,并将它们放在一个数组中。之后,您就可以接受各种搜索算法中的任何一种了。
【讨论】:
【参考方案9】:我之前的答案很仓促(已删除),但我确实有实际答案。它由类似的概念factoradic 提供,并且与排列有关(我的回答与组合有关,我为这种混淆道歉)。我讨厌只发布***链接,但由于某种原因,我不久前写的文章难以理解。因此,如果需要,我可以稍后再进行扩展。
【讨论】:
【参考方案10】:有一本书是关于这个的。对不起,但我不记得它的名字(你很可能会从***找到它)。 但无论如何我写了一个枚举系统的python实现:http://kks.cabal.fi/Kombinaattori 其中一些是芬兰语,但只需复制代码和名称变量...
【讨论】:
【参考方案11】:我有这个确切的问题,并认为我会提供我的 Python 解决方案。是 O(n^2)。
import copy
def permute(string, num):
''' generates a permutation '''
def build_s(factoradic): # Build string from factoradic in list form
string0 = copy.copy(string)
n = []
for i in range(len(factoradic)):
n.append(string0[factoradic[i]])
del string0[factoradic[i]]
return n
f = len(string)
factoradic = []
while(f != 0): # Generate factoradic number list
factoradic.append(num % f)
num = (num - factoradic[-1])//f
f -= 1
return build_s(factoradic)
s = set()
# Print 120 permutations of this string
for i in range(120):
m = permute(list('abcde'), i)
s.add(''.join(m))
print(len(s)) # Check that we have 120 unique permutations
这很简单;在生成数字的阶乘表示后,我只需从字符串中挑选并删除字符。从字符串中删除是为什么这是一个 O(n^2) 解决方案。
Antoine 的解决方案在性能方面更好。
【讨论】:
【参考方案12】:一个相关的问题是计算逆排列,当只有排列数组已知时,这种排列会将排列的向量恢复到原始顺序。这是 O(n) 代码(在 php 中):
// Compute the inverse of a permutation
function GetInvPerm($Perm)
$n=count($Perm);
$InvPerm=[];
for ($i=0; $i<$n; ++$i)
$InvPerm[$Perm[$i]]=$i;
return $InvPerm;
// GetInvPerm
大卫斯佩克特 春天软件
【讨论】:
以上是关于快速排列 -> 数字 -> 排列映射算法的主要内容,如果未能解决你的问题,请参考以下文章