Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Sepa

Download as doc, pdf, or txt
Download as doc, pdf, or txt
You are on page 1of 9

SEPA: A Simple, Efficient Permutation Algorithm

Abstract

Research in progress developing a fast algorithm which will find all possible
permutations of a data set without the use of "helping" data structures or recursion.

The SEPA Algorithm

Three steps are needed to convert a data set to its next permutation. The first step is to
find the key field for this permutation. The first field which changes between the two
permutations is the key field. The key field in "abdca" as it becomes "acabd" (its next
sorted order permutation) is the field containing the value 'b'.

The key field can be determined by checking pairs of digits (or fields) starting from the
rear (or right side) of the set. For each pair, if the left value of the pair is smaller than the
right, then the field on the left is the key field. At this point all values to the right of the
key field must be in sorted order from right to left (if not a field to the right would be the
key field). If the key does not exist, the last permutation has been created. At this time,
the data set is in reverse sorted order (from left to right).

For clarification, those values to the right of the key field will be termed the "tail" of the
set, while those to the left will be the "head". Note that the head, key, and tail will be
different for each permutation.

To find the next permutation in order, the key field must be exchanged with the smallest
value in the tail which is larger than the key value. For example in the data set "abdca"
the field with the value 'b' is the key, and the field containing the value 'c' has the smallest
value in the tail which is larger than the key value. After swapping values, the set
becomes "acdba". It is possible to find this value by comparing each value with the key
value, starting at the end of the data set. The first value larger than the key value is the
value to be exchanged with the key value. Notice that the tail (the 'c' is the key now) is
still in reverse sorted order. This is important, because the last step is putting the tail into
sorted order.

Since the tail is in reverse sorted order, to sort it requires only a single loop, which works
from both ends of the tail at the same time, swapping each pair of items while moving to
the center of the tail. In this example, a character data set was used, but as stated before
the logic behind this algorithm will work with other data sets, as long as there is a less
than/greater than relationship among the data items. If all permutations are required, it is
necessary that the original data set be the first permutation (exist in sorted order).

A quick review of the algorithm shows it is compose of three loops (an example in C is
given as Listing 1). The first finds the key. The second finds the value to swap with the
key. The third reorders the tail from reverse order to sorted order. A short analysis shows
all three loops are bounded by the size of the data set, therefore the running time is
bounded by O(n).

References

[1]Robert Sedgewick. Algorithms (Second Edition). Addison-Wesley Publishing


Company, August 1989.
[2]Donald E. Knuth. The Art of Computer Programming (Volume 3 /Sorting and
Searching). Addison-Wesley Publishing Company, 1973.
[3]Jacques Arsac. Foundations of Programming. Academic Press Inc. (London) LTD.
1985.

Listing 1

#include<STDIO.H>
#include<STDLIB.H>

void swap(char *s, int a, int b)


{
char temp=s[a];
s[a] = s[b];
s[b] = temp;
}

int permute(char *str, int len)


{
int key=len-1;
int newkey=len-1;

/* The key value is the first value from the end which
is smaller than the value to its immediate right */

while( (key > 0) && (str[key] <= str[key-1]) )


{ key--; }

key--;

/* If key < 0 the data is in reverse sorted order,


which is the last permutation. */

if( key < 0 )


return 0;

/* str[key+1] is greater than str[key] because of how key


was found. If no other is greater, str[key+1] is used */

newkey=len-1;
while( (newkey > key) && (str[newkey] <= str[key]) )
{
newkey--;
}

swap(str, key, newkey);

/* variables len and key are used to walk through the tail,
exchanging pairs from both ends of the tail. len and
key are reused to save memory */

len--;
key++;

/* The tail must end in sorted order to produce the


next permutation. */

while(len>key)
{
swap(str,len,key);
key++;
len--;
}

return 1;
}

void main()
{
/* test data */

char test_string[] = "aabcd";

/* A short test loop to print each permutation, which are


created in sorted order. */

do {
printf("%s\n",test_string);
} while( permute(test_string, strlen(test_string)) );
}
{menu}

The Countdown QuickPerm Algorithm (Head):


