以前、「分岐しない再帰関数」について解説しましたが、ここでは「分岐する再帰関数」について解説したいと思います。ただ、分岐する再帰関数はサンプルプログラムが巨大になりますので、自作ライブラリ MyCanvas内のソースコードをベースにして、ざっくり説明する形にしたいと思います。
分岐する再帰関数については、以下の 3つに細分化してそれぞれ説明していきます。
再帰関数は理解するのがなかなか難しく、またオブジェクト指向とは無関係なので、今すぐに完全理解する必要はないです。ただ、実際にプログラムを作る際、「稀に」再帰関数を使用するとすごくシンプルにソースコードを記述できるときがあるかもしれません。そういう意味で、いつかは理解しておきたい知識になります。
ずっと以前に紹介した D303.javaと同じソースコードですが、改めて J601.javaとして製作します。
J601/MyCanvas.java
(ライブラリをそのまま利用します)
J601/J601.java
/** * MyCanvasライブラリをテストします。 */ public class J601 { /** * メインメソッド。 * @param args 引数 */ public static void main(String[] args) { MyCanvas.c.drawPoint(5, 2, 'a'); MyCanvas.c.drawLine(1, 1, 10, 10, 'b'); MyCanvas.c.drawLine(-10, 15, 25, 0, 'c'); MyCanvas.c.fill(15, 15, '+'); MyCanvas.c.drawRect(10, 10, 15, 13, 'd'); MyCanvas.c.drawRectFill(1, 15, 8, 18, 'e'); // 画面に表示する MyCanvas.c.print(); } }
実行結果は以下の通り。
.................... .b.................. ..b..a............cc ...b...........ccc++ ....b........ccc++++ .....b.....cc+++++++ ......b..cc+++++++++ ......ccc+++++++++++ ....cc++b+++++++++++ ..cc+++++b++++++++++ cc++++++++dddddd++++ c+++++++++d++++d++++ ++++++++++d++++d++++ ++++++++++dddddd++++ ++++++++++++++++++++ +eeeeeeee+++++++++++ +eeeeeeee+++++++++++ +eeeeeeee+++++++++++ +eeeeeeee+++++++++++ ++++++++++++++++++++
MyCanvasライブラリにはいろいろな機能があります。ソースコードを修正して、いろいろ実験してみてください。
線を描画するメソッド drawLineを見ていきます。MyCanvas.javaの 178行目以降を以下に抜粋します。
/** * 線を描画します。 * @param x1 起点 X座標 * @param y1 起点 Y座標 * @param x2 終点 X座標 * @param y2 終点 Y座標 * @param c 文字 */ public void drawLine(int x1, int y1, int x2, int y2, char c) { this.drawLine((double)x1, (double)y1, (double)x2, (double)y2, c); } /** * 線を描画するための内部メソッドです。再帰関数を使用しています。 * 再帰関数の例を示すために、このような実装としました。アルゴリズムの質は良くないです。 * @param x1 起点 X座標 * @param y1 起点 Y座標 * @param x2 終点 X座標 * @param y2 終点 Y座標 * @param c 文字 */ private void drawLine(double x1, double y1, double x2, double y2, char c) { this.drawPoint((int)x1, (int)y1, c); this.drawPoint((int)x2, (int)y2, c); if(Math.abs(x2 - x1) < 1.0 && Math.abs(y2 - y1) < 1.0) { ; // 何もしない } else { double x3 = (x1 + x2) / 2.0; double y3 = (y1 + y2) / 2.0; // 再帰呼び出し(2分岐) this.drawLine(x1, y1, x3, y3, c); this.drawLine(x3, y3, x2, y2, c); } }
このメソッドは直線を描画します。と言っても、「点をたくさん描画して直線に見せる」というだけです。このアルゴリズムでは、2点の中点を計算し、その左側半分と右側半分のそれぞれについて、再び中点を計算し~、と繰り返すことで、直線を描画します。
今の時点ではソースコードの細かいところまで理解する必要はありません。まだ学習していないことがたくさんありますから。ただ、引用したソースコードの 33行目、34行目が「2分岐する再帰関数」の肝心の部分になる、ということについては理解できるでしょうか。
2分岐する再帰関数のその他の例は以下の通りです。
指定文字で塗りつぶすメソッド fillを見ていきます。MyCanvas.javaの 207行目以降を以下に抜粋します。
/** * 指定文字で塗りつぶします。カンバス外を指定した場合は何もしません。 * @param x X座標 * @param y Y座標 * @param c 文字 */ public void fill(int x, int y, char c) { if(c == this.canvas[y][x]) { ; // 指定座標の文字と描画文字が同じ場合は何もしない } else { try { this.fill(x, y, c, this.canvas[y][x]); } catch(ArrayIndexOutOfBoundsException e) { ; // 何もしない } } } /** * 指定文字で塗りつぶすための内部メソッドです。再帰関数を使用しています。 * @param x X座標 * @param y Y座標 * @param toChar 置換後の文字 * @param fromChar 置換前の文字 */ private void fill(int x, int y, char toChar, char fromChar) { this.drawPoint(x, y, toChar); // 再帰呼び出し(4分岐) if(x > 0 && this.canvas[y][x - 1] == fromChar) { this.fill(x - 1, y, toChar, fromChar); } if(x < this.width - 1 && this.canvas[y][x + 1] == fromChar) { this.fill(x + 1, y, toChar, fromChar); } if(y > 0 && this.canvas[y - 1][x] == fromChar) { this.fill(x, y - 1, toChar, fromChar); } if(y < this.height - 1 && this.canvas[y + 1][x] == fromChar) { this.fill(x, y + 1, toChar, fromChar); } }
このメソッドは指定文字で塗りつぶします。と言っても、「点をひとつずつ描画して塗りつぶす」というだけです。このアルゴリズムでは、ある点の上下左右の4方面に注目し、その 4方面それぞれについて、再び上下左右の 4方面に注目し~、と繰り返すことで、塗りつぶしを実現します。
今の時点ではソースコードの細かいところまで理解する必要はありません。まだ学習していないことがたくさんありますから。ただ、引用したソースコードの 31行目、34行目、37行目、40行目が「4分岐する再帰関数」の肝心の部分になる、ということについては理解できるでしょうか。
4分岐する再帰関数は平面(2次元空間)上で何か処理をする場合によく使用されます。例えば、以下のような場合に使用することができます。
分岐する数が不特定の再帰関数もあります。例えば、以下のような場合に再帰関数を使用することができます。