Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Permutation Generation Using Matrices


NOV95: ALGORITHM ALLEY

Mani is a senior programmer/analyst at a mutual-funds company in Boston. He has a masters degree in Computer Science from the University of Bombay, India and is a CCP. Mani can be reached at [email protected].


In his classic paper "Permutation Generation Methods" (Computing Surveys 9, 1977), Robert Sedgewick surveys the various permutation generation methods published until then, categorizing them as follows:

  • Methods based on exchanges in which the n! permutations of n are obtained by a series of (n!-1) exchanges. Algorithms proposed by M.B. Wells, J. Boothroyd, B.R. Heap, S.M. Johnson, H.F. Trotter, and F.M. Ives fall into this category.
  • Algorithms not based on exchanges. Some algorithms, for instance, use cyclic rotations to obtain the n! permutations. One such algorithm is by G.W. Langdon (I arrived at his method independently). Other algorithms proposed by Fischer and Krause and R.J. Ord-Smith generate lexicographic permutations.
Sedgewick discusses the different classes of algorithms and details the analysis and implementation of the most prominent. For example, Langdon's cyclic method can be implemented with only a few computer instructions and can be made to run very fast on computers with hardware-rotation capabilities.

The algorithm I propose here generates permutations in a novel way, using cyclic rotations (implemented without hardware dependencies) and matrices. The only restrictions on the implementation language are the availability of a "string copy" function and efficient pointer manipulations.

The Algorithm

The first step toward generating all n! permutations of 1,2,...,n is to generate the pivot permutations. A pivot permutation is obtained by certain rules and is used to generate an nxn matrix, which in turn yields 2n permutations. Consequently, you need n!/2n pivot permutations. Before discussing pivot-permutation generation, however, certain definitions are in order.

A "permutation" is a sequence of pi, where i=1,2,...,n and pi is a unique integer between 1 and n, inclusive. The function right rotate(f,l) of a permutation yields a sequence where pf=pl and pi=pi-1 for i=f+1,...,l.

The function left rotate(f,l) of a permutation yields a sequence where pl=pf and pi=pi+1 for i=f,...,l-1. A "full-right rotate" and a "full-left rotate" are special cases where f=1 and l=n.

A "cyclic-permutation matrix" is an nxn matrix whose first row is a pivot permutation and whose ith row is obtained by a full-right rotate of the (i-1)th row for i=2,...,n. In all rotations, f (first) is <=l (representing last).

Pivot permutations are obtained by performing rotations, right or left, but only in one direction throughout the process. For the purpose of this discussion, I'll stick with right rotate, although left works equally well.

Successive pivot permutations are generated using (n-1) right rotates with l=(n-1). The nth right rotate will yield the starting sequence; you can refer to these as (n-1)th order pivots. Using each of the (n-1) pivots, generate the (n-2)th order pivots using (n-2) right rotates with l=(n-2). Continue the process until l=2, at which point no more pivot permutations can be generated, leaving you with n!/2n pivot permutations. The fact that the nth right rotate of an nth order pivot permutation yields the original sequence in the (n+1)th order implies that the third-order pivots are the ones actually used to generate the nxn matrices.

For a given third-order pivot permutation, using this pivot permutation as the first row of an nxn matrix, generate the other (n-1) rows of the cyclic-permutation matrix by performing (n-1) full-right rotates. After creating the matrix, read the rows and columns of the matrix to yield 2n permutations. Repeating the process for the other pivot permutations will yield (n!/2n)x2n=n! permutations.

Figure 1 and Figure 2 show the algorithm generating all permutations of 1,2,3,4, and 5 (that is, n=5). Reading the rows and the columns of the 12 matrices in Figure 2 yields the 120 permutations of {1,2,3,4,5}. Consequently, the algorithm can be coded recursively in an elegant manner; see Example 1. Listing One is the complete C implementation of the permutation-generation algorithm. The array num, which is one relative, is defined as a character array to facilitate using character pointers in the memcpy functions and building the cyclic-permutation matrices via an array of pointers.

In rightRotate(f,l), the right rotates are performed by cloning the substring of num indexed by f and having length l into the array temp, which is zero relative. The final result is obtained by getting the string from temp, indexed by (l-1) and having length l. For example, if num={0,3,2,4,1,5} and a rightRotate (1,4) needs to be performed, then the first and second memcpy functions from num to temp would yield temp equal to {3,2,4,1,3,2,4,1}. The string indexed by 3 and having length 4 in temp is copied back into num indexed by 1, thus yielding num as equal to {0,1,3,2,4,5}.

