【アルゴリズム】 動的計画法 ~ フィボナッチ数列 を例にして~

■ はじめに

http://blogs.yahoo.co.jp/dk521123/34536714.html
再帰を使ったフィボナッチ数列の処理は、同じ処理を何度か呼び出すといった無駄な部分がある

例:再帰処理で、5番目のフィボナッチ数を計算した場合

calculateFibonacci(5) => 3 + 2
 |
 +-> calculateFibonacci(4) => 2 + 1
 |    |
 |    +-> calculateFibonacci(3) => 1 + 1(★1)
 |    |    |
 |    |    +-> calculateFibonacci(2) => 1 + 0(★)
 |    |    |    |
 |    |    |    +-> calculateFibonacci(1) => 1(★)
 |    |    |    +-> calculateFibonacci(0) => 0(★)
 |    |    +-> calculateFibonacci(1) => 1(★)
 |    +-> calculateFibonacci(2) => 1 + 0(★)

 |
 +-> calculateFibonacci(3) => 1 + 1(★2)
      |
      +-> calculateFibonacci(2) => 1 + 0(★)
      |    |
      |    +-> calculateFibonacci(1) => 1(★)
      |    +-> calculateFibonacci(0) => 0 (★)
      +-> calculateFibonacci(1) => 1(★)

 * 注目すべき、calculateFibonacci(3)~calculateFibonacci(0)は同じ処理を行っている。
  1回処理すれば、後は使いまわせばいい。
   例えば、calculateFibonacci(3)の計算を(★1)で行って結果を保持していたら、
 (★2)で計算するときにそのままその計算結果を使えば、以降の呼び出しはしなくてすむ。

動的計画法 (Dynamic Programming) とは

http://blogs.yahoo.co.jp/dk521123/36662096.html
より抜粋

 * 問題を小さい問題に分割し、部分問題の解を利用して、問題全体の解を得る手法
 * 途中の計算結果を記憶しておき、うまく再利用することで計算効率を上げる手法
 => 一度行った処理はメモリ上に記憶しておき、再度同じ処理が呼ばれたらそのメモリ上の結果を利用する

■ 目的

 * 無駄な処理を避ける

■ サンプル

 * 動的計画法再帰 を組み合わせて、フィナボッチ数列を実装してみる

フィナボッチ数列(Fibonacci Number)

 import java.util.ArrayList;
import java.util.List;

public class FibonacciEx {
   /** まだ計算していない事を示す値 */
   private static final int NOT_RESULT_YET = -1;
   private int valueOfFibonacci;
   /** 結果をため込んで置くリスト */
   private List<Integer> resultsOfFibonacci;

   public FibonacciEx(int value) {
      this.valueOfFibonacci = value;
      this.resultsOfFibonacci = new ArrayList<>(value);
      for (int i = 0; i <= value; i++) {
         // 値を予め初期化しておく
         this.resultsOfFibonacci.add(NOT_RESULT_YET);
      }
   }

   public static void main(String[] args) {
      FibonacciEx fibonacci = new FibonacciEx(7);
      System.out.println("Result : " + fibonacci.calculate());
   }

   private int calculate() {
      return this.calculate(this.valueOfFibonacci);
   }

   private int calculate(int value) {
      int result = this.resultsOfFibonacci.get(value);
      if (result == NOT_RESULT_YET) {
         // ここはもう再帰処理で行った事と同じ
         if (value == 0) {
            result = 0;
         } else if (value == 1) {
            result = 1;
         } else {
            result = this.calculate(value - 1) + this.calculate(value - 2);
         }
         // 結果を保持しておく
         this.resultsOfFibonacci.set(value, result);
      }
      return result;
   }
}

■ おまけ

http://blogs.yahoo.co.jp/dk521123/34536714.html
でも書いたが、上記のサンプルで「10000」を指定すると、スタックオーバーフローのエラーで落ちる。
その対策版を以下に記載。(再帰動的計画法も使ってないが)

Exception in thread "main" java.lang.StackOverflowError
	at com.sample.recursive.FibonacciEx.calculate(FibonacciEx.java:32)
	at com.sample.recursive.FibonacciEx.calculate(FibonacciEx.java:40)
	at com.sample.recursive.FibonacciEx.calculate(FibonacciEx.java:40)
        ...

サンプル

import java.math.BigDecimal;

public class FibonacciEx2 {
   public static void main(String[] args) {
      BigDecimal value = new BigDecimal(10000);
      System.out.println("Result : " + calculateFibonacci(value).toString());
  }
   static BigDecimal calculateFibonacci(BigDecimal value) {
      if (value.compareTo(new BigDecimal(0)) == 0) {
         return new BigDecimal(0);
      } else if (value.compareTo(new BigDecimal(0)) == 0
            || value.compareTo(new BigDecimal(0)) == 0) {
         return new BigDecimal(0);
      }
      BigDecimal f, fn1, fn2;
      f = new BigDecimal(1);
      fn1 = new BigDecimal(1);
      for (long i = 3L; i <= value.longValue(); i++) {
         fn2 = fn1;
         fn1 = f;
         f = fn1.add(fn2);
      }
      return f;
   }
}

補足:BigDecimal について

 * 非常に大きい数になるので、int -> BigDecimal にした。
  BigDecimal については、以下の関連記事を参照のこと。
http://blogs.yahoo.co.jp/dk521123/34102519.html

■ 関連事項

編集距離/レーベンシュタイン距離(Levenshtein distance)

編集距離を計算するためには、動的計画法を用いる
 * 二つの文字列がどの程度異なっているか(逆に言うと、類似度)を示す尺度
 * 詳細は、以下の関連記事を参照のこと
http://blogs.yahoo.co.jp/dk521123/36655532.html

関連記事

再帰処理 ~ 階乗 と フィボナッチ数列 を例にして~

http://blogs.yahoo.co.jp/dk521123/34536714.html

ナップサック問題(knapsack problem) [1] ~ 貪欲法(greedy algorithm) ~

http://blogs.yahoo.co.jp/dk521123/35392669.html

Java】 文字列の類似度・レーベンシュタイン距離/ジャロ・ウィンクラー距離 ~ Apache Lucene

http://blogs.yahoo.co.jp/dk521123/36655532.html