プログラムの解釈

 投稿者:GAI  投稿日:2013年10月30日(水)22時01分53秒
  順列を生成するプログラムで、以下のものをトレースしながらその考え方を理解しようと試みていたんですが、複雑に入り組んでいて特に再帰的構造での動きがいろいろな変数を巧に使い分けられていて、どのような動きになっているのかが解り難い感じでした。

全体的流れの構成や、順列の生成をどの様な考え方で作り出そうとしているのかをプログラムにそって説明して頂けませんでしょうか?



REM 1~nの順列を辞書式順序で生成する。
DECLARE EXTERNAL SUB perm
DIM a(100)
INPUT n
MAT a=ZER(n)
FOR i=1 TO n
   LET a(i)=i
NEXT i
CALL perm(a,1)
END
EXTERNAL SUB perm(a(),n)
LET m=UBOUND(a)
IF n=m THEN
   MAT PRINT a;
ELSE
   FOR i=n TO m
      LET t=a(i)
      FOR j=i-1 TO n STEP -1
         LET a(j+1)=a(j)
      NEXT j
      LET a(n)=t
      CALL perm(a,n+1)
      LET t=a(n)
      FOR j=n TO i-1
         LET a(j)=a(j+1)
      NEXT j
      LET a(i)=t
   NEXT i
END IF
END SUB
 

Re: プログラムの解釈

 投稿者:山中和義  投稿日:2013年10月31日(木)10時05分55秒
  > No.3184[元記事へ]

GAIさんへのお返事です。

> 順列を生成するプログラム

アルゴリズムの本にあります。

サブルーチン perm の処理
先頭から(n-1)までを固定して、その並びから始まる最小のものを用意する(される)。
  n
  ↓
1  i      m
□□□□□□□□□
  └┘ ← この範囲を右へ1つローテイトさせる
  └─┘
  └──┘
    :
  └─────┘
とする。
再帰呼出しされるごとに、辞書式順序で先頭から(n-1)までが確定されていく。


REM 1~nの順列を辞書式順序で生成する。
DECLARE EXTERNAL SUB perm
DIM a(100)
INPUT n
MAT a=ZER(n)
FOR i=1 TO n !並び 1,2,3,4,…,n
   LET a(i)=i
NEXT i
CALL perm(a,1)
END
EXTERNAL SUB perm(a(),n)
LET m=UBOUND(a)
!----- debug -----
PRINT REPEAT$(" ",(m+1)*(n-1)); !階層
FOR i=1 TO m !並びを表示する
   PRINT STR$(a(i));
NEXT i
PRINT
!----- debug -----
IF n=m THEN !すべて並んだなら
!!MAT PRINT a;
ELSE
   FOR i=n TO m !ローテイトする範囲を設定する
      LET t=a(i) !右へ1つローテイト
      FOR j=i-1 TO n STEP -1
         LET a(j+1)=a(j)
      NEXT j
      LET a(n)=t

      CALL perm(a,n+1) !次へ
      LET t=a(n) !左へ1つローテイトで元に戻す
      FOR j=n TO i-1
         LET a(j)=a(j+1)
      NEXT j
      LET a(i)=t

   NEXT i

END IF
END SUB



n=4の場合の実行結果

   n=1 n=2 n=3 n=4
? 4
1234
     1234
          1234
               1234
               1243
          1324
               1324
               1342
          1423
               1423
               1432
     2134
          2134
               2134
               2143
          2314
               2314
               2341
          2413
               2413
               2431
     3124
          3124
               3124
               3142
          3214
               3214
               3241
          3412
               3412
               3421
     4123
          4123
               4123
               4132
          4213
               4213
               4231
          4312
               4312
               4321

 

Re: プログラムの解釈

 投稿者:山中和義  投稿日:2013年10月31日(木)11時13分40秒
  > No.3186[元記事へ]

つづき

> GAIさんへのお返事です。
>
> > 順列を生成するプログラム


nからiの範囲をローテイトさせる必要がなく、nとiを入れ替えのみで可能です。
ただし、辞書式順序にはなりませんが、代入処理が少ないためやや高速です。