In createCyclicMatrix(), the entire array num is cloned into temp, and the pointer array p is created by pointing to the different indexes of temp. Since a cyclic-permutation matrix is created by performing (n-1) full-right rotates on the pivot permutation, it is only natural to create the matrix as an array of pointers. For example, if a cyclic-permutation matrix needs to be created for the pivot permutation stored in num as {0,2,4,1,3,5}, then temp would be equal to {2,4,1,3,5,2,4,1,3,5} and p would be created as described in Table 1. The permutations are obtained by reading the n rows and n columns of the cyclic-permutation matrix stored in p.

Salient Features of the Algorithm

Any of the n! permutations of n can be used as an initial permutation to generate the n! permutations of n. This is because every permutation of n must be either a row or a column of a cyclic-permutation matrix and any row or column of the matrix can be used as a pivot permutation to generate the matrix.

The cyclic-permutation matrix can be created using full-left rotates, but the matrix will have to be created bottom up; that is, the pivot permutation is the nth row of the matrix, and for i=(n-1),...,1, the ith row is created by performing a full-left rotate on the (i+1)th row.

The ith order pivot permutations can be created using one of the following:

  • Right rotate (1,i).
  • Left rotate (1, i).
  • Right rotate (n-i+1, n).
  • Left rotate (n-i+1, n).
In fact, between different orders of pivot permutations, the direction of rotates can be changed. The direction cannot be changed within the same order.

In the cyclic-permutation matrix, the primary diagonals or the secondary diagonals, depending on the direction of rotates used (primary for right and secondary for left), have the same number. This is true for the ixi matrix obtained by the ith order pivot permutations; for example, the matrix obtained by (1,i) elements or (n-i+1) elements of the i pivot permutations. The uniqueness of the permutations can be attributed to this property.

Conclusion

Sedgewick notes that the exchange method by B.R. Heap will run fastest on most computers. In Data Structures and Program Design in C (Prentice-Hall, 1991), Robert L. Kruse, Bruce P. Leung, and Clovis L. Tondo claim that the linked-list, or simple-exchange algorithm is at least as efficient as the Heap method. Both the Heap and linked-list algorithms have been implemented using the C programs supplied by Kruse et al. in their book. I've used these programs for performance comparisons to the Matrix method, on a 486-based PC running under DOS and a Sun workstation under Solaris. The results are as follows:

  • If the processing of a given permutation is implemented as a simple printf function resulting in mere enumeration of the permutations, the Matrix method is only slightly faster than the Heap and linked-list methods. This slight difference may be attributed to the integer conversion involved in the printing of a character.
  • If the processing of a given permutation is implemented as a dummy function, the Matrix method is faster than the other two methods by a factor of two.
If parallelism were available, the algorithm could be made to run much faster, since the generation of cyclic-permutation matrices from the pivot permutations would be treated as independent processes, thus making larger values of n tractable.

Figure 1: Generating pivot permutations

     Fifth-order   Fourth-order   Third-order
        Pivot         Pivot          Pivot
        
      1 2 3 4 5     4 1 2 3 5      2 4 1 3 5
                                   1 2 4 3 5
                                   4 1 2 3 5
                    3 4 1 2 5      1 3 4 2 5
                                   4 1 3 2 5
                                   3 4 1 2 5
                    2 3 4 1 5      4 2 3 1 5
                                   3 4 2 1 5     
                                   2 3 4 1 5
                    1 2 3 4 5      3 1 2 4 5
                                   2 3 1 4 5
                                   1 2 3 4 5
Figure 2: Cyclic-permutation matrices generated by the 12 pivot permutations.

[ 2 4 1 3 5 ]  [ 1 2 4 3 5 ]  [ 4 1 2 3 5 ]  [ 1 3 4 2 5 ]  [ 4 1 3 2 5 ]
[ 5 2 4 1 3 ]  [ 5 1 2 4 3 ]  [ 5 4 1 2 3 ]  [ 5 1 3 4 2 ]  [ 5 4 1 3 2 ]
[ 3 5 2 4 1 ]  [ 3 5 1 2 4 ]  [ 3 5 4 1 2 ]  [ 2 5 1 3 4 ]  [ 2 5 4 1 3 ]
[ 1 3 5 2 4 ]  [ 4 3 5 1 2 ]  [ 2 3 5 4 1 ]  [ 4 2 5 1 3 ]  [ 3 2 5 4 1 ]
[ 4 1 3 5 2 ]  [ 2 4 3 5 1 ]  [ 1 2 3 5 4 ]  [ 3 4 2 5 1 ]  [ 1 3 2 5 4 ]