(Formally Example_01)
A complete permutation over a very large array is time consuming to say the least.
Without a strategic plan, a permutation effort will be fruitless. In the algorithm presented
below, exchanges are concentrated within the head of the target array such that one swap
produces a new ordered list. Index values will not advance until all combinations are
exhausted inside of the established range. Specifically, repeated access to an established
upper index peak must be counted. Only when this count equals the value of the upper
index in question can the upper index value proceed to the next available index level.

Consider the following example regarding a prioritized list. Imagine that your house keys
were lost while surfing the net at a friend's house. Where would you begin your search?
Would your search begin
a.) near the computer?
b.) from an adjacent room?
c.) at your house?
d.) at a neighbor’s house?
Odds are you would retrace your steps by first searching your pockets and the
surrounding area repeatedly long before you would even search the neighbor’s house.
Here choice "a" is intentionally placed first immediately followed by an ordered list of
less likely choices (by degree). Choices very similar to these can be initially prioritized
within an array before a permutation is even attempted. Frequently permutations begin
with a known solution and attempt to improve upon agreeable ideas as permutable
subsets. Simply stated, time management is vital when dealing with very large arrays or
large character strings.

Furthermore, assume that a fast computer can complete a permutation set using a 50-
element target array in exactly 10 seconds. It's also reasonable to assume this computer
can complete a permutation set using a larger target array within a greater time period.
Using the presented scenario above and the known factorial properties of a permutation,
the following conclusions can be established:
A permutation over a new 51-element array would complete in approximately:
10 Seconds * 51 = 510 Seconds or 8.5 Minutes
A permutation over a new 52-element array would complete in approximately:
10 Seconds * 51 * 52 = 26520 Seconds or 442 Minutes or 7.37 Hours
OR 8.5 Minutes * 52 = 442 Minutes or 7.37 Hours
A permutation over a new 53-element array would complete in approximately:
10 Seconds * 51 * 52 * 53 = 1405560 Seconds or 16.27 Days
A permutation over a new 54-element array would complete in approximately:
10 Seconds * 51 * 52 * 53 * 54 = 75900240 Seconds or 2.41 Years
A permutation over a new 55-element array would complete in approximately:
10 Seconds * 51 * 52 * 53 * 54 * 55 = 4174513200 Seconds or 132.37 Years
In the above scenario, an absolute solution can probably be identified within a 53-element
array. Using an array with more than 53-elements would cause an unreasonable and
predictable wait. The QuickPerm algorithm presented below uses permutation subsets on
the head of the target array. Often the target array a[N] is simply used to index large
complex data structures. Here's the actual QuickPerm C++ algorithm for your review:
QuickPerm - Head Permutations Using a Linear Array Without Recursion

// NOTICE: Copyright 1991-2006, Phillip Paul Fuchs

#define N 12 // number of elements to permute. Let N > 2

void QuickPerm(void)
{
unsigned int a[N], p[N+1];
register unsigned int i, j, tmp; // Upper Index i; Lower Index j

for(i = 0; i < N; i++) // initialize arrays; a[N] can be any type


{
a[i] = i + 1; // a[i] value is not revealed and can be arbitrary
p[i] = i;
}
p[N] = N; // p[N] > 0 controls iteration and the index boundary for i
//display(a, 0, 0); // remove comment to display array a[]
i = 1; // setup first swap points to be 1 and 0 respectively (i & j)
while(i < N)
{
p[i]--; // decrease index "weight" for i by one
j = i % 2 * p[i]; // IF i is odd then j = p[i] otherwise j = 0
tmp = a[j]; // swap(a[j], a[i])
a[j] = a[i];
a[i] = tmp;
//display(a, j, i); // remove comment to display target array a[]
i = 1; // reset index i to 1 (assumed)
while (!p[i]) // while (p[i] == 0)
{
p[i] = i; // reset p[i] zero value
i++; // set new index value for i (increase by one)
} // while(!p[i])
} // while(i < N)
} // QuickPerm()

QuickPerm - Function to Display Permutations (Optional)

// NOTICE: Copyright 1991-2006, Phillip Paul Fuchs

void display(unsigned int *a, unsigned int j, unsigned int i)


