技術とか戦略とか

SIerで証券レガシーシステムを8年いじってからSESに転職した普通の文系SEによる技術ブログ。

動的計画法を試してみた

動的計画法とは、再帰的なロジックを、計算結果を都度記録するロジックで代替することで、計算速度を向上させるテクニックです。
競技プログラミングのテクニックの一つなのですが、高度なアルゴリズムを実装する開発だけではなく普通の業務システム開発にも応用できそうなので、紹介します。
 
今回は、フィボナッチ数列の計算を例に挙げます。
再帰的に処理した場合、数列が1つ増える度に呼び出し量が2倍になるため、オーダはO^2となります。また、再帰的に関数を呼び出すとその分メモリ(スタック)を消費するため、異常終了するリスクも高くなります。
しかし、動的計画法を用いた場合は、数列が1つ増えても計算が1回増えるだけなので、オーダはOとなります。
実際に45項目を計算した結果、再帰呼び出しでは約11000ミリ秒を要しましたが、動的計画法の場合は1ミリ秒未満でした。
 
【スペック・動作環境】
・OS:Windows8.1 64bit
・CPU:Inter(R) Core(TM) i5-4210U CPU @ 1.70GHz 2.40GHz
・メモリ:8.00GB
・ディスク:SSD 128GB
・言語:java8
IDEEclipse Oxygen
 
【確認用プログラム】
public class FibMain {

    public static void main(String[ ] args) {

        // 再帰呼び出しの場合
        System.out.println("■再帰呼び出し");
        long startTime1 = System.currentTimeMillis();
        int result1 = fib1(45);
        long endTime1 = System.currentTimeMillis();
        System.out.println(" 結果    :" + result1);
        System.out.println(" 要したミリ秒:" + (endTime1 - startTime1));

        // 動的計画法の場合
        System.out.println("■動的計画法");
        long startTime2 = System.currentTimeMillis();
        int result2 = fib2(45);
        long endTime2 = System.currentTimeMillis();
        System.out.println(" 結果    :" + result2);
        System.out.println(" 要したミリ秒:" + (endTime2 - startTime2));
    }

    // 再帰呼び出し
    static int fib1(int n) {
        switch (n) {
            case 1: return 0;
            case 2: return 1;
            default: return fib1(n - 1) + fib1(n - 2);
        }
    }

    // 動的計画法
    static int fib2(int n) {
        int memo[ ] = new int[1000];
        memo[0] = 0;
        memo[1] = 1;
        for (int i = 2; i < n; i++) {
            memo[i] = memo[i - 1] + memo[i - 2];
        }
        return memo[n - 1];
    }

}
 
【実行結果】
再帰呼び出し
 結果    :701408733
 要したミリ秒:11033
動的計画法
 結果    :701408733
 要したミリ秒:0