マルチスレッド版数独自動生成ソフトC++コードを題材とする超初心者のためのVisual Studio C++講義
第9章 関数の再帰的使用

第10話 部屋数3・生徒数3の場合におけるトレースその2と新しい解釈

プログラムコード再掲

#include<iostream>//インクルードファイルiostreamの読み込み

#include<conio.h>//while(!_kbhit());を使うためのお呪い

#include<strins> //文字列変数を使えるようにするために組み込む

#include <iomanip> //setprecisionを使えるように組み込む

#include <cmath>//powなどを使うときに必要

#include <ctime>//time()(←現時刻発生する関数)を使うために必要

using namespace std;//coutを使うときに必要なお呪い

const int n = 5;//具体的な数字を使うのではなく、 n を使うと汎用性のあるプログラムになる!

void f(int s);//順列生成関数←引数名をgからsに変更(2026年3月19日)

int a[25]; //将来5次魔方陣まで生成できるように25に変更

int cn = 0; //anに変更 変更理由はnuberの頭文字nは整数を表す場合が多いからです

int main() {

              f(0);

              cout << endl << "生成された順列は" << cn << "個です。" << endl;//←2026年3月19日訂正

              while (!_kbhit());//待機させるための命令

              return(0);

}

void f(int s) {

              int h;

              for (int i = 0; i < n; i++) {

                            if (s == 0)a[s] = i + 1;

                            h = 1;

                            if (s > 0) {

                                          for (int j = 0; j < s; j++) {

                                                        if (a[j] == i + 1) {

                                                                      h = 0;

                                                                      break;

                                                        }

                                          }

                                          if (h == 1)a[s] = i + 1;

                            }

                            if (h == 1) {

                                          if (s + 1 < n) {

                                                        f(s + 1);

                                          }

                                          else {

                                                        for (int j = 0; j < n; j++)cout << a[j];

                                                        cout << " ";

                                                        cn++;

                                                        if (cn % 10 == 0)cout << endl;

                                          }

                            }

              }

}



             重複検査を初めてクリアして順列の1番目が生成されます。

             そして、

                            if (h == 1) {

                                          if (s + 1 < n) {

                                                        f(s + 1);

                                          }

                                          else {

                                                        for (int j = 0; j < n; j++)cout << a[j];

                                                        cout << " ";

                                                        cn++;

                                                        if (cn % 10 == 0)cout << endl;

                                          }

                            }
       
             の明るい紫部分によってコンソール画面にと打ち出されます。
 
             さて、f(2)のfor文はi = 0, i = 1,i = 2と最後まで展開が終わりました。

             するとどうなるのでしょうか。

void f(int s) {

              int h;

              for (int i = 0; i < n; i++) {

                           ・
                           ・
                           ・

              }

}


第8話では修正しましませんでしたが、


今回からは修正画像で臨みます。

f(1)の世界の

             int h;

              for (int i = 0; i < n; i++) {

                            if (s == 0)a[s] = i + 1;

       3度目のfor文
によって i = 2 となり

                   a[1] = 3
       となり、

                    if (s > 0) {

                                          for (int j = 0; j < s; j++) {

                                                        if (a[j] == i + 1) {

                                                                      h = 0;

                                                                      break;

                                                        }

                                          }

                                          if (h == 1)a[s] = i + 1;

                            }

        重複検査もクリアして、

                            if (h == 1) {

                                          if (s + 1 < n) {

                                                        f(s + 1);

                                          }

                                          else {

                                                        for (int j = 0; j < n; j++)cout << a[j];

                                                        cout << " ";

                                                        cn++;

                                                        if (cn % 10 == 0)cout << endl;

                                          }

                            }
       
             の明るい紫部分が実行され、

             f(2)が再生成されます。

             今まで新しい空間(新しい部屋)がどこに発生するのか説明しましせんので、

             図のように部屋f(1)の右隣に新しい空間 = 異世界f(2)としてイメージされても結構ですが、

             私は次のようにイメージしています。

             それは入れ子式人形(マトリョーシカ人形)です。

             一番外側にf(0)人形があり、その内側に入れ子式人形f(1)があり、

             2番目の人形f(1)の更に内側に入れ子式人形f(2)があると。

             入れ子式の人形は比喩にすぎなくて、

             再帰の世界では内側に人形が生成したり消滅したりを繰り返します。

             現実の入れ子式人形では内側に人形が生成されたり、消滅したりすることはありません。

             ですから、所詮比喩にすぎないのですが、再帰の本質をイメージできるものとして、

             2010年に『世界で一番わかりやすいVC++ 入門』講義を始めて以来、

             この比喩を使い続けてきました。

             そもそも再帰とはどういうみですか。

             再び戻るです。

             内側に人形を生成して仕事をした後消滅して、

             再度戻って来るので再帰です。

             f(0)がf(1)を呼び出した時に、f(0)の内側にf(1)が生成されます。

             そして、条件を満たせば(重複検査をクリアすれば)f(1)は内側にf(2)を生成させます。

             つまり、再帰の世界は重層構造をなして存在しているのです。

             10次順列の世界であれば10層の重層構造です。

             f(2)の世界が消滅してf(1)の再帰します。

             さらに、トレースの進行によってf(1)が消滅してf(0)に再帰します。

             再帰 = 再び戻るですが、関数の再帰的使用にはもう一つの側面があることに注意しなければなりません。

             それは一番最初こそは

int main() {

              f(0);

             main()がfを呼び出していますが、

             それ以外はfがfを呼ぶという構造をしています。

             自分が自分を呼び出しています。

             再帰には自分が自分を呼び出すという意味合いがあると私は解釈しています。

             何度も申し上げてきました。

             理解とは、自分の言葉で理解することです。

             理解は自分なりの理解でよいのです。

             ただし、その解釈は間違っている可能性もあるので、

             時には一番最初から読み直したり、

             必要な範囲で読みなおしたりして、

             自分の解釈が正しいか常に検討しなければなりません。

             反復は正しい解釈を生み出すための条件になるばかりか、

             エビングハウスの忘却曲線の研究の通り、

             記憶を短期記憶から生涯記憶に変えるための絶対条件でした。

             しかも、その反復は
忘れる前の反復でした。

             解釈は自由です。

             ただし、間違っている可能性があるので繰り返して読むのです。

             ときには、修正されながらより強い解釈へと成長していきます。

             解釈が正しいか、正しくないかの基準はコンピュータがこちらが意図した通りに動くです。


                  

                   

                   







     






第9章第9話へ 第9章第11話へ

本講義トップへ