[ 3 4 1 2 5 ]  [ 4 2 3 1 5 ]  [ 3 4 2 1 5 ]  [ 2 3 4 1 5 ]  [ 3 1 2 4 5 ]
[ 5 3 4 1 2 ]  [ 5 4 2 3 1 ]  [ 5 3 4 2 1 ]  [ 5 2 3 4 1 ]  [ 5 3 1 2 4 ]
[ 2 5 3 4 1 ]  [ 1 5 4 2 3 ]  [ 1 5 3 4 2 ]  [ 1 5 2 3 4 ]  [ 4 5 3 1 2 ]
[ 1 2 5 3 4 ]  [ 3 1 5 4 2 ]  [ 2 1 5 3 4 ]  [ 4 1 5 2 3 ]  [ 2 4 5 3 1 ]
[ 4 1 2 5 3 ]  [ 2 3 1 5 4 ]  [ 4 2 1 5 3 ]  [ 3 4 1 5 2 ]  [ 1 2 4 5 3 ]

[ 2 3 1 4 5 ]  [ 1 2 3 4 5 ]
[ 5 2 3 1 4 ]  [ 5 1 2 3 4 ]
[ 4 5 2 3 1 ]  [ 4 5 1 2 3 ]
[ 1 4 5 2 3 ]  [ 3 4 5 1 2 ]
[ 3 1 4 5 2 ]  [ 2 3 4 5 1 ]
Example 1: Recursive algorithm for generating permutations by the matrix method.
matrixPermute (n)
{
    if  (n = 3) createCyclicMatrix
and return;
    for  (n - 1) times
         {
          rightRotate  (1, n - 1);
          matrixPermute (n - 1);
         }
}
Table 1: Simulation of the cyclic-permutation matrix by an array of pointers.
i  p[i]    *(p[i]+0)  *(p[i]+1)  *(p[i]+2)  *(p[i]+3)  *(p[i]+4) 
0  temp+5      2          4          1          3          5
1  temp+4      5          2          4          1          3
2  temp+3      3          5          2          4          1
3  temp+2      1          3          5          2          4
4  temp+1      4          1          3          5          2

Listing One

#include    <stdio.h>
#include    <stdlib.h>
#include    <memory.h>
#define     MAX 20
char        num[MAX + 1];
int         n; 
main (int argc, char *argv[])
{
    int     i;
    void    matrixPermute (), createCyclicMatrix (), rightRotate ();
    if  (argc != 2)
        {
        fprintf (stderr, "Usage: permute <string>\n");
        exit (1);
        }
    n = atoi (argv [1]);
    if  (n < 3 || n > MAX)
        {
        fprintf (stderr, "number must be between 3 and %d\n", MAX);
        exit (1);
        }
    for (i = 1; i <= n; ++i)
        num [i] = i;
    matrixPermute (n);
}
void matrixPermute (int k)
{
    int     i, temp;
    if  (k == 3) 
        {
        createCyclicMatrix ();
        return;
        }
    temp = k - 1;
    for (i = 0; i < temp ; ++i) 
        {
        rightRotate (1, temp);
        matrixPermute (temp);
        } 
}
void createCyclicMatrix ()
{
    char    *p[MAX], temp[2*MAX];
    int     i, j;
    /* create the cyclic permutation matrix P as an array of pointers */ 
    memcpy (temp, num + 1, n);
    memcpy (temp + n , num + 1, n);
   
    for (i = 0; i < n; ++i)
        p[i] = temp + n - i;
   
    /* generate the 2n permutations from the cyclic permutation matrix P */
    for (i = 0; i < n; ++i)
        {
        /* print the ith row */
        for (j = 0; j < n; ++j)
            printf ("%d ", *(p[i] + j));
        printf ("\n");
        /* print the ith column */
        for (j = 0; j < n; ++j)
            printf ("%d ", *(p[j] + i));
        printf ("\n");
        }
}
void rightRotate (int f, int l)
{
    char    temp [2*MAX], *saveptr;
    int     i;
    saveptr = num + f;
    memcpy (temp , saveptr, l);
    memcpy (temp + l, saveptr, l);
    memcpy (saveptr, temp + l - 1, l);
}


Copyright © 1995, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.