Algorithm 给定词典索引的数值置换算法

Algorithm 给定词典索引的数值置换算法,algorithm,permutation,Algorithm,Permutation,我正在寻找一种算法,给定一组数字(例如1、2、3)和一个索引(例如2),将根据字典顺序得到这些数字的第二个排列。例如,在本例中,算法将返回1 3 2。,因为您没有指定要使用哪种语言 您只需要获得序列的第n个元素 该算法的一个想法可能是生成一个表示输入序列笛卡尔积的序列,并对其进行迭代,跳过具有重复元素的项 请注意,这可能不是最快的方法,但肯定是一种简单的方法。有关快速解决方案,请参见电子人的答案。这里有一个简单的解决方案: from math import factorial # python

我正在寻找一种算法,给定一组数字(例如1、2、3)和一个索引(例如2),将根据字典顺序得到这些数字的第二个排列。例如,在本例中,算法将返回1 3 2。

,因为您没有指定要使用哪种语言

您只需要获得序列的第n个元素

该算法的一个想法可能是生成一个表示输入序列笛卡尔积的序列,并对其进行迭代,跳过具有重复元素的项


请注意,这可能不是最快的方法,但肯定是一种简单的方法。有关快速解决方案,请参见电子人的答案。

这里有一个简单的解决方案:

from math import factorial # python math library

i = 5               # i is the lexicographic index (counting starts from 0)
n = 3               # n is the length of the permutation
p = range(1, n + 1) # p is a list from 1 to n

for k in range(1, n + 1): # k goes from 1 to n
    f = factorial(n - k)  # compute factorial once per iteration
    d = i // f            # use integer division (like division + floor)
    print(p[d]),          # print permuted number with trailing space
    p.remove(p[d])        # delete p[d] from p
    i = i % f             # reduce i to its remainder
输出:

3 2 1

如果
p
是一个列表,则时间复杂度为O(n^2);如果
p
是一个哈希表,并且预计算了
factorial
,则时间复杂度为O(n^2)。

这是Scala中的一个示例解决方案,我将详细解释:

/**
    example: index:=15, list:=(1, 2, 3, 4)
*/ 
def permutationIndex (index: Int, list: List [Int]) : List [Int] = 
  if (list.isEmpty) list else {
    val len = list.size     // len = 4
    val max = fac (len)     // max = 24
    val divisor = max / len // divisor = 6
    val i = index / divisor // i = 2
    val el = list (i)
    el :: permutationIndex (index - divisor * i, list.filter (_ != el)) }
因为Scala不是很有名,我想我必须解释算法的最后一行,除此之外,这是非常不言自明的

    el :: elist
从元素el和列表elist构造一个新列表。Elist是一个递归调用

    list.filter (_ != el)
只是没有元素el的列表

使用一个小列表对其进行彻底测试:

(0 to fac (4) - 1).map (permutationIndex (_, List (1, 2, 3, 4))).mkString ("\n")
使用两个示例测试更大列表的速度:

scala> permutationIndex (123456789, (1 to 12).toList) 
res45: List[Int] = List(4, 2, 1, 5, 12, 7, 10, 8, 11, 6, 9, 3)

scala> permutationIndex (123456790, (1 to 12).toList) 
res46: List[Int] = List(4, 2, 1, 5, 12, 7, 10, 8, 11, 9, 3, 6)
结果立即出现在一台使用了5年的笔记本电脑上。对于12个元素的列表,有479 001 600个排列,但是对于100或1000个元素,这个解决方案应该仍然工作得很快-您只需要使用BigInt作为索引

它是如何工作的?为了使示例可视化,我制作了一个图形,一个(1、2、3、4)列表和一个15的索引:

4个元素的列表产生4个!排列(=24)。我们选择从0到4的任意索引-1,比如说15

我们可以可视化树中的所有排列,第一个节点为1..4。我们除以4!由4可知,每个第一个节点指向6个子树。如果我们将索引15除以6,结果是2,索引为2的零基列表中的值是3。第一个节点是3,其余的节点是(1,2,4)。下面是一个表格,显示了15如何导致数组/列表/任何内容中索引为2的元素:

0 1 2 3 4 5 | 6 ... 11 | 12 13 14 15 16 17 | 18 ... 23
     0      |     1    |         2         |     3
            |          | 0  1  2  3  4  5  |
我们现在减去12,单元格的第一个元素(12…17),最后3个元素有6种可能的排列,看看15如何映射成3。数字3现在指向数组索引1,它是元素2,因此到目前为止的结果是列表(3,2,…)

同样,我们减去2,在剩下的2个元素中以2个置换结束,索引(0,3),映射到值(1,4)。我们看到,第二个元素属于从顶部算起的15,映射到值3,最后一步的剩余元素是另一个:

                             | 0 | 1 |
                             | 0 | 3 |
                             | 3 | 0 |

我们的结果是列表(3,2,4,1)或索引(2,1,3,0)。按顺序测试所有索引表明,它们按顺序生成所有排列

链接到提到的文章:

