トップアルゴリズム > 再帰処理

再帰処理

再帰処理とは

再帰処理(再帰コール)とは、あるメソッド(関数)の中で、自分自身を呼び出すことを言う。 大抵のプログラミング言語で使用できる。

再帰を使うことにより、プログラムがスリムになるが、 その動作は初心者あるいは中級者でも理解しにくいことがある。

また、メソッド(関数)コールのオーバヘッドが生じるため、実行時間がかかったり、 メモリ不足に陥るケースもある。 再帰を使わず、for 文に置き換えた方がよい場合もある。

再帰処理の事例

バイナリサーチ[2]

int binary_search(int ary[], int key, int imin, int imax) {
    if (imax < imin) {
        return KEY_NOT_FOUND;
    } else {
        int imid = imin + (imax - imin) / 2;
        if (ary[imid] > key) {
            return binary_search(ary, key, imin, imid - 1);
        } else if (ary[imid] < key) {
            return binary_search(ary, key, imid + 1, imax);
        } else {
            return imid;
        }
    }
}

階層ディレクトリ下のファイル名(パス名)の一覧表を得る

階層ディレクトリ下のファイル名(パス名)の一覧表を得る再帰処理は比較的分かりやすく、 また、しばしば必要とする処理である。Java言語による例を下に示す。

import java.io.File;

public class ListFiles {

    static void list(File file) {
        if (file.isDirectory()) {
            File[] subfiles = file.listFiles();
            for (File subfile : subfiles) {
                list(subfile);  // 再帰処理
            }
        } else {                // 終了処理:ファイル
             System.out.println(file.toString());
        }           
    }

    public static void main(String[] args) {
        list(new File( "c:/_www"));
    }
}

このプログラムの実行例を下に示す。 ホームページのファイルを c:\_www に置いているとき、 新規作成または更新されたファイルをサーバーに FTP転送するときに使う。 この場合は、ディレクトリやファイルの時刻も調べる。 c:\_www に新しいサブディレクトリができたときは、FTPコマンドを送り、 サーバー側にもその新しいサブディレクトリを作成する。

FFFTPのミラーリングアップロードを使えば済むが、ファイル数が増えると 実行時間がかかる。

c:\_www\.htaccess
c:\_www\algorithm\osm-landparser01.html
c:\_www\algorithm\point_in_polygon01.html
c:\_www\algorithm\recursive_call01.html
c:\_www\algorithm\src\pointf_java.txt
c:\_www\android_java\onview01.html
[後略]

Ramer-Douglas-Peuckerアルゴリズム

Ramer-Douglas-Peuckerアルゴリズム(またはDouglas-Peuckerアルゴリズム)は 下に示すように、再帰を用いて折れ線データを簡略化するものである[1]。

simplySection関数は線分 points[i]-points[j] の間引きを行う関数である。 許容誤差を epsilon とする。

線分 points[i]-points[j]と途中の点 points[k] との垂直距離を調べて、 その最大値を maxDistance とする。

この最大値が epsilon を超えなければ、全ての中間点は間引いて、処理は終わりである。

最大値が許容誤差を超える場合には、 この最大値を持った点 points[maxIndex] は間引かないことに決定する。 この点を境として、ふたつの線分 points[i]-points[maxIndex]および points[maxIndex]-points[j] に分けて、 それぞれについて simplySection関数を再帰コールする。

[C/C++言語]

// Returns the distance from point p to the line between p1 and p2
double perpendicular_distance(point_t p, point_t p1, point_t p2) {
    double dx = p2.x - p1.x;
    double dy = p2.y - p1.y;
    double d = sqrt(dx * dx + dy * dy);
    return fabs(p.x * dy - p.y * dx + p2.x * p1.y - p2.y * p1.x)/d;
}

void simplifySection(point_t *points, size_t i, size_t j, double epsilon) {
    if ((i + 1) == j) return;

    double maxDistance = -1.0;
    size_t maxIndex = i;
    for (size_t k = i + 1; k < j; k++) {
        double distance = perpendicular_distance(points[k], points[i], points[j]);
        if (distance > maxDistance) {
            maxDistance = distance;
            maxIndex = k;
        }
    }
    if (maxDistance <= epsilon) {
        for (size_t k = i + 1; k < j; k++) {
            points[k].unused = true;   // 間引く
        }
    } else {
        simplifySection(points, i, maxIndex, epsilon);
        simplifySection(points, maxIndex, j, epsilon);
    }
}

参考記事

[1] Ramer-Douglas-Peucker line simplification
[2] 二分探索