{
for(unsigned int x = 0; x < N; x++)
printf("%d ",a[x]);
printf(" swapped(%d, %d)\n", j, i);
getch(); // press any key to continue...
} // display()
The primary index controller array in the QuickPerm algorithm above is p[N], which
controls iteration and the upper index boundary for variable i. This array is initialized to
corresponding index values so that a simple countdown process can be utilized. The
controller array is analogous to an incremental base N odometer, where digit p[0] is
always zero, digit (or wheel) p[1] counts in base 2 or binary, digit p[2] counts in
base 3, ..., and digit p[N] counts in base N+1. Initial values are assigned as follows:

p[0] = 0
p[1] = 1
p[2] = 2
...
p[N] = N

Initially the default upper index variable i is set to one as a reference to the second least
significant odometer digit which in-turn is immediately reduced by one (a countdown
process). Since the upper index variable is clearly odd, the default lower index variable j
is assigned to the odometer digit referenced by the upper index which coincides to the
value now stored in p[i] or zero. Both index variables are properly set and the first
SWAP is called...

Before another swap can take place, upper and lower index values must be recalculated
based upon values stored in the base N odometer p[N]. Again the upper index begins at
the binary digit p[1] and resets each consecutive ZERO digit to a corresponding index
value (maximum digit). The upper index will settle upon the first odometer digit that is
not zero then reduce the referenced digit by one (again a true countdown process). If the
upper index is even, the lower index is assigned to the least significant odometer digit
which coincides to the value stored in p[0] (or simply the lower index will remain at
zero). Otherwise the lower index will be assigned to the actual odometer digit now
referenced by the upper index. A SWAP is executed and this process continues until the
upper index attempts to reference an odometer digit beyond the length of the linear
permutable target in question.

Notice the lower index always follows the upper index's lead. Observable modifications
compared to the counting QuickPerm algorithm include the following:

1. Initializing all integers in the controller array to corresponding index values.


2. Decrement p[i] by one before the conditional assignment j = p[i]
3. While p[i] equals 0 DO reset p[i] to i then increment i by one.

Using a nested while loop instead of a conditional if-else statement (as described in the
counting QuickPerm algorithm) clearly lead to the development of a Meta-permutation
class. Although this conditional optimization can be made regardless of the counting
process utilized, the Meta-permutation class precludes such an optimization in order to
return valid indices.
Fundamental Analysis of QuickPerm Above {Switch to Characters}
Number of Total Number of Unique
a[N] Display of a[N] Display of
Objects Combinations Produced
Initial Sequence Final Sequence
to Permute (N) (N!)

3 123 321 {Output} 2*3=6

4 1234 4123 {Output} 6 * 4 = 24

5 12345 52341 {Output} 24 * 5 = 120

6 123456 634125 120 * 6 = 720

7 1234567 7234561 720 * 7 = 5040

8 12345678 83456127 5040 * 8 = 40320

9 123456789 923456781 40320 * 9 = 362880

1 2 3 . . . (N - 1) N 3 4 . . . (N - 2) 1 2 (N -
if N is even (N - 1)! * N = N!
N 1)

1 2 3 . . . (N - 1)
if N is odd N 2 3 . . . (N - 1) 1 (N - 1)! * N = N!
N

A Permutation Ruler:
The graph below reflects how the value stored in the lower index variable j consistently
reduces by one when the upper index variable i is odd, otherwise j is assigned a zero
value. For each reference to j when i is odd, the lower index variable j will be assigned
to the upper index variable “i – 1” then “i – 2” until the lower index variable j
reaches zero. To control this iteration behavior, the value stored in the controller array
p[i] is initialized to the same value stored in the upper index variable i (as mentioned
above) then repeatedly reduced by one and assigned to the lower index variable j. This
process continues until the lower index variable j reaches 0 then repeats. (Please note,
the lower index variable j can also begin at zero and advance by one until the upper
index value is reached.) This behavior is strictly controlled by the following statement:
“IF i is odd then j = p[i] otherwise j = 0”
Which in-turn translates to the following C++ statement:

j = i % 2 * p[i];
(Click here to enlarge graph.)

You might also like