REM 1~nの順列を辞書式順序でなく生成する。
DECLARE EXTERNAL SUB perm
DIM a(100)
INPUT n
MAT a=ZER(n)
FOR i=1 TO n !並び 1,2,3,4,…,n
   LET a(i)=i
NEXT i
CALL perm(a,1)
END
EXTERNAL SUB perm(a(),n)
LET m=UBOUND(a)
!----- debug -----
PRINT REPEAT$(" ",(m+1)*(n-1)); !階層
FOR i=1 TO m !並びを表示する
   PRINT STR$(a(i));
NEXT i
PRINT
!----- debug -----
IF n=m THEN !すべて並んだなら
!!MAT PRINT a;
ELSE
   FOR i=n TO m !箇所を設定する
      LET t=a(n) !a[n]とa[i]を入れ替える
      LET a(n)=a(i)
      LET a(i)=t

      CALL perm(a,n+1) !次へ
      LET t=a(n) !元に戻す
      LET a(n)=a(i)
      LET a(i)=t

   NEXT i
END IF
END SUB


実行結果

? 4
1234
     1234
          1234
               1234
               1243
          1324
               1324
               1342
          1432
               1432
               1423
     2134
          2134
               2134
               2143
          2314
               2314
               2341
          2431
               2431
               2413
     3214
          3214
               3214
               3241
          3124
               3124
               3142
          3412
               3412
               3421
     4231
          4231
               4231
               4213
          4321
               4321
               4312
          4132
               4132
               4123


 

Re: プログラムの解釈

 投稿者:SECOND  投稿日:2013年11月 1日(金)05時39分39秒
  > No.3184[元記事へ]

!左ローテイト1つだけの方法・・・速度は、右左ローテイトより速く、SWAP より遅い。

!●アルゴリズム

! n 番目から、右終点 m 番までを1組に、順列の CALL を受ける。
! n+1 番目から、右終点 m 番までを、次の組として、その順列を、自身へ CALL する。
! これを、rotate left n~m で、先頭を、一順させる ・・・が、プログラムの、ほぼ全部。
!
! 以上の行き着く先は、n+1=m で、組の長さが1になって始めて終端する。
!
! そこでは、各階層での先頭が履歴として、配列 a() の中に、木構造の経路の様に並び、
! 1つの順列パターンとなるので、それをプリントして RETURN する。
!
! CALL から戻ったら、
! n+1 番目から、右終点 m 番までの残りも、左ローテイトして先頭へ移し、同様に繰返す。
!
! n 番目から、右終点 m 番の、配列部分は、一順した後、必ず元へ戻るように終了させる。
! 元へ戻っていないと、各階層での CALL~RETURN で、配列が保存されず、一順管理も破綻する。
!------------------------------------------------------------------------------------

LET m=3
DIM a(m)
!
FOR i=1 TO m
   LET a(i)=i
NEXT i
CALL perm(a,1)    !( m 個の数列,  n の初期値)

SUB perm(a(),n)
   local i
   IF n=m THEN
      MAT PRINT USING REPEAT$("## ",m): a
   ELSE
      FOR i=n TO m
         CALL perm(a,n+1)
         !--------                !rotate Left 1
         LET t=a(n)               !  ┌── → ──┐
         FOR j=n TO m-1           ! a(n)・・←・・a(m)
            LET a(j)=a(j+1)
         NEXT j
         LET a(m)=t
         !--------
      NEXT i
   END IF
END SUB

END
 

Re: プログラムの解釈

 投稿者:SECOND  投稿日:2013年11月 3日(日)17時02分28秒
  > No.3188[元記事へ]

! m 個から r 個、取る場合は、以下の様に速めに終らせます。
!------------
LET m=4
LET r=2   !m 個から r 個
!
DIM a(m)
FOR i=1 TO m
   LET a(i)=i
NEXT i
CALL perm( a,1)     !CALL perm_fast( a,1) !速い