/*将置换索引转换为置换
*从“算法巷:随机洗牌”的代码中,
*多布博士期刊,第25卷,第1期(2000年1月)
* http://penguin.ewu.edu/~trolfe/#Shuffle
*
*作者:蒂姆·罗尔夫
*          RolfeT@earthlink.net
*          http://penguin.ewu.edu/~trolfe/
*/
#包括
#包括
// http://stackoverflow.com/questions/8940470/algorithm-for-finding-numerical-permutation-given-lexicographic-index
//反转排列索引——生成将要
//具有维数的N维数组中的下标
//[N][N-1][N-2]…[2][1]
void IndexInvert(int J[],int N,int Idx)
{int M,K;
对于(M=1,K=N-1;K>1;K--)//生成(N-1)!
M*=K;
对于(K=0;M>1;K++)
{J[K]=Idx/M;//维度K中的偏移量
Idx=Idx%M;//删除K个贡献
M/=--N;//下一个低阶乘
}
J[K]=Idx;//最右边的索引
}
//根据其索引/下标集生成置换。
//为了生成词典顺序,这涉及到_移位_
//字符交换,而不是交换。右手边必须
//保持字典顺序
void Permute(字符行[],字符优先,int N,int Jdx[])
{int极限;
第[0]行=第一行;
对于(限制=1;限制Limit)
{Line[Idx]=Line[Idx-1];
Idx-;
}
行[Idx]=保持;
}
}
//注:硬编码以在集合中生成置换[abc。。。
int main(int argc,字符**argv)
{int N=argc>1?原子(argv[1]):4;
char*Perm=(char*)calloc(N+1,sizeof*Perm);
int*Jdx=(int*)calloc(N,sizeof*Jdx);
int Index=argc>2?原子(argv[2]):23;
int K,验证;
对于(K=Validate=1;K=Validate)
{printf(“无效索引%d:%d!是%d\n”,索引,n,验证);
return-1;//返回错误
}
IndexInvert(Jdx,N,Index);
Permute(Perm'a',N,Jdx);
printf(“对于N=%d,[0..%d]中的置换%d是%s\N”,
N、 索引,Validate-1,Perm);
返回0;//成功返回
}

到目前为止您尝试了什么?一种简单的方法是生成所有排列,按字典顺序排列,然后选择第n个排列。提示:首先找到确定第n个排列的第一位数字的算法。相关提示:尝试搜索“阶乘基”和“Lehmer代码”。此解决方案需要O(k!)时间虽然这是最好的答案,但list.remove()在python中不是O(1),因此这个解决方案是Θ(n^2)。可以通过使用BST而不是列表将其缩减为Θ(n log n)。这是假设阶乘()是O(1)(不是)。
                             | 0 | 1 |
                             | 0 | 3 |
                             | 3 | 0 |
/* Converting permutation index into a permutation
 * From code accompanying "Algorithm Alley: Randomized Shuffling",
 * Dr. Dobb’s Journal, Vol. 25, No. 1  (January 2000)
 * http://penguin.ewu.edu/~trolfe/#Shuffle
 *
 * Author:  Tim Rolfe
 *          RolfeT@earthlink.net
 *          http://penguin.ewu.edu/~trolfe/
 */
#include <stdio.h>
#include <stdlib.h>

// http://stackoverflow.com/questions/8940470/algorithm-for-finding-numerical-permutation-given-lexicographic-index

// Invert the permutation index --- generate what would be
// the subscripts in the N-dimensional array with dimensions
// [N][N-1][N-2]...[2][1]
void IndexInvert(int J[], int N, int Idx)
{  int M, K;

   for (M=1, K=N-1; K > 1; K--)   // Generate (N-1)!
      M *= K;
   for ( K = 0; M > 1; K++ )
   {  J[K] = Idx / M;   // Offset in dimension K
      Idx = Idx % M;    // Remove K contribution
      M /= --N;         // next lower factorial
   }
   J[K] = Idx;          // Right-most index
}

// Generate a permutation based on its index / subscript set.
// To generate the lexicographic order, this involves _shifting_
// characters around rather than swapping.  Right-hand side must
// remain in lexicographic order
void Permute (char Line[], char first, int N, int Jdx[])
{  int Limit;

   Line[0] = first;
   for (Limit = 1; Limit < N; Limit++)
      Line[Limit] = (char)(1+Line[Limit-1]);

   for (Limit = 0; Limit < N; Limit++)
   {  char Hold;
      int Idx = Limit + Jdx[Limit];
      Hold = Line[Idx];
      while (Idx > Limit)
      {  Line[Idx] = Line[Idx-1];
         Idx--;
      }
      Line[Idx] = Hold;
   }
}

// Note:  hard-coded to generate permutation in the set [abc...
int main(int argc, char** argv)
{  int   N = argc > 1 ? atoi(argv[1]) : 4;
   char *Perm  = (char*) calloc(N+1, sizeof *Perm);
   int  *Jdx   = (int*)  calloc(N, sizeof *Jdx);
   int   Index = argc > 2 ? atoi(argv[2]) : 23;
   int   K, Validate;

   for (K = Validate = 1; K <= N; K++)
      Validate *= K;
   if (Index < 0 || Index >= Validate)
   {  printf("Invalid index %d:  %d! is %d\n", Index, N, Validate);
      return -1;   // Error return
   }
   IndexInvert(Jdx, N, Index);
   Permute (Perm, 'a', N, Jdx);
   printf("For N = %d, permutation %d in [0..%d] is %s\n",
          N, Index, Validate-1, Perm);
   return 0;       // Success return
}