通过反复循环3个元素来查找排列

时间:2016-01-16 17:07:08

标签: algorithm permutation combinatorics

是否有算法可以查找一系列独特元素的所有可能排列,遵循此规则?

从给定的排列中,必须通过正好循环3个元素来找到下一个排列。它们可以是任何三个元素。

使用这样的3个周期,只能找到所有排列的子集,但是 应找到所有可能通过3个周期到达的那些,并且在找到所有可到达的排列之前,不应找到相同的排列两次。

以下是输入示例:

1,2,3,4,5

输出可能是:

3,1,2,4,5
2,3,1,4,5
4,2,1,3,5
3,4,1,2,5
1,3,4,2,5
4,1,3,2,5
2,4,3,1,5

......等等。

我尝试生成这样一个序列的众多算法之一如下(对于数组 a 和length n ):

print (a)
for i = 0 to n-1
    for j = i+1 to n-1
        for l = j+2 to n-1 
            for k = j+1 to l 
                cycle a[i],a[j],a[k]
                print (a)
                cycle a[i],a[j],a[k]
                print (a)

这会生成上面打印的系列,但接着继续:

1,2,3,4,5

..这是已经输出的排列。到目前为止我找到的任何其他找到下一个3周期的算法都找不到所有可达排列。

4 个答案:

答案 0 :(得分:7)

有一篇旧论文 V. L. Kompel'makher and V. A. Liskovets. Sequential generation of arrangements by a basis of transpositions.,它表明你可以通过简单的换位生成所有排列,并且每个换位都必须将置换的第一个元素与任何其他元素交换(所谓的星形基础) )。例如对于S(3),因为第一个元素(与元素1相对)在每一步都被交换。

123->213->312->132->231->321->[123, Hamiltonian cycle!]

