• No results found

8 Type tt = f[g]; 9 f[g] = t; 10 t = tt; 11 g = x(g); // <--= 12 } 13 f[g] = t; 14 [--snip--]

2.5

Random permutations

The following routine randomly permutes an array with arbitrary elements [FXT: perm/permrand.h]:

1 template <typename Type>

2 void random_permute(Type *f, ulong n)

3 {

4 for (ulong k=n; k>1; --k)

5 {

6 const ulong i = rand_idx(k);

7 swap2(f[k-1], f[i]);

8 }

9 }

An alternative version for the loop is:

1 for (ulong k=1; k<n; ++k)

2 {

3 const ulong i = rand_idx(k+1);

4 swap2(f[k], f[i]);

5 }

The method is given in [132], it is sometimes calledKnuth shuffle orFisher-Yates shuffle, see [213, alg.P, sect.3.4.2]. We use the auxiliary routine [FXT: aux0/rand-idx.h]

1 inline ulong rand_idx(ulong m)

2 // Return random number in the range [0, 1, ..., m-1].

3 // Must have m>0.

4 {

5 if ( m==1 ) return 0; // could also use % 1

6 ulong x = (ulong)rand();

7 x ^= x>>16; // avoid using low bits of rand() alone

8 return x % m;

9 }

A random permutation is computed by applying the function to the identical permutation:

1 void random_permutation(ulong *f, ulong n)

2 // Create a random permutation

3 {

4 for (ulong k=0; k<n; ++k) f[k] = k;

5 random_permute(f, n);

6 }

A slight modification of the underlying idea can be used for a routine for random selection from a list with only one linear read. LetLbe a list ofnitemsL1, . . . , Ln.

1. Sett=L1, set k= 1.

2. Setk=k+ 1. Ifk > nreturnt. 3. With probability 1/k sett=Lk.

4. Go to step 2.

Note that one does not need to known, the number of elements in the list, in advance: replace the second statement in step 2 by “If there are no more elements, returnt”.

2.5.1

Random cyclic permutation

A routine to apply a random cyclic permutation (as defined in section 2.2.1 on page 105) to an array is [FXT: perm/permrand-cyclic.h]

1 template <typename Type>

2 void random_permute_cyclic(Type *f, ulong n)

3 // Permute the elements of f by a random cyclic permutation.

4 {

5 for (ulong k=n-1; k>0; --k)

6 {

7 const ulong i = rand_idx(k);

8 swap2(f[k], f[i]);

9 }

10 }

The method is called Sattolo’s algorithm, see [296], and also [171] and [362]. It can be described as a method to arrange people in a cycle: Assume there arenpeople in a room. Let the first person choose a successor out of the remaining persons not yet chosen. Then let the person just chosen make the next choice of a successor. Repeat until everyone has been chosen. Finally, let the first person be the successor of the last person chosen.

The cycle representation of a random cyclic permutation can be computed by applying a random per- mutation to all elements (of the identical permutation) except for the first element.

2.5.2

Random prefix of a permutation

A length-mprefix of a random permutation ofnelements is computed by the following routine that uses justO(m) operations [FXT: perm/permrand-pref.h]:

1 template <typename Type>

2 void random_permute_pref(Type *f, ulong n, ulong m)

3 // Set the first m elements to a prefix of a random permutation.

4 // Same as: set the first m elements of f to a random permutation

5 // of a random selection of all n elements.

6 // Must have m<=n-1. 7 // Same as random_permute() if m>=n-1. 8 { 9 if ( m>n-1 ) m = n-1; // m>n is not admissable 10 for (ulong k=0,j=n; k<m; ++k,--j) 11 {

12 const ulong i = k + rand_idx(j); // k<=i<n

13 swap2(f[k], f[i]);

14 }

15 }

The first element is randomly selected from allnelements, the second from the remainingn−1 elements, and so on. Thus there aren(n−1). . . (n−m+ 1) =n!/(n−m)! length-mprefixes of permutations of nelements.

2.5.3

Random permutation with prescribed parity

To compute a random permutation with prescribed parity (as defined in section 2.2.2 on page 105) we keep track of the parity of the generated permutation and change it via a single transposition if necessary [FXT: perm/permrand-parity.h]:

1 template <typename Type>

2 void random_permute_parity(Type *f, ulong n, bool par)

3 // Randomly permute the elements of f, such that the

4 // parity of the permutation equals par.

5 // I.e. the minimal number of transpositions of the

6 // permutation is even if par==0, else odd.

7 // Note: with n<=1 there is no odd permutation.