SUB perm(a(),n)
   local i
   IF n >r THEN     !● r で、速めに終らせる。
      MAT PRINT USING REPEAT$("## ",r)& " 配列の残り(無視)→"& REPEAT$("## ",m-r) :a
   ELSE
      FOR i=n TO m
         CALL perm(a,n+1)
         !--------                !rotate Left 1
         LET t=a(n)               !  ┌── → ──┐
         FOR j=n TO m-1           ! a(n)・・←・・a(m)
            LET a(j)=a(j+1)
         NEXT j
         LET a(m)=t
         !--------
      NEXT i
   END IF
END SUB


!-------------------- swap を使った速い方も、同様に・・
SUB perm_fast( a(),n)
   local i
   IF n<=r THEN
      FOR i=n TO  m
         swap a(n), a(i)
         CALL perm_fast( a,n+1)
         swap a(n), a(i)
      NEXT i
   ELSE
      MAT PRINT USING REPEAT$("## ",r)& " 配列の残り(無視)→"& REPEAT$("## ",m-r) :a
   END IF
END SUB

END
 

Re: プログラムの解釈

 投稿者:SECOND  投稿日:2013年11月22日(金)19時43分54秒
  > No.3191[元記事へ]

!途中のk番目、1回分だけのプログラムで、
! Permutation(n,r) 全体を処理する方法 6通りほど、まとめてみた

! 先の・・・で、変数名が、mPr と、n= 中間ポインター になっていたが、
!
!●● 通常の変数名、 nPr と、k= 中間ポインター に、全て変更した ●●

!順列 Permutation   nPr  P(n,r)
!---------------------
! 入力数列 a() の準備
!---------------------
LET n=3
LET r=2
DIM a(n)
!
FOR i=1 TO n
   LET a(i)=i
NEXT i

!----------------------------------------------------------
! 出力1行。速度を測るときは、MAT PRINT を削除( 先頭に"!")
!----------------------------------------------------------
SUB output1p
   MAT PRINT USING REPEAT$("## ",r)& " 配列の残り(無視)→"& REPEAT$("## ",n-r) :a
END SUB

!----------------------------------------
!(No_print, n=9,r=9 の所要時間)@P3_500MHz
!-------perm00  27.08 sec.
!-------perm50  25.15 sec.
!-------perm10  19.88 sec.
!-------perm40  15.82 sec.
!-------perm20  12.03 sec.
!-------perm30  9.83 sec.

!(No_print, n=8,r=8 の所要時間)@P3_500MHz
!-------perm00  3.02 sec.
!-------perm50  2.75 sec.
!-------perm10  2.2 sec.
!-------perm40  1.76 sec.
!-------perm20  1.37 sec.
!-------perm30  1.1 sec.

!------------------------------------------------------------
!各文の引数から、a() が消え、k のみになっているが、
!元々 a() は、local(局所変数)でないので、問題を簡単にするため
!------------------------------------------------------------
!
MAT PRINT USING REPEAT$("## ",n) :a    !入力数列 a()表示。
LET t0=TIME
CALL perm00(1)                         !( k の初期値=1)
PRINT "-------perm00 ";TIME-t0;"sec."
PRINT
!
MAT PRINT USING REPEAT$("## ",n) :a    !入力数列 a()表示。先プログラムでの a() 保存 検査
LET t0=TIME
CALL perm50(1)                         !( k の初期値=1)
PRINT "-------perm50 ";TIME-t0;"sec."
PRINT
!
MAT PRINT USING REPEAT$("## ",n) :a    !入力数列 a()表示。先プログラムでの a() 保存 検査
LET t0=TIME
CALL perm10(1)                         !( k の初期値=1)
PRINT "-------perm10 ";TIME-t0;"sec."
PRINT
!
MAT PRINT USING REPEAT$("## ",n) :a    !入力数列 a()表示。先プログラムでの a() 保存 検査
LET t0=TIME
CALL perm40(1)                         !( k の初期値=1)
PRINT "-------perm40 ";TIME-t0;"sec."
PRINT
!
MAT PRINT USING REPEAT$("## ",n) :a    !入力数列 a()表示。先プログラムでの a() 保存 検査
LET t0=TIME
CALL perm20(1)                         !( k の初期値=1)
PRINT "-------perm20 ";TIME-t0;"sec."
PRINT
!
MAT PRINT USING REPEAT$("## ",n) :a    !入力数列 a()表示。先プログラムでの a() 保存 検査
LET t0=TIME
CALL perm30(1)                         !( k の初期値=1)
PRINT "-------perm30 ";TIME-t0;"sec."
PRINT
!
MAT PRINT USING REPEAT$("## ",n) :a    !入力数列 a()表示。先プログラムでの a() 保存 検査