还有一种类似的方法A `Hot Potato' Gray Code for Permutations,它不在付费墙后面。本文的一个重要见解是,即使必须交换每个转置元素1,仍然可以生成所有排列而不重复(每一步都交换元素1):

123->213->231->132->312->321->[123, Hamiltonian cycle!]

循环通过星形基础的所有排列的另一种算法是来自Knuths" The Art of computer programming",Chapter" Generating all permutations&#34 ;.算法被称为" Ehrlich的交换方法"。我没有声称理解那里发生了什么,它只是将算法转换为java。最有趣的部分就是这一行:

    //swap values to get next permutation:
    swap(per,0,b[k]);

在每个步骤中都有一个转置,并且在每个转置中交换元素[0]( - >星形基础)。

import java.util.Arrays;

public class EhrlichPermuter {
    //Follows Knuths "The Art of computer programming", Chapter "Generating all permutations",  "Ehrlich's swap method".
    int n;
    int[] c;
    int[] b;
    int[] per;

    boolean done;

    void initialize(){
        c=new int[n];
        b=new int[n];
        per=new int[n];
        for(int j=0;j<n;j++){
            b[j]=j;
            per[j]=j;
        }
    }

    EhrlichPermuter(int n){
        this.n=n;
        initialize();
    }

    void swap(int[] a, int i, int j){
        int h=a[i];a[i]=a[j];a[j]=h;
    }

    int[] getNextPermut(){
        int[] result=Arrays.copyOf(per, per.length);//remember permutation

        int k=1;
        while(c[k]>=k){
            c[k]=0;
            k++;
            if(k==n){
                done=true;
                initialize();//we got all permutations so far
                return result;//return the last permutation
            }
        }
        c[k]=c[k]+1;

        //swap values to get next permutation:
        swap(per,0,b[k]);

        //flip:
        int j=1; k--;
        while(j<k){
            swap(b,j,k);
            j++;
            k--;
        }

        return result;//return remembered permutation
    }
}

现在已经完成了很多事情!

最后一步是:形式(1a)(1b)的任何两个连续转置可以写为3个元素周期(1ab)。因此,您只需跳过具有负奇偶校验的排列。对于Hot-Potato,这看起来如下

123 --(213)-->231--(132)-->312--(321)-->[123, Hamiltonian cycle!]

跳过()中的排列。

答案 1 :(得分:2)

在我之前对这个问题的回答中,我描述了一种方法来查找相同方向的3元素旋转序列,这些旋转将生成N个元素的所有(可到达的)排列。我在这个方法中找到的最简单的序列用于下面的实现。每个元素的旋转显示重复模式,仅对N的奇数/偶数值不同;这意味着可以很容易地为任意数量的元素生成旋转序列。

N=3: (0,1,2) (0,1,2)  
N=4: (0,1,3) (1,2,3) (1,2,3)
N=5: (0,3,4) (0,3,4) (0,3,4) (0,3,4)
N=6: (0,1,5) (0,2,5) (0,2,5) (0,2,5) (0,2,5)
N=7: (0,5,6) (0,5,6) (0,5,6) (0,5,6) (0,5,6) (0,5,6)
N=8: (0,1,7) (0,2,7) (0,3,7) (0,4,7) (0,4,7) (0,4,7) (0,4,7)
...

从5个元素开始,你会发现这种反复出现的模式:

N=odd:  (0,N-2,N-1) (0,N-2,N-1) (0,N-2,N-1) ...  
N=even: (0,1,N-1) (0,2,N-1) (0,3,N-1) ... (0,N-4,N-1) (0,N-4,N-1) (0,N-4,N-1) (0,N-4,N-1)

或以图形方式,<表示旋转元素的位置:

N=3  N=4  N=5     N=6     N=7       N=8       N=9        N=10         N=11         N=12
<<< <<=< <==<<  <<===<  <====<<  <<=====<  <======<<  <<=======<  <========<<  <<=========<
<<< =<<< <==<<  <=<==<  <====<<  <=<====<  <======<<  <=<======<  <========<<  <=<========<
    =<<< <==<<  <=<==<  <====<<  <==<===<  <======<<  <==<=====<  <========<<  <==<=======<
         <==<<  <=<==<  <====<<  <===<==<  <======<<  <===<====<  <========<<  <===<======<
                <=<==<  <====<<  <===<==<  <======<<  <====<===<  <========<<  <====<=====<
                        <====<<  <===<==<  <======<<  <=====<==<  <========<<  <=====<====<
                                 <===<==<  <======<<  <=====<==<  <========<<  <======<===<
                                           <======<<  <=====<==<  <========<<  <=======<==<
                                                      <=====<==<  <========<<  <=======<==<
                                                                  <========<<  <=======<==<
                                                                               <=======<==<

然后通过按此顺序运行旋转序列来完成生成排列,其中每个第n个n 被 n + 1 替换:

  

3,3,4,3,3,4,3,3,4,3,3, 5 中,3,3,4,3,3,4,3,3, 4,3,3, 5 下,3,3,4,3,3,4,3,3,4,3,3, 5 中,3,3, 4,3,3,4,3,3,4,3,3, 5 下,3,3,4,3,3,4,3,3,4,3,3,< b> 6 ,...

以便旋转:

  

(0,1,2),(0,1,2),(0,1,3),(0,1,2),(0,1,2),(1,2,3) ,(0,1,2),(0,1,2),(1,2,3),(0,1,2),(0,1,2),(0,3,4) )中,
  (0,1,2),(0,1,2),(0,1,3),(0,1,2),(0,1,2),(1,2,3),(0 ,1,2),(0,1,2),(1,2,3),(0,1,2),(0,1,2),(0,3,4)下,
  (0,1,2),(0,1,2),(0,1,3),(0,1,2),(0,1,2),(1,2,3),(0 ,1,2),(0,1,2),(1,2,3),(0,1,2),(0,1,2),(0,3,4)下,
  (0,1,2),(0,1,2),(0,1,3),(0,1,2),(0,1,2),(1,2,3),(0 ,1,2),(0,1,2),(1,2,3),(0,1,2),(0,1,2),(0,3,4)下,
  (0,1,2),(0,1,2),(0,1,3),(0,1,2),(0,1,2),(1,2,3),(0 ,1,2),(0,1,2),(1,2,3),(0,1,2),(0,1,2),(0,1,5),...

下面的代码示例生成最多到所请求的元素数的旋转序列,然后执行旋转并输出结果排列。

function rotate3permute(elems) {
    // GENERATE ROTATION SEQUENCES
    var pos = [,,,[[0,1],[0,1]],[[0,1],[1,2],[1,2]]];
    for (var i = 5; i <= elems; i++) {
        pos[i] = [];
        for (var j = 1; j < i; j++) pos[i].push([0, i % 2 ? i - 2 : j < i - 4 ? j : i - 4])
    }
    // PREPARE INITIAL PERMUTATION AND STEP COUNTER
    var perm = [0,1], step = [,,,], seq = 3;
    for (var i = 2; i < elems; i++) {
        perm.push(i);
        step.push(0);
    }
    document.write(perm + "<BR>");
    // EXECUTE ROTATIONS
    while (seq <= elems) {
        rotate(pos[seq][step[seq]++], seq - 1);
        document.write(perm + "<BR>");
        seq = 3;
        while (step[seq] == seq - 1) step[seq++] = 0;   // seq = 3,3,4,3,3,4,3,3,4,3,3,5...
    }
    function rotate(pair, third) {
        var temp = perm[pair[0]];
        perm[pair[0]] = perm[pair[1]];
        perm[pair[1]] = perm[third];
        perm[third] = temp;
    }
}
rotate3permute(8);

注意:用更严格的语言将while (step[seq] == seq - 1)替换为while (seq <= elems && step[seq] == seq - 1),以避免数组越界错误。

如上所述,要生成所有排列,而不仅仅是通过3个元素旋转可以达到的一半,输出每个排列两次,一次是原样,一次是切换前两个元素。

答案 2 :(得分:1)

我很确定我没有得到这个问题,因为听起来你已经拥有了实现它所需要的所有部分,但是这里有。请评论这听起来是否正确。

我选择了递归方法。循环3个元素的每个组合,然后递归处理新组合。只处理独特的组合。

以下是LINQPad中作为C#程序实现的代码:

var Category = Parse.Object.extend("Category");
var queryCategory = new Parse.Query(Category);
queryCategory.equalTo("objectId", $stateParams.categoryId);
queryCategory.first({
    success: function(category) {
        var SubCategory = Parse.Object.extend("SubCategory");
        var query = new Parse.Query(SubCategory);
        query.equalTo("isActive", true);
        query.equalTo("categoryId", category);
        query.ascending("name");
        query.find(..);
    }
});

输出:

  

12345,23145,31245,14235,42135,21435,13425,34125,41325,15324,53124,31524,12534,25134,51234,13254,32154,21335,14352,43152,31452,15432,54132,41532 ,13542,35142,51342,45312,53412,34512,42513,25413,54213,41253,12453,24153,45123,51423,14523,43521,35421,54321,42351,23451,34251,45231,52431,24531,32541 ,25341,53241,43215,32415,24315,52314,23514,35214,15243,52143,21543

答案 3 :(得分:1)

构建N元素排列

要通过在相同方向上重复旋转3个元素来生成N个元素的排列,您可以从3个元素的旋转构建4个元素的排列,然后从4个元素的5个元素的排列构建 - 元素置换,依此类推,直到达到N个元素。

E.g。 3元素的轮换如下:

        012
<<<     120
<<<     201

4元素置换重复使用3元素旋转,通过第4个元素旋转的旋转交替:

        0123
<<<     1203
<<<     2013
<<=<    0312
<<<     3102
<<<     1032
<<=<    0231
<<<     2301
<<<     3021
=<<<    3210
<<<     2130
<<<     1320

(其中<是旋转元素的位置,=是保留在原位的元素的位置。)

5元素permutaton重复4元素排列,通过第5个元素旋转的旋转交替:

        01234      <=<=<   23401      <=<=<   40123      <=<=<   12340      <=<=<   34012
<<<     12034      <<<     34201      <<<     01423      <<<     23140      <<<     40312
<<<     20134      <<<     42301      <<<     14023      <<<     31240      <<<     03412
<<=<    03124      <<=<    20341      <<=<    42013      <<=<    14230      <<=<    31402
<<<     31024      <<<     03241      <<<     20413      <<<     42130      <<<     14302
<<<     10324      <<<     32041      <<<     04213      <<<     21430      <<<     43102
<<=<    02314      <<=<    24031      <<=<    41203      <<=<    13420      <<=<    30142
<<<     23014      <<<     40231      <<<     12403      <<<     34120      <<<     01342
<<<     30214      <<<     02431      <<<     24103      <<<     41320      <<<     13042
=<<<    32104      =<<<    04321      =<<<    21043      =<<<    43210      =<<<    10432
<<<     21304      <<<     43021      <<<     10243      <<<     32410      <<<     04132
<<<     13204      <<<     30421      <<<     02143      <<<     24310      <<<     41032

定义N元素排列

对于从N-1到N元素的每个步骤,有几个选项,每个选项都有自己的结束状态。因此,每个步骤由N-1个旋转定义,并且它们一起定义N元素置换;例如对于上面的例子:

3 ELEMENTS
rotations:            <<<     <<<
complete permutation: 012 -> 201

4 ELEMENTS
rotations:            <<=<    <<=<    =<<<
complete permutation: 0123 -> 1320

5 ELEMENTS
rotations:            <=<=<   <=<=<   <=<=<   <=<=<
complete permutation: 01234 -> 41032

选择轮换

您将在上面的示例中看到,4元素排列不使用3个相同的旋转,而是<<=<,再次<<=<,最后=<<< ;这是因为并非所有可能的旋转组合都会产生正确的排列 要找出可用于N元素置换的旋转,请查看N-1元素置换的结束状态,并查看它包含的周期,例如:

排列0123 -> 1320有两个周期:[031][2],因为位置0移动到位置3,位置3移动到位置1,位置1移动到位置0,而位置2留在原地。

排列0123 -> 3210有两个周期:[03][12],因为0和3切换位置,以及1和2个切换位置。

案例:多个周期

要从N-1个元素置换创建N元素置换,您需要使用第N个元素从前N-1个元素旋转两个元素。检查N-1个元素排列具有哪些循环,并选择位置0处的旋转点和第二个循环中的位置。重复此旋转次数与第二个循环中的元素一样多次,然后将第二个点移动到第三个循环中的位置(如果有的话),并重复此次数与第三个循环中的元素一样多次,并且等等。使用完所有循环后,根据需要重复最后一次循环。

一个例子可以说明这一点:

N-1-permutation: 012345 -> 254301
cycles: [042], [15], [3]

         0123456 ... 2543016
<<====<  5643012 ... 4103562  (positions 0 and 1, for cycle [15])
<<====<  1203564 ... 0653124  (repeat for second element in [15])
<==<==<  3654120 ... 5214360  (positions 0 and 3, for cycle [3])
<==<==<  4210365 ... 1630425  (done; repeat last rotation till end)
<==<==<  0635421 ... 3245061
<==<==<  5241063 ... 4601523

正如您将注意到的,在2个循环的情况下,旋转始终保持不变:

N-1-permutation: 012345 -> 354201
cycles: [0423], [15]

         0123456 ... 3542016
<<====<  5642013 ... 2104563  (positions 0 and 1, for cycle [15])
<<====<  1304562 ... 4650132  (repeat for second element in [15])
<<====<  6250134 ... 0315624  (done; repeat last rotation till end)
<<====<  3415620 ... 5261340
<<====<  2061345 ... 1436205
<<====<  4536201 ... 6023451

案例:一个周期

找到两个位置X和Y,其中X位于Y的左侧,N-1排列将X移动到Y,而Y不是N-1排列中的最右侧位置。然后重复旋转位置X和Y与第N个元素,最后一步旋转Y和最右侧位置。

再一次,一个例子会让这个更清楚:

N-1-permutation: 012345 -> 153024
cycles: [032451]
positions X and Y: e.g. 0 and 3, because 0 moves to 3 and 3 < 5 (other option: 2 and 4)

         0123456 ... 1530246
<==<==<  0536241 ... 5460321  (positions X and Y)
<==<==<  0461325 ... 4210635  (repeat until penultimate rotation)
<==<==<  0215634 ... 2350164
<==<==<  0354162 ... 3640512
<==<==<  0642513 ... 6120453
===<=<<  6125430 ... 1356240  (last rotation: position Y and right-most)

当N-1排列由与旋转相同方向的1位置移位组成时存在边缘情况;在这种情况下,在位置0和1以及位置1和2之间交替:

N-1-permutation: 012345 -> 123450
cycles: [054321]

         0123456 ... 1234506
<<====<  2634501 ... 6345021
=<<===<  6415023 ... 4150263
<<====<  1350264 ... 3502614
=<<===<  3042615 ... 0426135
<<====<  4526130 ... 5261340
=<<===<  5601342 ... 6013452

轮换选择示例

以下是最多10个元素的排列示例;有很多选项,但是这个选项显示了一个有趣的重复模式,从7个元素开始(比较7和9元素以及8和10元素的旋转,以及7和9元素的结束状态):

THREE ELEMENTS (3 permutations)

        012
<<<     120
<<<     201                         = 1 cycle: [012]   X=0, Y=1
FOUR ELEMENTS (12 permutations)

        0123 ... 2013
<<=<    0312 ... 1032
<<=<    0231 ... 3021
=<<<    3210 ... 1320               = 2 cycles: [031][2]   X=0, Y=2
FIVE ELEMENTS (60 permutations)

        01234 ... 13204
<=<=<   23401 ... 30421
<=<=<   40123 ... 02143
<=<=<   12340 ... 24310
<=<=<   34012 ... 41032             = 3 cycles: [024][1][3]   X=0, Y=1,3
SIX ELEMENTS (360 permutations)

        012345 ... 410325
<<===<  150324 ... 251304
<==<=<  351402 ... 053412
<==<=<  453210 ... 154230
<==<=<  254031 ... 352041
<==<=<  052143 ... 450123           = 2 cycles: [024][135]   X=0, Y=1
SEVEN ELEMENTS (2,520 permutations)

         0123456 ... 4501236
<<====<  5601234 ... 2356014
<<====<  3456012 ... 0134562
<<====<  1234560 ... 5612340
<<====<  6012345 ... 3460125
<<====<  4560123 ... 1245603
<<====<  2345601 ... 6023451        = 5 cycles: [016][2][3][4][5]; X=0, Y=2,3,4,5
EIGHT ELEMENTS (20,160 permutations)

          01234567 ... 60234517
<=<====<  20734516 ... 12734506
<==<===<  32764501 ... 03764521
<===<==<  43761520 ... 24761530
<====<=<  54761032 ... 35761042
<====<=<  05761243 ... 40761253
<====<=<  20761354 ... 52761304
<====<=<  32761405 ... 03761425     = 2 cycles: [0][1457263]; X=0, Y=1
NINE ELEMENTS (181,440 permutations)

           012345678 ... 037614258
<<======<  387614250 ... 365281740
<<======<  605281743 ... 624708513
<<======<  234708516 ... 271530486
<<======<  761530482 ... 758463102
<<======<  528463107 ... 540126837
<<======<  470126835 ... 413872065
<<======<  153872064 ... 186057324
<<======<  846057321 ... 802345671  = 7 cycles: [018][2][3][4][5][6][7]; X=0, Y=2,3,4,5,6,7
TEN ELEMENTS (1,814,400 permutations)

            0123456789 ... 8023456719
<=<======<  2093456718 ... 1293456708
<==<=====<  3298456701 ... 0398456721
<===<====<  4398156720 ... 2498156730
<====<===<  5498106732 ... 3598106742
<=====<==<  6598102743 ... 4698102753
<======<=<  7698102354 ... 5798102364
<======<=<  3798102465 ... 6398102475
<======<=<  4398102576 ... 7498102536
<======<=<  5498102637 ... 3598102647  = 2 cycles: [051483][2679]; X=0, Y=2

生成排列

通过首先选择要使用的旋转,然后使用以下顺序执行旋转来完成生成排列:其中每第三个3由4替换,每四个4替换为5,每五个替换5一个6,依此类推:

  

3,3,4,3,3,4,3,3,4,3,3,5,3,3,4,3,3,4,3,3,4,3,3,5 ,3,3,4,3,3,4,3,3,4,3,3,5,3,3,4,3,3,4,3,3,4,3,3,5,3 ,3,4,3,3,4,3,3,4,3,3,6 ...

类似于Ehrlich的序列,因为你可以使用前2个旋转来生成3个元素的3个排列,或者前4个用于4个元素的12个排列,或者前50个用于5个元素的60个排列,或者通常:N!/ 2-1旋转以产生N!/ 2个排列。

(更新:有关实施,请参阅我对此问题的其他答案。)

无法访问的排列

正如问题和评论中所提到的,只有一半的可能排列可以使用3元素旋转生成。使用上述方法生成的每个排列都有一个不可达的伴随排列,可以通过切换前两个元素来创建,例如:

        0123    (1023)
<<<     1203    (2103)
<<<     2013    (0213)
<<=<    0312    (3012)
<<<     3102    (1302)
<<<     1032    (0132)
<<=<    0231    (2031)
<<<     2301    (3201)
<<<     3021    (0321)
=<<<    3210    (2310)
<<<     2130    (1230)
<<<     1320    (3120)