8 {

9 if ( (par==1) && (n<2) ) return; // not admissable

10

11 bool pr = 0; // identity has even parity

2.5: Random permutations 113

13 {

14 const ulong i = rand_idx(k+1);

15 swap2(f[k], f[i]);

16 pr ^= ( k != i ); // parity changes with swap

17 }

18

19 if ( par!=pr ) swap2(f[0], f[1]); // need to change parity

20 }

2.5.4

Random permutation with

m

smallest elements in prescribed order

In the last algorithm we conditionally changed the positions 0 and 1. Now we conditionally change the elements 0 and 1 to preserve their relative order [FXT: perm/permrand-ord.h]:

1 template <typename Type>

2 void random_ord01_permutation(Type *f, ulong n)

3 // Random permutation such that elements 0 and 1 are in order.

4 {

5 random_permutation(f, n);

6 ulong t = 0;

7 while ( f[t]>1 ) ++t;

8 if ( f[t]==0 ) return; // already in correct order

9 f[t] = 0;

10 do { ++t; } while ( f[t]!=0 );

11 f[t] = 1;

12 }

The routine generates half of all the permutations but not their reversals. The following routine fixes the relative order of themsmallest elements:

1 template <typename Type>

2 void random_ordm_permutation(Type *f, ulong n, ulong m)

3 // Random permutation such that the m smallest elements are in order.

4 // Must have m<=n.

5 {

6 random_permutation(f, n);

7 for (ulong t=0,j=0; j<m; ++t) if ( f[t]<m ) { f[t]=j; ++j; }

8 }

A random permutation where 0 appears as the last of themsmallest elements is computed by:

1 template <typename Type>

2 void random_lastm_permutation(Type *f, ulong n, ulong m)

3 // Random permutation such that 0 appears as last of the m smallest elements.

4 // Must have m<=n.

5 {

6 random_permutation(f, n);

7 if ( m<=1 ) return;

8

9 ulong p0=0, pl=0; // position of 0, and last (in m smallest elements)

10 for (ulong t=0, j=0; j<m; ++t)

11 {

12 if ( f[t]<m )

13 {

14 pl = t; // update position of last

15 if ( f[t]==0 ) { p0 = t; } // record position of 0

16 ++j; // j out of m smallest found

17 }

18 }

19 // here t is the position of the last of the m smallest elements

20 swap2( f[p0], f[pl] );

21 }

2.5.5

Random permutation with prescribed cycle type

To create a random permutation with given cycle type (see section 11.1.2 on page 278) we first give a routine for permuting by one cycle of prescribed length. We need to keep track of the set of unprocessed elements. The positions of those (available) elements are stored in an array r[]. After an element is processed its index is swapped with the last available index [FXT: perm/permrand-cycle-type.h]:

1 template <typename Type>

2 inline ulong random_cycle(Type *f, ulong cl, ulong *r, ulong nr)

4 // r[0], ..., r[nr-1]) by a random cycle of length cl.

5 // Must have nr >= cl and cl != 0.

