Heap's algorithm generates all possible permutations of objects. It was first proposed by B. R. Heap in 1963.[1] The algorithm minimizes movement: it generates each permutation from the previous one by interchanging a single pair of elements; the other elements are not disturbed. In a 1977 review of permutation-generating algorithms, Robert Sedgewick concluded that it was at that time the most effective algorithm for generating permutations by computer.[2]
The sequence of permutations of objects generated by Heap's algorithm is the beginning of the sequence of permutations of objects. So there is one infinite sequence of permutations generated by Heap's algorithm .
For a collection
C
Described recursively as a decrease and conquer method, Heap's algorithm operates at each step on the
k
k=n
k<n
k!
n-k
kth
k-1
kth
k-1
k-1
k
k
// Generate permutations for k-th swapped with each k-1 initial for i := 0; i < k-1; i += 1 do // Swap choice dependent on parity of k (even or odd) if k is even then swap(A[i], A[k-1]) // zero-indexed, the k-th is at k-1 else swap(A[0], A[k-1]) end if generate(k - 1, A) end for end if
One can also write the algorithm in a non-recursive format.[3]
for i := 0; i < n; i += 1 do c[i] := 0 end for
output(A) // i acts similarly to a stack pointer i := 1; while i < n do if c[i] < i then if i is even then swap(A[0], A[i]) else swap(A[c[i]], A[i]) end if output(A) // Swap has occurred ending the for-loop. Simulate the increment of the for-loop counter c[i] += 1 // Simulate recursive call reaching the base case by bringing the pointer to the base case analog in the array i := 1 else // Calling generate(i+1, A) has ended as the for-loop terminated. Reset the state and simulate popping the stack by incrementing the pointer. c[i] := 0 i += 1 end if end while
In this proof, we'll use the implementation below as Heap's Algorithm. While it is not optimal (it does not minimize moves, which is described in the section below), the implementation is nevertheless still correct and will produce all permutations. The reason for using the below implementation is that the analysis is easier, and certain patterns can be easily illustrated.
end for end if
Claim: If array has length, then performing Heap's algorithm will either result in being "rotated" to the right by 1 (i.e. each element is shifted to the right with the last element occupying the first position) or result in being unaltered, depending if is even or odd, respectively.
Basis: The claim above trivially holds true for
n=1
Induction: Assume the claim holds true for some
i\geq1
i+1
i+1
If, for,
n=i+1
k\leqi+1
n=4
1,2,3,4 ... Original Array 1,2,3,4 ... 1st iteration (Permute subset) 4,2,3,1 ... 1st iteration (Swap 1st element into "buffer") 4,2,3,1 ... 2nd iteration (Permute subset) 4,1,3,2 ... 2nd iteration (Swap 2nd element into "buffer") 4,1,3,2 ... 3rd iteration (Permute subset) 4,1,2,3 ... 3rd iteration (Swap 3rd element into "buffer") 4,1,2,3 ... 4th iteration (Permute subset) 4,1,2,3 ... 4th iteration (Swap 4th element into "buffer") ... The altered array is a rotated version of the original
If, for,
n=i+1
n=5
1,2,3,4,5 ... Original Array 4,1,2,3,5 ... 1st iteration (Permute subset/Rotate subset) 5,1,2,3,4 ... 1st iteration (Swap) 3,5,1,2,4 ... 2nd iteration (Permute subset/Rotate subset) 4,5,1,2,3 ... 2nd iteration (Swap) 2,4,5,1,3 ... 3rd iteration (Permute subset/Rotate subset) 3,4,5,1,2 ... 3rd iteration (Swap) 1,3,4,5,2 ... 4th iteration (Permute subset/Rotate subset) 2,3,4,5,1 ... 4th iteration (Swap) 5,2,3,4,1 ... 5th iteration (Permute subset/Rotate subset) 1,2,3,4,5 ... 5th iteration (Swap) ... The final state of the array is in the same order as the original
The induction proof for the claim is now complete, which will now lead to why Heap's Algorithm creates all permutations of array . Once again we will prove by induction the correctness of Heap's Algorithm.
Basis: Heap's Algorithm trivially permutes an array of size as outputting is the one and only permutation of .
Induction: Assume Heap's Algorithm permutes an array of size . Using the results from the previous proof, every element of will be in the "buffer" once when the first elements are permuted. Because permutations of an array can be made by altering some array through the removal of an element from then tacking on to each permutation of the altered array, it follows that Heap's Algorithm permutes an array of size
i+1
It is tempting to simplify the recursive version given above by reducing the instances of recursive calls. For example, as:
// Recursively call once for each k for i := 0; i < k; i += 1 do generate(k - 1, A) // swap choice dependent on parity of k (even or odd) if k is even then // no-op when i
k-1 swap(A[0], A[k-1]) end if
end for end if
This implementation will succeed in producing all permutations but does not minimize movement. As the recursive call-stacks unwind, it results in additional swaps at each level. Half of these will be no-ops of
A[i]
A[k-1]
i==k-1
k
kth
0th
n | n!-1 | swaps | additional = swaps -(n!-1) |
---|---|---|---|
1 | 0 | 0 | 0 |
2 | 1 | 1 | 0 |
3 | 5 | 6 | 1 |
4 | 23 | 27 | 4 |
5 | 119 | 140 | 21 |
6 | 719 | 845 | 126 |
7 | 5039 | 5922 | 883 |
8 | 40319 | 47383 | 7064 |
9 | 362879 | 426456 | 63577 |
These additional swaps significantly alter the order of the
k-1
The additional swaps can be avoided by either adding an additional recursive call before the loop and looping
k-1
k
i
k-1
// Recursively call once for each k for i := 0; i < k; i += 1 do generate(k - 1, A) // avoid swap when i
The choice is primarily aesthetic but the latter results in checking the value of
i