int is_valid_permutationconst ulong *f, ulong n, bitarray *bp/*=0*/// check whether all values 0...n-1 appear exactly once { // check whether any element is out of range: for ulong k=0;
Trang 1int is_valid_permutation(const ulong *f, ulong n, bitarray *bp/*=0*/)
// check whether all values 0 n-1 appear exactly once
{
// check whether any element is out of range:
for (ulong k=0; k<n; ++k) if ( f[k]>=n ) return 0;
// check whether values are unique:
One can apply arbitrary many permutations to an array, one by one The resulting permutation is called
the composition of the applied permutations As an example, the check whether some permutation g is equal to f applied twice, or f · f , or f squared use:
int is_square(const ulong *f, const ulong *g, ulong n)
int is_inverse(const ulong *f, const ulong *g, ulong n)
// check whether f[] is inverse of g[]
int is_involution(const ulong *f, ulong n)
// check whether max cycle length is <= 2
{
for (ulong k=0; k<n; ++k) if ( f[f[k]] != k ) return 0;
return 1;
}
Finding the inverse of a given permutation is trivial:
void make_inverse(const ulong *f, ulong * restrict g, ulong n)
// set g[] to the inverse of f[]
Trang 2ulong print_cycles(const ulong *f, ulong n, bitarray *bp=0)
// print the cycles of the permutation
// return number of fixed points
ulong g = f[i]; // next index
if ( g==i ) // fixed point ?
Trang 3The bitarray is used to keep track of the elements already processed.
For the computation of the inverse we have to reverse each cycle:
void make_inverse(ulong *f, ulong n, bitarray *bp/*=0*/)
// set f[] to its own inverse
Similarly for the straighforward
void make_square(const ulong *f, ulong * restrict g, ulong n)
// set g[] = f[] * f[]
{
for (ulong k=0; k<n; ++k) g[k] = f[f[k]];
}
whose inplace version is
void make_square(ulong *f, ulong n, bitarray *bp/*=0*/)
ulong t = f[i]; // save
ulong g = f[i]; // next index
Trang 4if ( 0==bp ) delete tp;
}
Random permutations are sometimes useful:
void random_permute(ulong *f, ulong n)
// randomly permute the elements of f[]
void random_permutation(ulong *f, ulong n)
// create a random permutation of the canonical sequence
{
for (ulong k=0; k<n; ++k) f[k] = k;
random_permute(f, n);
}
8.6.3 Applying permutations to data
The following routines are from [FXT: file perm/permapply.h]
The inplace analogue of the routine apply shown near the beginning of section 8.6 is:
template <typename Type>
void apply(const ulong *x, Type *f, ulong n, bitarray *bp=0)
// apply x[] on f[] (inplace operation)
template <typename Type>
void apply_inverse(const ulong *x, const Type *f, Type * restrict g, ulong n)
// apply inverse of x[] on f[]
Trang 5// i.e g[x[k]] < f[k] \forall k
{
for (ulong k=0; k<n; ++k) g[x[k]] = f[k];
}
whereas the inplace version is
template <typename Type>
void apply_inverse(const ulong *x, Type * restrict f, ulong n,
bitarray *bp=0)// apply inverse of x[] on f[] (inplace operation)
8.7 Generating all Permutations
In this section a few algorithms for the generation of all permutations are presented These are typicallyuseful in situations where an exhausive search over all permutations is needed At the time of writing
the pre-fascicles of Knuths The Art of Computer Programming Volume 4 are available Therefore (1) the
title of this section is not anymore ‘Enumerating all permutations’ and (2) I won’t even try to elaborate
on the underlying algorithms Consider the reference to the said place be given between any two lines inthe following (sub-)sections
TBD: perm-visit cf [FXT: file perm/permvisit.h]
Trang 6The sign given is plus or minus if the (minimal) number of transpositions is even or odd, respectively.
The minimalistic class perm_lex implementing the algorithm is
class perm_lex
{
protected:
ulong n; // number of elements to permute
ulong *p; // p[n] contains a permutation of {0, 1, , n-1}
ulong idx; // incremented with each call to next()
ulong sgn; // sign of the permutation
ulong current() const { return idx; }
ulong sign() const { return sgn; } // 0 for sign +1, 1 for sign -1
const ulong *data() const { return p; }
Trang 7// do something, e.g just print the permutation:
for (ulong i=0; i<n; ++i) cout << x[i] << " ";
class perm_minchange
{
protected:
ulong n; // number of elements to permute
ulong *p; // p[n] contains a permutation of {0, 1, , n-1}
ulong *ip; // ip[n] contains the inverse permutation of p[]
ulong *d; // aux
ulong *ii; // aux
ulong sw1, sw2; // index of elements swapped most recently
ulong idx; // incremented with each call to next()
public:
9 There is more than one minimal change order, e.g reversing the order yields another one.
Trang 8perm_minchange(ulong nn);
~perm_minchange();
void first();
ulong next() { return make_next(n-1); }
ulong current() const { return idx; }
ulong sign() const { return idx & 1; } // 0 for sign +1, 1 for sign -1
const ulong *data() const { return p; }
const ulong *invdata() const { return ip; }
void get_swap(ulong &s1, ulong &s2) const { s1=sw1; s2=sw2; }
protected:
ulong make_next(ulong m);
};
[FXT: class perm minchange in perm/permminchange.h]
The algorithm itself can be found in [FXT: perm minchange::make next in perm/permminchange.cc]ulong perm_minchange::make_next(ulong m)
const ulong *x = perm.data();
const ulong *ix = perm.invdata();
ulong sw1, sw2;
do
Trang 9// do something, e.g just print the permutation:
for (ulong i=0; i<n; ++i) cout << x[i] << " ";
// sometimes one only uses the indices swapped
perm.get_swap(sw1, sw2);
cout << " swap: (" << sw1 << ", " << sw2 << ") ";
// inverse permutation courtesy of the algorithm
for (ulong i=0; i<n; ++i) cout << ix[i] << " ";
}
while ( perm.next() );
Cf also [FXT: file demo/permminchange-demo.cc]
An alternative implementation using the algorithm of Trotter (based on code by Helmut Herold) can befound in [FXT: perm trotter::make next in perm/permtrotter.cc]
Trang 10There is no such sequence for n = 3.
The utility class, that implements the underlying algorithm is [FXT: class perm derange
in perm/permderange.h] The central piece of code is [FXT: perm derange::make next inperm/permderange.cc]:
cout << " #"; cout.width(3); cout << perm.current() << ": ";
for (ulong i=0; i<n; ++i) cout << x[i] << " ";
Trang 11cout << " #"; cout.width(3); cout << ct << ": ";
for (ulong i=0; i<n; ++i) cout << x[i] << " ";
cout << " swap: (" << 0 << ", " << perm.get_swap() << ") ";
8.7.5 Yet another order
to enumerate all permutations of n elements was given in [32]:
Trang 12as not yet visited (−1) or to contain at which point in the path (0 for starting point n − 1 for end
point) it was visited A recursive implementation looks like
cout << " #"; cout.width(3); cout << perm.current() << ": ";
for (ulong i=0; i<n; ++i) cout << x[i] << " ";
cout << endl;
}
while ( perm.next() );
Trang 13Sorting and searching
template <typename Type>
void selection_sort(Type *f, ulong n)
A verification routine is always handy:
template <typename Type>
int is_sorted(const Type *f, ulong n)
While the quicksort-algorithm presented below scales ∼ n log(n) (in the average case) it does not just
obsolete the more simple schemes because (1) for arrays small enough the ‘simple’ algorithm is usually
140
Trang 14the fastest method because of its minimal bookkeeping overhead and (2) therefore it is used inside thequicksort for lengths below some threshold.
The main ingredient of quicksort is to partition the array: The corresponding routine reorders some ments where needed and returns some partition index k so that max(f0, , f k−1 ) ≤ min(f k , , f n−1):
ele-template <typename Type>
ulong partition(Type *f, ulong n)
// rearrange array, so that for some index p
which we want to be able to verify:
template <typename Type>
Type inline min(const Type *f, ulong n)
// returns minimum of array
template <typename Type>
inline Type max(const Type *f, ulong n)
// returns maximum of array
template <typename Type>
int is_partitioned(const Type *f, ulong n, ulong k)
{
++k;
Type lmax = max(f, k);
Type rmin = min(f+k, n-k);
return ( lmax<=rmin );
}
Quicksort calls partition on the whole array, then on the parts left and right from the partition indexand repeat When the size of the subproblems is smaller than a certain threshold selection sort is used.template <typename Type>
void quick_sort(Type *f, ulong n)
Trang 15The reason why some data was sorted may be that a fast search has to be performed repeatedly The
following bsearch is ∼ log(n) and works by the obvious subdivision of the data:
template <typename Type>
ulong bsearch(const Type *f, ulong n, Type v)
// return index of first element in f[] that is == v
// return ~0 if there is no such element
// f[] must be sorted in ascending order
{
ulong nlo=0, nhi=n-1;
while ( nlo != nhi )
if ( f[nhi]==v ) return nhi;
else return ~0UL;
}
A simple modification of bsearch makes it search the first element greater than v: Replace the operator
== in the above code by >= and you have it [FXT: bsearch ge in sort/search.h]
Approximate matches are found by
template <typename Type>
ulong bsearch_approx(const Type *f, ulong n, Type v, Type da)
// return index of first element x in f[] for which |(x-v)| <= a
// return ~0 if there is no such element
// f[] must be sorted in ascending order
Trang 16template <typename Type>
inline long search_down(const Type *f, Type v, ulong &i)
// search v in f[], starting at i (so i must be < length)
// f[i] must be greater or equal v
// f[] must be sorted in ascending order
// returns index k if f[k]==v or ~0 if no such k is found
// i is updated so that it can be used for a following
// search for an element u where u < v
template <typename Type>
void idx_selection_sort(const Type *f, ulong n, ulong *x)
The verification code looks like:
template <typename Type>
int is_idx_sorted(const Type *f, ulong n, const ulong *x)
The index-sort routines reorder the indices in x such that x applied to f as a permutation (in the sense
of section 8.6.3) will render f a sorted array
While the transformation of partition is straight forward:
template <typename Type>
ulong idx_partition(const Type *f, ulong n, ulong *x)
// rearrange index array, so that for some index p
// max(f[x[0]] f[x[p]]) <= min(f[x[p+1]] f[x[n-1]])
Trang 17The index-quicksort itself deserves a minute of contemplation comparing it to the plain version:
template <typename Type>
void idx_quick_sort(const Type *f, ulong n, ulong *x)
Thereby pointer sort is the perfect way to highly cryptic and powerful programs that segfault when youleast expect it Admittedly, all the ‘dangerous’ features of pointer sort except the unaligned one are also
there in index sort However, with index sort you will not so often use them by accident.
Just to make the idea clear, the array of indices is replaced by an array of pointers:
template <typename Type>
void ptr_selection_sort(const Type *f, ulong n, Type **x)
{
for (ulong i=0; i<n; ++i)
{
Type v = *x[i];
Trang 18ulong m = i; // position-ptr of minimum
9.5 Sorting by a supplied comparison function
The routines in [FXT: file sort/sortfunc.h] are similar to the C-quicksort qsort that is part of thestandard library A comparison function cmp has to be supplied by the called so that compound datatypes can be sorted with respect to some key contained Citing the manual page for qsort:
The comparison function must return an integer less than, equal to, or greater than
zero if the first argument is considered to be respectively less than, equal to, or
greater than the second If two members compare as equal, their order in the
sorted array is undefined
Note that the numerous calls to cmp do have a negative impact on the performance And then with C++you can provide a comparision ‘function’ for compound data by overloading the operators <, <, <= and >=
and use the plain version Back in performance land Isn’t C++ nice? TBD: add a compile-time inlined version?
As a prototypical example here the version of selection sort:
template <typename Type>
void selection_sort(Type *f, ulong n, int (*cmp)(const Type &, const Type &))
template <typename Type>
void quick_sort(Type *f, ulong n, int (*cmp)(const Type &, const Type &))
Trang 19Sorting complex numbers
You want to sort complex numbers? Fine for me, but don’t tell your local mathematician To see the mathematical problem we ask whether i is smaller or greater than zero Assume i > 0: follows i · i > 0 (we multiplied with a positive value) which is −1 > 0 and that is false So, is i < 0? Then i · i > 0 (multiplication with a negative value, as assumed) So −1 > 0, oops! The lesson is that there is no way
to impose an arrangement on the complex numbers that would justify the usage of the symbols < and >
in the mathematical sense
Nevertheless we can invent a relation that allows us to sort: arranging (sorting) the complex numbersaccording to their absolute value (modulus) leaves infinitely many numbers in one ‘bucket’, namely all
those that have the same distance to zero However, one could use the modulus as the major ordering parameter, the angle as the minor Or the real part as the major and the imaginary part as the minor.
The latter is realized in
static inline int
cmp_complex(const Complex &f, const Complex &g)
which, when used as comparison with the above function-sort as in
void complex_sort(Complex *f, ulong n)
// major order wrt real part
// minor order wrt imag part
Testing whether all values are unique:
template <typename Type>
int test_unique(const Type *f, ulong n)
// for a sorted array test whether all values are unique
// (i.e whether no value is repeated)
//
// returns 0 if all values are unique
// else returns index of the second element in the first pair found
//
// this function is not called "is_unique()" because it
// returns 0 (=="false") for a positive answer
Trang 20return 0;
}
The same thing, but for inexact types (floats): the maximal (absolute) difference within which twocontiguous elements will still be considered equal can be provided as additional parameter One subtlepoint is that the values can slowly ‘drift away’ unnoticed by this implementation: Consider a long array
where each difference computed has the same sign and is just smaller than da, say it is d = 0.6·da The difference of the first and last value then is 0.6 · (n − 1) · d which is greater than da for n ≥ 3.
template <typename Type>
int test_unique_approx(const Type *f, ulong n, Type da)
// for a sorted array test whether all values are
// unique within some tolerance
// (i.e whether no value is repeated)
//
// returns 0 if all values are unique
// else returns index of the second element in the first pair found
An alternative way to deal with inexact types is to apply
template <typename Type>
void quantise(Type *f, ulong n, double q)
//
// in f[] set each element x to q*floor(1/q*(x+q/2))
// e.g.: q=1 ==> round to nearest integer
// q=1/1000 ==> round to nearest multiple of 1/1000
// For inexact types (float or double)