6 {

7 if ( cl==1 ) // just remove a random position from r[]

8 {

9 const ulong i = rand_idx(nr);

10 --nr; swap2( r[nr], r[i] ); // remove position from set

11 }

12 else // cl >= 2

13 {

14 const ulong i0 = rand_idx(nr);

15 const ulong k0 = r[i0]; // position of cycle leader

16 const Type f0 = f[k0]; // cycle leader

17 --cl;

18 --nr; swap2( r[nr], r[i0] ); // remove position from set

19

20 ulong kp = k0; // position of predecessor in cycle

21 do // create cycle

22 {

23 const ulong i = rand_idx(nr);

24 const ulong k = r[i]; // random available position

25 f[kp] = f[k]; // move element

26 --nr; swap2( r[nr], r[i] ); // remove position from set

27 kp = k; // update predecessor 28 } 29 while ( --cl ); 30 31 f[kp] = f0; // close cycle 32 } 33 34 return nr; 35 }

To permute according to a cycle type, we call the routine according to the elements of an arrayc[]that specifies how many cycles of each length are required:

1 template <typename Type>

2 inline void random_permute_cycle_type(Type *f, ulong n, const ulong *c, ulong *tr=0)

3 // Permute the elements of f by a random permutation of prescribed cycle type.

4 // The permutation will have c[k] cycles of length k+1.

5 // Must have s <= n where s := sum(k=0, n-1, c[k]).

6 // If s < n then the permutation will have n-s fixed points.

7 {

8 ulong *r = tr;

9 if ( tr==0 ) r = new ulong[n];

10 for (ulong k=0; k<n; ++k) r[k] = k; // initialize set

11 ulong nr = n; // number of elements available

12 // available positions are r[0], ..., r[nr-1]

13

14 for (ulong k=0; k<n; ++k)

15 {

16 ulong nc = c[k]; // number of cycles of length k+1;

17 if ( nc==0 ) continue; // no cycles of this length

18 const ulong cl = k+1; // cycle length

19 do 20 { 21 nr = random_cycle(f, cl, r, nr); 22 } 23 while ( --nc ); 24 } 25 26 if ( tr==0 ) delete [] r; 27 }

2.5.6

Random self-inverse permutation

For the self-inverse permutations (involutions) we need to compute certain branch probabilities. At each step either a 2-cycle or a fixed point is generated. The probability that the next step generates a fixed point is R(n) =I(n−1)/I(n) where I(n) is the number of involutions of nelements. This can be seen by dividing relation 11.1-6 on page 279 byI(n):

1 = I(n−1) I(n) +

(n−1)I(n−2)

2.5: Random permutations 115 At each step we generate a random numbert where 0≤t <1, ift > R(n) then a 2-cycle is created, else a fixed point. The quantities I(n) cannot be used with fixed precision arithmetic because an overflow would occur for largen. Instead, we updateR(n) via

R(n+ 1) = 1

1 +n R(n) (2.5-2)

The recurrence is numerically stable [FXT: perm/permrand-self-inverse.h]:

1 inline void next_involution_branch_ratio(double &rat, double &n1)

2 {

3 n1 += 1.0;

4 rat = 1.0/( 1.0 + n1*rat );

5 }

The following routine initializes the array of valuesR(n):

1 inline void init_involution_branch_ratios(double *b, ulong n)

2 { 3 b[0] = 1.0; 4 double rat = 0.5, n1 = 1.0; 5 for (ulong k=1; k<n; ++k) 6 { 7 b[k] = rat; 8 next_involution_branch_ratio(rat, n1); 9 } 10 }

1 template <typename Type>

2 inline void random_permute_self_inverse(Type *f, ulong n,

3 ulong *tr=0, double *tb=0, bool bi=false)

4 // Permute the elements of f by a random self-inverse permutation (an involution).

5 // Set bi:=true to signal that the branch probabilities in tb[]

6 // have been precomputed (via init_involution_branch_ratios()).

7 {

8 ulong *r = tr;

9 if ( tr==0 ) r = new ulong[n];

10 for (ulong k=0; k<n; ++k) r[k] = k;

11 ulong nr = n; // number of elements available

12 // available positions are r[0], ..., r[nr-1]

13

14 double *b = tb;

15 if ( tb==0 ) { b = new double[n]; bi=false; }

16 if ( !bi ) init_involution_branch_ratios(b, n);

17

18 while ( nr>=2 )

19 {

20 const ulong x1 = nr-1;

21 const ulong r1 = r[x1]; // available position

22 --nr; // no swap needed if x1==last

23

24 const double rat = b[nr]; // probability to choose fixed point

25

26 const double t = rnd01(); // 0 <= t < 1

27 if ( t > rat ) // 2-cycle

28 {

29 const ulong x2 = rand_idx(nr);

30 const ulong r2 = r[x2]; // random available position != r1

31 --nr; swap2(r[x2], r[nr]);

32 swap2( f[r1], f[r2] );

33 }

34 // else // fixed point, nothing to do

35 }

36

37 if ( tr==0 ) delete [] r;

38 if ( tb==0 ) delete [] b;

39 }

The auxiliary functionrand01()returns a random number twhere 0≤t <1 [FXT: aux0/randf.cc].

2.5.7

Random derangement

In each step of the routine for a random permutation without fixed points (a derangement) we join two cycles and decide whether to close the resulting cycle. The probability of closing isB(n) = (n−1)D(n−

2)/D(n) whereD(n) is the number of derangements ofnelements. This can be seen by dividing relation 11.1-12a on page 280 byD(n): 1 = (n−1)D(n−1) D(n) + (n−1)D(n−2) D(n) (2.5-3)

The probabilityB(n) is close to 1/n for largen. Already forn >30 the relative error (forB(n) versus 1/n) is less than 10−32, soB(n) is indistinguishable from 1/nwith floating-point types where the mantissa has at most 106 bits. We compute a table of just 32 valuesB(n) [FXT: perm/permrand-derange.h]:

1 // number of precomputed branch ratios:

2 #define NUM_PBR 32 // OK for up to 106-bit mantissa

3

4 inline void init_derange_branch_ratios(double *b)

5 {

6 b[0] = 0.0; b[1] = 1.0;

7 double dn0 = 1.0, dn1 = 0.0, n1 = 1.0;

8 for (ulong k=2; k<NUM_PBR; ++k)

9 {

10 const double dn2 = dn1;

11 next_num_derangements(dn0, dn1, n1);

12 const double rat = (n1) * dn2/dn0; // == (n-1) * D(n-2) / D(n)

13 b[k] = rat;

14 }

15 }

TheD(n) are updated using D(n) = (n−1) [D(n−1) +D(n−2)]:

1 inline void next_num_derangements(double &dn0, double &dn1, double &n1)

2 {

3 const double dn2 = dn1; dn1 = dn0; n1 += 1.0;

4 dn0 = n1*(dn1 + dn2);

5 }

Now theB(n) are computed as

1 inline double derange_branch_ratio(const double *b, ulong n)

2 {

3 if ( n<NUM_PBR ) return b[n];

4 else return 1.0/(double)n; // relative error < 1.0e-32

5 }

The routine for a random derangement is

1 template <typename Type>

2 inline void random_derange(Type *f, ulong n,

3 ulong *tr=0,

4 double *tb=0, bool bi=false)

5 // Permute the elements of f by a random derangement.

6 // Set bi:=true to signal that the branch probabilities in tb[]

7 // have been precomputed (via init_derange_branch_ratios()).

8 // Must have n > 1.

9 {

10 ulong *r = tr;

11 if ( tr==0 ) r = new ulong[n];

12 for (ulong k=0; k<n; ++k) r[k] = k;

13 ulong nr = n; // number of elements available

14 // available positions are r[0], ..., r[nr-1]

15

16 double *b = tb;

17 if ( tb==0 ) { b = new double[NUM_PBR]; bi=false; }

18 if ( !bi ) init_derange_branch_ratios(b);

19

20 while ( nr>=2 )

21 {

22 const ulong x1 = nr-1; // last element

23 const ulong r1 = r[x1];

24

25 const ulong x2 = rand_idx(nr-1); // random element !=last

26 const ulong r2 = r[x2];

27

28 swap2( f[r1], f[r2] ); // join cycles containing f[r1] and f[r2]

29

30 // remove r[x1]=r1 from set:

2.5: Random permutations 117

32

33 const double rat = derange_branch_ratio(b, nr);

34 const double t = rnd01(); // 0 <= t < 1

35 if ( t < rat ) // close cycle

36 {

37 // remove r[x2]=r2 from set:

38 --nr; swap2(r[x2], r[nr]);

39 }

40 // else cycle stays open

41 }

42

43 if ( tr==0 ) delete [] r;

44 if ( tb==0 ) delete [] b;

45 }

The method is (essentially) given in [245]. A generalization for permutations with all cycles of length ≥mis given in [24].

2.5.8

Random connected permutation

A random connected (indecomposable) permutation can be computed via the rejection method: create a random permutation, if it is not connected, repeat. An implementation is [FXT: perm/permrand- connected.h]

1 inline void random_connected_permutation(ulong *f, ulong n)

2 {

3 for (ulong k=0; k<n; ++k) f[k] = k;

4 do { random_permute(f, n); } while ( ! is_connected(f, n) );

5 }

The method is efficient because the number of connected permutations is (asymptotically) given by C(n) = n! 1−2 n−O 1 n2 (2.5-4) That is, the test for connectedness is expected to fail with a probability of about 2/n for largen. The probability of failure can be reduced to about 2/n2by avoiding the permutations that fix either the first or the last element. The small cases (n≤3) are treated separately:

1 if ( n<=3 ) 2 { 3 for (ulong k=0; k<n; ++k) f[k] = k; 4 if ( n<2 ) return; // [] or [0] 5 swap2(f[0], f[n-1]); 6 if ( n==2 ) return; // [1,0] 7 // here: [2,1,0]

8 const ulong i = rand_idx(3);

9 swap2(f[1], f[i]); 10 // i = 0 ==> [1,2,0] 11 // i = 1 ==> [2,1,0] 12 // i = 2 ==> [2,0,1] 13 return; 14 } 15 16 do 17 { 18 for (ulong k=0; k<n; ++k) f[k] = k; 19 20 while ( 1 ) 21 {

22 const ulong i0 = 1 + rand_idx(n-1); // first element must move

23 const ulong i1 = 1 + rand_idx(n-1); // f[1] will be last element

24 swap2( f[0], f[i0] );

25 swap2( f[1], f[i1] );

26 if ( f[1]==n-1 ) // undo swap and repeat (here: f[0]!=0)

27 {

28 swap2( f[1], f[i1] );

29 swap2( f[0], f[i0] );

30 continue; // probability 1/n but work only O(1)

31 }

32 else break;

34

35 swap2(f[1], f[n-1]); // move f[1] to last

36 // here: f[0] != 0 and f[n-1] != n-1

37 random_permute(f+1, n-2); // permute 2nd ... 2nd last element

38 }

39 while ( ! is_connected(f, n) );