C Test LPG (Lexicographic permutation generation) C including operation counts (how many times are the steps of the C permutation routine executed to create all permutations of C n distinct elements) C C Author: Hugo Pfoertner http://www.pfoertner.org/ C C Version history: C 14.01.2003 Asymptotic values of counts added in comments C 12.01.2003 Counters for element comparisons and index tests C 09.01.2003 Counters yielding obvious results removed C 08.01.2003 Initial version C C Diagnostic counts C...+....1....+....2....+....3....+....4....+....5....+....6....+....7.. INTEGER & L22XGE, L22TES, L22YGE, L22YGT, L2XLTZ, L2XGEZ, L3TES, & L3EASI, L31TES, L31LOP, L31YGE, L41REV, L4LOP, L4KLTL COMMON /DIAGNS/ & L22XGE, L22TES, L22YGE, L22YGT, L2XLTZ, L2XGEZ, L3TES, & L3EASI, L31TES, L31LOP, L31YGE, L41REV, L4LOP, L4KLTL C LCOMA counts all comparisons between array elements of A C LCOMJ counts tests of array indices INTEGER LCOMA, LCOMJ, LCOMAO, LCOMJO COMMON /DIAGNC/ LCOMA, LCOMJ C INTEGER A(20) C OPEN ( UNIT=10, FILE='LPGTES.LIS', FORM='FORMATTED', & STATUS='UNKNOWN', RECL=160 ) C Header line WRITE (10,1001) C C Loop over all cases (limitation to N=13 due to 32 bit counts) DO 10 N = 4, 13 C L2EAS=0 C L21TES=0 C Preset diagnostic counts L22XGE=0 L22TES=0 L22YGE=0 L22YGT=0 L2XLTZ=0 L2XGEZ=0 L3TES=0 L3EASI=0 L31TES=0 L31LOP=0 L31YGE=0 L41REV=0 L4LOP=0 L4KLTL=0 C LCOMA = 0 LCOMAO = 0 LCOMJ = 0 LCOMJO = 0 C Preset array to be permuted DO 20 I = 1, N A(I) = I 20 CONTINUE C C IFAC = 0 30 CONTINUE C IFAC = IFAC + 1 C LPG returns NEXT=0 after N! calls (for distinct A(i)) C (IFAC exceeds range 2*31-1 for N=13) CALL LPG ( N, A, NEXT) CDIAG IF ( LCOMA .GT. 1000000000 ) THEN LCOMAO = LCOMAO + 1 LCOMA = LCOMA - 1000000000 ENDIF IF ( LCOMJ .GT. 1000000000 ) THEN LCOMJO = LCOMJO + 1 LCOMJ = LCOMJ - 1000000000 ENDIF CDIAG C Terminate when reverse order is reached IF ( NEXT .EQ. 0 ) GOTO 40 GOTO 30 40 CONTINUE C Output of diagnostic counts WRITE (10,1000) N, & L22XGE, L22TES, L22YGE, L22YGT, L2XLTZ, L2XGEZ, L3TES, & L3EASI, L31TES, L31LOP, L31YGE, L41REV, L4LOP, L4KLTL 1000 FORMAT ( I3, 14I11 ) 1001 FORMAT ( ' N',5X,'L22XGE', 5X,'L22TES', 5X,'L22YGE', & 5X,'L22YGT', 5X,'L2XLTZ', 5X,'L2XGEZ', & 5X,' L3TES', 5X,'L3EASI', 5X,'L41TES', & 5X,'L31LOP', 5X,'L31YGE', 5X,'L41REV', & 5X,' L4LOP', 5X,'L4KLTL') WRITE (*,1002) N, LCOMAO, LCOMA, LCOMJO, LCOMJ 1002 FORMAT (I3, I4, I9.9, I4, I9.9) 10 CONTINUE END C...+....1....+....2....+....3....+....4....+....5....+....6....+....7.. SUBROUTINE LPG ( N, A, NEXT ) C C Generate next permutation of N elements A(i) in C lexicographic order C C The method used is C "Algorithm L (Lexicographic permutation generation)" C described in Chapter 7.2.1.2 C of Donald E. Knuth's The Art of Computer Programming, Volume 4, C Combinatorial Algorithms, C Volume 4A, Enumeration and Backtracking. C C with the modification proposed in the answer to C exercise 1, which is based on "Algorithm 28, C PERMUTATIONS OF THE ELEMENTS OF A VECTOR IN LEXICOGRAPHIC ORDER" C by J. P. N. Phillips. The Computer Journal, Volume 10, C Issue 3: November 1967. (Algorithms supplement), C also available online: C http://www3.oup.co.uk/computer_journal/hdb/Volume_10/Issue_03/default.html C C The printed version of TAOCP Vol.4 is expected to be available C in the year 2007. D. Knuth has put some chapters online, including C Pre-fascicle 2b (generating all permutations), available at C http://www-cs-faculty.stanford.edu/~knuth/fasc2b.ps.gz C Most of the comments are copied verbally from Knuth's C description C C This subroutine is a special version including counters for C all paths of the algorithm. The main result found is: C C To create all permutations of n distinct elements the C number of comparisons between the array elements becomes C 2.410756*n! for large n (e.g. n>8) C The exact number of comparisons c(n) is given by the recursion C c(3)=11, c(n)=n*c(n-1)+n*(n+1)/2 for n>=4 C OEIS: A079884(n) = 2*n! - 1 + A079750(n) + A079753(n) C c(3)...c(14): 11,54,285,1731,12145,97196,874809,8748145, C 96229661,1154756010,15011828221,210165595199 C C The required number of index tests (test for termination and C test in the final reversion loop) becomes C 0.2613625*n! for large n, if the test for n=3 is excluded. C The exact number of index comparisons i(n) is given by the recursion C i(3)=0, i(n)=n*i(n-1)+1+(n-1)*floor((n-1)/2) for n>=4 C OEIS: A079885(n) = A079751(n) + A079755(n) C i(3)...i(14): 0,4,29,185,1314,10534,94839,948427,10432748, C 125193032,1627509489,22785132925 C If n=3 is included the additionally required termination test C adds n!/6 index comparisons, increasing the number of index C comparisons to 0.428029*n! (63.8% more index comparisons). C C Author: Hugo Pfoertner http://www.pfoertner.org/ C C Change history: C 10.01.2003 Results from diagnostic counts included as comments C 08.01.2003 Diagnostic counts C 06.01.2003 Optional code to treat N<4 C 05.01.2003 Initial version C C Call parameters: INTEGER N, A(N), NEXT C N: number of elements to permute. N>=4 must hold (not checked) C A: Integer array holding the elements. C to be preset externally (e.g. by 1,2,3,...,n-1,n) C A is overwritten by the lexicographically next permutation C NEXT: =1 if A has been successfully updated C =0 if A was already in reverse lexicographic order C i.e. if a(n)>=a(n-1)>=....>=a(2)>=a(1) C In this case no change is made to A C C Local variables INTEGER J, K, L, X, Y, Z, H C C Diagnostic counts C...+....1....+....2....+....3....+....4....+....5....+....6....+....7.. INTEGER & L22XGE, L22TES, L22YGE, L22YGT, L2XLTZ, L2XGEZ, L3TES, & L3EASI, L31TES, L31LOP, L31YGE, L41REV, L4LOP, L4KLTL COMMON /DIAGNS/ & L22XGE, L22TES, L22YGE, L22YGT, L2XLTZ, L2XGEZ, L3TES, & L3EASI, L31TES, L31LOP, L31YGE, L41REV, L4LOP, L4KLTL INTEGER LCOMA, LCOMJ COMMON /DIAGNC/ LCOMA, LCOMJ C C Preset return status NEXT = 1 C C In a generally applicable version for all N all lines starting C with comment CS have to be activated CSC Start part necessary for N<3 CS IF ( N .LE. 1 ) THEN CS NEXT = 0 CS RETURN CS ENDIF CS IF ( N .EQ. 2 ) THEN CS IF ( A(1) .LT. A(2) ) THEN CS H = A(1) CS A(1) = A(2) CS A(2) = H CS ELSE CS NEXT = 0 CS ENDIF CS RETURN CS ENDIF CSC End part necessary for N<3 C C Find the largest j such that a(j) can be increased C C Step L2 finds j=n-1 half of the time when the elements are distinct, C because exactly n!/2 of the n! permutations have a(n-1)=4 LCOMJ = LCOMJ + 1 CS IF ( J .EQ. 0 ) THEN CS NEXT = 0 CS RETURN CS ENDIF CSC End part necessary for N=3 Y = A(J) 10 CONTINUE CDIAG L22TES = L22TES + 1 CDIAG L22TES_sum = 0, 4, 25, 156, 1099, 8800, 79209, 792100, 8713111, CDIAG 104557344, 1359245485 -> 0.2182818*n! for large n CDIAG for n = 3, 4, .... , 13 OEIS A079750 CDIAG a079750(3)=0, a079750(n) = n * a079750 + n for n>=4 LCOMA = LCOMA + 1 CDIAG IF ( Y .GE. X ) THEN CDIAG L22YGE = L22YGE + 1 CDIAG L22YGE_sum = 0, 1, 6, 37, 260, 2081, 18730, 187301, 2060312, CDIAG 24723745, 321408686 -> 0.0516151*n! for large n CDIAG for n = 3, 4, .... , 13 OEIS A079751 CDIAG a079751(3)=0, a079751(n) = n * a079751(n-1) + 1 for n>=4 CDIAG J = J - 1 C Reverse lexicographic order reached? CDIAG LCOMJ = LCOMJ + 1 CDIAG IF ( J .EQ. 0 ) THEN C Terminate NEXT = 0 RETURN ENDIF X = Y Y = A(J) GOTO 10 ENDIF C C At this point j is the largest subscript such that we have already C visited all permutations beginning with a(1)...a(j). Therefore C the lexicographically next permutation will increase the value C of a(j). C ELSE C C Test for next easiest case passed, permute last 3 elements C CDIAG L22YGT = L22YGT + 1 CDIAG L22YGT_sum = n!/3 LCOMA = LCOMA + 1 CDIAG IF ( X .LT. Z ) THEN CDIAG L2XLTZ = L2XLTZ + 1 CDIAG L22XLTZ_sum = n!/6 CDIAG A(N-2) = Z A(N-1) = X A(N) = Y ELSE CDIAG L2XGEZ = L2XGEZ + 1 CDIAG L22XGEZ_sum = n!/6 CDIAG A(N-2) = Y A(N-1) = Z A(N) = X ENDIF RETURN ENDIF C C L3 Increase a(j) by the smallest feasible amount C C If a(j) >= a(l) decrease l repeatedly until a(j)a(l) C C L3' Easy increase? CDIAG L3TES = L3TES + 1 CDIAG L3TES_sum = n!/6 - 1 LCOMA = LCOMA + 1 CDIAG IF ( Y .LT. Z ) THEN CDIAG L3EASI = L3EASI + 1 CDIAG L3EASI_sum = L22YGE_sum (OEIS A079751) A(J) = Z A(J+1) = Y A(N) = X C Done, go to L4.1' GOTO 41 ENDIF C L3.1' Increase A(J) L = N - 1 CDIAG L31TES = L31TES + 1 C...+....1....+....2....+....3....+....4....+....5....+....6....+....7.. CDIAG L31TES_sum = 0, 2, 13, 82, 579, 4638, 41749, 417498, 4592487, CDIAG 55109854, 716428113 -> 0.1150515*n! for large n CDIAG for n = 3, 4, .... , 13 OEIS A079752 CDIAG a079752(3)=0, a079752(n) = n * a079752(n-1) + n - 2 for n>=4 CDIAG 20 CONTINUE CDIAG L31LOP = L31LOP + 1 CDIAG L31LOP_sum = 0, 3, 21, 136, 967, 7757, 69841, 698446, 7682951, CDIAG 92195467, 1198541137 -> 0.1924742*n! for large n CDIAG for n = 3, 4, .... , 13 OEIS A079753 CDIAG a079753(3)=0, a079753(n)=n*a079753(n-1) + (n-1)*(n-2)/2 for n>=4 CDIAG a079753(n)=a079752(n)+a079754(n) LCOMA = LCOMA + 1 CDIAG IF ( Y .GE. A(L) ) THEN CDIAG L31YGE = L31YGE + 1 CDIAG L32YGE_sum = 0, 1, 8, 54, 388, 3119, 28092, 280948, 3090464, CDIAG 37085613, 482113024 -> 0.0774227*n! for large n CDIAG for n = 3, 4, .... , 13 OEIS A079754 CDIAG a079754(3)=0, a079754(n)=n*a079754(n-1) + (n-2)*(n-3)/2 for n>=4 CDIAG L = L - 1 GOTO 20 ENDIF C C Since a(j+1) >= ... >= a(n), element a(l) is the smallest element C greater than a(j) that can legitimately follow a(1)...a(j-1) in a C permutation. Before the interchange we have a(j+1)>=...>=a(l-1)>=a(l) C >a(j)>=a(l+1)>=...>=a(n) C C Interchange C A(J) = A(L) A(L) = Y C C After the interchange, we have C a(j+1)>=...>=a(l-1)>=a(j)>a(l)>=a(l+1)>=...>=a(n) C C Find the lexicographically least way to extend the new a(1)...a(j) C to a complete pattern: C The first permutation beginning with the current prefix a(1)...a(j) C is a(1)...a(j)a(n)...a(j+1), and step L4 produces it by doing C floor (n-j)/2 interchanges C C L4 Reverse a(j+1)...a(n) C L4' Begin to reverse A(N) = A(J+1) A(J+1) = Z C L4.1' Reverse a(j+1)...a(n-1) 41 CONTINUE CDIAG L41REV = L41REV + 1 CDIAG L41REV_sum = n!/6 - 1 K = J + 2 L = N - 1 30 CONTINUE CDIAG L4LOP = L4LOP + 1 CDIAG L4LOP_sum = 0, 3, 23, 148, 1054, 8453, 76109, 761126, 8372436, CFIAG 100469287, 1306100803 -> 0.2097473*n! for large n CDIAG for n = 3, 4, .... , 13 OEIS A079755 CDIAG a079755(3) = 0 CDIAG a079755(n) = n * a079755(n-1) + (n-1)*floor((n-1)/2) for n>=4 LCOMJ = LCOMJ + 1 CDIAG IF ( K .LT. L ) THEN CDIAG L4KLTL = L4KLTL + 1 CDIAG L4KLTL_sum = 0, 0, 4, 29, 215, 1734, 15360, 156327, 1719637, CDIAG 20635688, 268264004 -> 0.0430806*n! for large n CDIAG for n = 3, 4, .... , 13 OEIS A079756 CDIAG a079756(4) = 0 CDIAG a079756(n) = n*a079756(n-1) + (n-1)*(floor((n-1)/2)-1) for n>=5 CDIAG H = A(K) A(K) = A(L) A(L) = H K = K + 1 L = L - 1 GOTO 30 ENDIF RETURN C End of SUBROUTINE LPG END