プログラミングのダイナミックへの導入を理解しやすいこのノート、パリティを見て適切な方法クラウドノートを
01古典的なナップザック問題
軸受パッケージのアイテムの合計重量を超えていないn個のアイテムから袋に複数の物品を選択するように尋ねられたとき、これらのパッケージおよびN物品の一つ、Vのパッケージベアリングは、各アイテムは、自重及び値を有しますときVを得ることができる最大値はどのくらいですか?【各項目のみ最大を取ることができ、その理由は、01バックパック、0手段なしテイクと呼ばれる、1を取る表し、複数回服用することができないため]
入力:ベアリング記事、シーケンス値p、パッケージワットVの重量シーケンス
再帰
第一の問題は、再帰を用いて解決することができます。
)前回の記事のために、我々は、選択されているか判断、1の二種類から選択されていないから選択されるPの値[N] + M(N-1、V = VW [N])、すなわち、現在の値に加え、現在の記事の記事の正味第2条の前のn-1の重量最適解は)から選択されていない後:V前記事、最適解、M(N-1、V)に、N-1の全てに割り当てられます。
だから、再帰式:
M(N、V)= MAX(M(N-1、VW [N])+ P [N]、M(N-1、V))
境界:
VのW [0]、さもなければP [0]が0を返す場合n = 0の場合、保持することができます
N <0、又はV <0、0を返します
現在の要素がフィットVであっても、Vは、n-1の前の記事に直接割り当て
この再帰式上記によると、我々は完全な再帰的なコードを書くことができます。
/**
*
* @param w
* 重量表
* @param p
* 价值表
* @param volumn
* 背包的最大承重
* @return 不超过最大承重的情况下所能装载物品的最大价值总和
*/
public static int recursion(int[] w, int[] p, int volumn) {
return recursion(w, p, w.length - 1, volumn);
}
private static int recursion(int[] w, int[] p, int index, int volumn) {
if (index < 0 || volumn <= 0) {
return 0;
}
if (index == 0 && volumn >= w[0]) {
return p[0];
}
if (index == 0 && volumn < w[0]) {
return 0;
}
if (volumn < w[index]) {
return recursion(w, p, index - 1, volumn);
} else {
return Math.max(recursion(w, p, index - 1, volumn), // 不选
recursion(w, p, index - 1, volumn - w[index]) + p[index]); // 选
}
}
}
しかし、私のコンピュータの実行、実行時間でこのコードは100個の要素でありますrecursion持续时间:46844毫秒
int[] w = Util.getRandomArr(100, 1, 50);
int[] p = Util.getRandomArr(100, 1, 30);
int total = 100;
Instant now = Instant.now();
System.out.println(recursion(w, p, total));
System.out.println("recursion持续时间:" + (Instant.now().toEpochMilli() - now.toEpochMilli()) + "毫秒");
非効率的な、我々はダブルカウントされているので。
我々は重量が副問題に対応するステータスが割り当てられ、Y XY項目をxは定義する前に、問題は、複数のサブ解決再帰的プロセスであろう。
我々は、2 ^ n個のサブ問題(繰り返してもよい)の合計が、2 ^ n回を解決する必要があります。
次に、私たちは、それぞれの状態が直接アクセスの値は、二重カウントを省略することができる次の時間を使用するために、それらを保存するためのソリューションを計算する場合、私たちは自然にそれを思うかもしれ改善する方法を検討します。
ここでは、すなわち、それは二次を完了するために、2次元配列の時間と複雑さをn²することができ、X * Y N * V状態と考えることができ、指数関数よりも効率ははるかに高いです。
メモリの再帰
この考え方によると、私たちは作り、上記のコードの改善を置くことができ、メモリ再帰動的なプログラミングプログラムを:
public static int recursion_m(int[] w, int[] p, int volumn) {
int[][] state = new int[w.length][volumn + 1];
return recursion_m(w, p, w.length - 1, volumn, state);
}
private static int recursion_m(int[] w, int[] p, int index, int volumn, int[][] state) {
if (index < 0 || volumn <= 0) {
return 0;
}
if (index == 0 && volumn >= w[0]) {
return p[0];
}
if (index == 0 && volumn < w[0]) {
return 0;
}
if (volumn < w[index]) {
// 此状态无值
if (state[index - 1][volumn] == 0) {
state[index - 1][volumn] = recursion_m(w, p, index - 1, volumn, state);
}
return state[index - 1][volumn];
} else {
// 此状态无值
if (state[index - 1][volumn] == 0) {
state[index - 1][volumn] = recursion_m(w, p, index - 1, volumn, state);
}
// 此状态无值
if (state[index - 1][volumn - w[index]] == 0) {
state[index - 1][volumn - w[index]] = recursion_m(w, p, index - 1, volumn - w[index], state);
}
return Math.max(state[index - 1][volumn], // 不选
state[index - 1][volumn - w[index]] + p[index]); // 选
}
}
パフォーマンスのアップグレードについて多くのことを書き、耐荷重試験の結果+100 50ランダムアイテム:
263
recursion持续时间:239毫秒
263
recursion_m持续时间:1毫秒
再発
再帰的な文言は、参考のために、もあります。
/**
* 递推
*/
private static int dp(int[] w, int[] p, int volumn) {
int length = w.length;
int[][] state = new int[length][volumn + 1]; // state[i][j] = max(i,j)
// 先填二维表的最下一行
for (int i = 1; i <= volumn; i++) {
state[length - 1][i] = i >= w[length - 1] ? p[length - 1] : 0;
}
for (int i = length - 2; i > -1; i--) {
for (int j = 1; j <= volumn; j++) {
if (j < w[i]) {
state[i][j] = state[i + 1][j];
} else {
state[i][j] = Math.max(p[i] + state[i + 1][j - w[i]], state[i + 1][j]);
}
}
}
for (int i = 0; i < state.length; i++) {
for (int j = 1; j < state[i].length; j++) {
System.out.print(state[i][j] + "\t|");
}
System.out.println();
}
return state[0][volumn];
}
再帰逆のプロセスが再帰的である、再帰は、繰り返しの使用が使用されていません。
私は、を参照してください、理解していない//ます。http://blog.csdn.net/mu399/article/details/7722810 HTTP
要約:
変換の一般的な規則に移動させるための再帰的方法
Xはn次元アレイを定義するために、再帰関数のパラメータ(X元素状態)を有する、配列インデックスが範囲の再帰関数のパラメータ(状態)で、配列要素の値は、再帰関数の戻り値であるように、あなたは徐々に配列を埋め、境界値から開始することができますので、小規模なソリューションの問題は、最大保存されている、大規模な問題は、状態の同じ問題の子に遭遇し、できるだけで解空間のクエリ状態ではなく、再帰的にするとき。
ダイナミックな規制を解決するための一般的な考え方
サブ問題に元の問題
元の問題は、いくつかのサブ質問、同一または類似の問題とサブ問題原形が、小規模に分割されています。サブの問題は、解決された元の問題を解決しています。
一度取得した問題の解決には、これだけ、それぞれの子のために一度問題を解決するために、保存されます。- 状態を判断し
場合動的プログラミング問題解決、我々は「状態」と呼ばれる各変数のサブ値のセットに関連する問題に傾向容量アイテムを割り当てる前に、Yナップザック問題であるXと、状態変数は、要素と呼ばれることもありますX、Yは、一緒になって状態を形成します。1つ以上のサブ問題の「状態」に対応する、いわゆる「状態」の「値」は、「状態」に対応の問題に対する準最適解です。
すべての「状態」のセット、の問題「状態空間。」直接動的プログラミングの問題を解決するために、時間の複雑さに関連した「状態空間」のサイズ。バックパック01の質問、N項目の合計、V容量なので、Nの合計は、* Vの状態がある問題の状態空間の内部。
問題全体の時間の複雑さは、状態の数は、それぞれの状態のために必要な時間を乗じて算出されています。
初期状態(境界)の値の数を決定します
物品は明らかであり、V P [0]戻り値を運ぶことができ(V、0)Mの最初の値として決定される場合、キャリアは0を返すことができません。
状態遷移方程式を決定します
すなわち、どのように1から「値」以上の既知の「ステータス」に - 異なる状態間を移行する方法を見つけることが必要である、「状態」であるかを定義し、この後の「状態」と「値」別の「状態」を取得する「の値。」状態遷移が漸化式で表すことができ、この漸化式は、とも呼ばれてもよい「状態遷移方程式」
if (volumn < w[index]) {
state[index][volumn]= state[index - 1][volumn];
} else {
state[index][volumn]= Math.max(state[index - 1][volumn], // 不选
state[index - 1][volumn - w[index]] + p[index]); // 选
}
規制は、動特性の問題を解決することができます
1)準最適な構造特性を発行します。問題のサブ最適解の問題の解決策も最高に含まれている場合、我々は問題は構造的な次善を持っていると言います。
2)重複したサブ問題:第二の特性を持つべき最適化問題を解くための動的計画法は、スペースのサブ問題は十分に「小さい」でなければならないで、あること、問題を解決するための再帰的なアルゴリズムは、同じサブ問題を繰り返し、常にではないであろう新しいサブ問題を発生させます。一般的に、同様に常に問題の異なるサブスケール入力多項式の総数。繰り返し再帰的アルゴリズムが同じ問題の子供を解決するならば、我々はサブ問題の重複の最適化問題の本質を言います。対照的に、問題を解決するための分割統治法は、通常、再帰の各段階において新たなサブ質問を生成します。
動的プログラミングアルゴリズムは一般的に、それぞれの子のために一度問題を解決するために、問題のサブ本質を重ねる利用子供は、直接ルックアップテーブルを再び定数を見つけるたびに、この問題を必要とするとき、溶液は、テーブルに格納されています。
3)無後効果。現在の値は状態の数を決定されると、その後、その後にのみ、これらの値の進化とに関連する状態、そしてどのような手段がいくつかの州の現在の進化にどのパスの前または後に撮影されている数で、重要ではありません。
ナップザック問題の完全なコード
package org.lanqiao.algo.dynamic_programming;
import java.time.Instant;
import org.lanqiao.algo.util.Util;
/**
* 背包01问题
*
* @author zhengwei
*/
public class Bag01 {
/**
*
* @param w
* 重量表
* @param p
* 价值表
* @param volumn
* 背包的最大承重
* @return 不超过最大承重的情况下所能装载物品的最大价值总和
*/
public static int recursion(int[] w, int[] p, int volumn) {
return recursion(w, p, w.length - 1, volumn);
}
private static int recursion(int[] w, int[] p, int index, int volumn) {
if (index < 0 || volumn <= 0) {
return 0;
}
if (index == 0 && volumn >= w[0]) {
return p[0];
}
if (index == 0 && volumn < w[0]) {
return 0;
}
if (volumn < w[index]) {
return recursion(w, p, index - 1, volumn);
} else {
return Math.max(recursion(w, p, index - 1, volumn), // 不选
recursion(w, p, index - 1, volumn - w[index]) + p[index]); // 选
}
}
public static int recursion_m(int[] w, int[] p, int volumn) {
int[][] state = new int[w.length][volumn + 1];
return recursion_m(w, p, w.length - 1, volumn, state);
}
private static int recursion_m(int[] w, int[] p, int index, int volumn, int[][] state) {
if (index < 0 || volumn <= 0) {
return 0;
}
if (index == 0 && volumn >= w[0]) {
return p[0];
}
if (index == 0 && volumn < w[0]) {
return 0;
}
if (volumn < w[index]) {
// 此状态无值
if (state[index - 1][volumn] == 0) {
state[index - 1][volumn] = recursion_m(w, p, index - 1, volumn, state);
}
return state[index - 1][volumn];
} else {
// 此状态无值
if (state[index - 1][volumn] == 0) {
state[index - 1][volumn] = recursion_m(w, p, index - 1, volumn, state);
}
// 此状态无值
if (state[index - 1][volumn - w[index]] == 0) {
state[index - 1][volumn - w[index]] = recursion_m(w, p, index - 1, volumn - w[index], state);
}
return Math.max(state[index - 1][volumn], // 不选
state[index - 1][volumn - w[index]] + p[index]); // 选
}
}
/**
* 递推
*/
private static int dp(int[] w, int[] p, int volumn) {
int length = w.length;
int[][] state = new int[length][volumn + 1]; // state[i][j] = max(i,j)
// 给定容量j,选择范围为第i个到最后一个时的最大价值
for (int i = 1; i <= volumn; i++) {
state[length - 1][i] = i >= w[length - 1] ? p[length - 1] : 0;
}
for (int i = length - 2; i > -1; i--) {
for (int j = 1; j <= volumn; j++) {
if (j < w[i]) {
state[i][j] = state[i + 1][j];
} else {
state[i][j] = Math.max(p[i] + state[i + 1][j - w[i]], state[i + 1][j]);
}
}
}
for (int i = 0; i < state.length; i++) {
for (int j = 1; j < state[i].length; j++) {
System.out.print(state[i][j] + "\t|");
}
System.out.println();
}
return state[0][volumn];
}
public static void main(String[] args) {
int[] w = Util.getRandomArr(50, 1, 50);
int[] p = Util.getRandomArr(50, 1, 30);
int total = 100;
Instant now = Instant.now();
System.out.println(recursion(w, p, total));
System.out.println("recursion持续时间:" + (Instant.now().toEpochMilli() -
now.toEpochMilli()) + "毫秒");
now = Instant.now();
System.out.println(recursion_m(w, p, total));
System.out.println("recursion_m持续时间:" + (Instant.now().toEpochMilli() - now.toEpochMilli()) + "毫秒");
now = Instant.now();
System.out.println(dp(w, p, total));
System.out.println("dp持续时间:" + (Instant.now().toEpochMilli() - now.toEpochMilli()) + "毫秒");
}
}
アイテムのN列は、フィボナッチを考えると、規制の問題を統合するために移動します
package org.lanqiao.algo.recursion;
import java.time.Duration;
import java.time.Instant;
public class Fibonacci2 {
/**
* 传统做法,直接递归,会重复计算很多项,n=50时运行时间为一分钟左右
*
* @param n
* @return
*/
public static long m1(int n) {
if (n == 0)
return 0;
if (n == 1)
return 1;
if (n == 2)
return 2;
return m1(n - 1) + m1(n - 2);
}
/**
* 记忆递归型
* @param n
* @return
*/
public static long mm1(int n) {
long tmp[] = new long[n + 1];
tmp[0] = 0;
tmp[1] = 1;
tmp[2] = 2;
return mm1(tmp, n);
}
private static long mm1(long[] tmp, int n) {
if (tmp[n] != 0 || n == 0)
return tmp[n];
tmp[n] = mm1(tmp, n - 2) + mm1(tmp, n - 1);
return tmp[n];
}
/**
* 递推型
*
* @param n
* @return
*/
public static long m2(int n) {
if(n==1)
return 1;
if(n==2)
return 2;
long pre = 1;
long next = 2;
long now =-1;
for (int i = 3; i <= n; i++) {
now = pre+next;
pre = next;
next = now;
}
return now;
}
public static void main(String[] args) {
Instant now = Instant.now();
// System.out.println(m1(50));
System.out.println("m1持续时间为:" + Duration.ofMillis(Instant.now().toEpochMilli() - now.toEpochMilli()).getSeconds());
now = Instant.now();
System.out.println(mm1(50));
System.out.println("mm1持续时间为:" + Duration.ofMillis(Instant.now().toEpochMilli() - now.toEpochMilli()).getSeconds());
now = Instant.now();
System.out.println(m2(50));
System.out.println("m2持续时间为:" + Duration.ofMillis(Instant.now().toEpochMilli() - now.toEpochMilli()).getSeconds());
}
}
フィボナッチ数の問題は、まず間違いなく再帰を使用しましたが、それは何の優れたが、何の優れた質問が、実際には存在しない、最適な下部構造と一致して、母親の問題の解決策である問題の解決策をマージし、問題のある子のオーバーラップ自然とのラインでは、再帰理由我々は、状態空間として一次元アレイを用いて溶液がそれだけにN-用語N自体ので、一次元アレイに依存するためのプロセスは、それぞれを解決するために繰り返される、完全に無アフターエフェクト問題。