!----------------------------------------
!1回毎に、右左rotate 復元しながら、一巡。
!----------------------------------------
SUB perm00(k)
   local i
   IF r< k THEN
      CALL output1p
   ELSE
      FOR i=k TO n
      !  !--------                !rotate Right 1
         LET t=a(i)               !  ┌── ← ──┐
         FOR j=i-1 TO k STEP -1   ! a(k)・・→・・a(i)
            LET a(j+1)=a(j)
         NEXT j
         LET a(k)=t
         !--------
         CALL perm00(k+1)
         !--------                !rotate Left 1
         LET t=a(k)               !  ┌── → ──┐
         FOR j=k TO i-1           ! a(k)・・←・・a(i)
            LET a(j)=a(j+1)
         NEXT j
         LET a(i)=t
         !--------
      NEXT i
   END IF
END SUB

!------------------------------------------------------------------
! 毎回、右左rotate で復元せず、RETURN までに1巡復元すればよいので、
! rotate 1回( rotate の左右方向は、何れかに固定できればよい)
!------------------------------------------------------------------
SUB perm10(k)
   local i
   IF r< k THEN
      CALL output1p
   ELSE
      FOR i=k TO n
         CALL perm10(k+1)
         !--------                !rotate Left 1
         LET t=a(k)               !  ┌── → ──┐
         FOR j=k TO n-1           ! a(k)・・←・・a(n)
            LET a(j)=a(j+1)
         NEXT j
         LET a(n)=t
         !--------
      NEXT i
   END IF
END SUB

!------------------------------------------------------------------
! 一巡の要求は、先頭k位置だけで、k+1 ~n は、その残りであればよく、
! 先頭位置・巡回位置の、交換(swap)・復元(swap)を、順番に行なう方法。
! rotate のように全桁の移送をしないため、高速になる。
!------------------------------------------------------------------
SUB perm20(k)
   local i
   IF r< k THEN
      CALL output1p
   ELSE
      FOR i=k TO n
         swap a(k),a(i)
         CALL perm20(k+1)
         swap a(k),a(i)
      NEXT i
   END IF
END SUB

!-----------------------------------------------------------------------
! perm20 の高速化。最初の 交換(k,k)・復元(k,k) が無駄で k+1 からに。最速
!-----------------------------------------------------------------------
SUB perm30(k)
   local i
   IF r< k THEN
      CALL output1p
   ELSE
      CALL perm30(k+1)
      FOR i=k+1 TO n
         swap a(k),a(i)
         CALL perm30(k+1)
         swap a(k),a(i)
      NEXT i
   END IF
END SUB

!---------------------------------------------------------
! Perm30 の swap を1回で巡回させ、最後に rotate1回で補修
!---------------------------------------------------------
SUB perm40(k)
   local i
   IF r< k THEN
      CALL output1p
   ELSE
      CALL perm40(k+1)
      FOR i=k+1 TO n
         swap a(k),a(i)
         CALL perm40(k+1)
      NEXT i
      !--------                !rotate Left 1
      LET t=a(k)               !  ┌── → ──┐
      FOR j=k TO n-1           ! a(k)・・←・・a(n)
         LET a(j)=a(j+1)
      NEXT j
      LET a(n)=t
      !--------
   END IF
END SUB

!---------------------------------------------------------------------
! 保存無し 巡回だけなら、Perm40 の様に swap1回にして、別の b() で保存
!---------------------------------------------------------------------
SUB perm50(k)
   local i,b(10)       !b()の添字は、変数不可なので大きめ
   IF r< k THEN
      CALL output1p
   ELSE
      MAT b=a
      CALL perm50(k+1)
      FOR i=k+1 TO n
         swap a(k),a(i)
         CALL perm50(k+1)
      NEXT i
      MAT a=b
   END IF
END SUB

END
 

戻る