c++を使って、部分和問題を深さ優先探索で書いています。
その際の再帰関数が分かりにくいのでご教授ください。
例は蟻本のp33です
bool dfs(int i ,int sum){
if(i==n) return sum == k;
// n個決め終わったら、sumがkと等しいかを返す
★sumが等しいかを返すならtrue or falseではないのですか?
★returnで何が返ってくるのかわかりません
if(dfs(i+1,sum)) return true;
★dfs(i,sum)はtrue なんですか?falseなんですか?
★何をもってdfs(i+1,sum)の値が決まるのかがこの式から読めません。
if(dfs(i+1),sum+a[i])) return true;
return false;
}
void solve(){
if(dfs(0,0)) printf("Yes\m");
★なぜ0,0なんですか?
}
素人質問ですみません。いろいろ調べまわりましたが、部分和のDFSについて詳しい説明がありませんでした。
何かわかりやすい解説があれば教えてください。よろしくお願いします。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答2件
0
ベストアンサー
★sumが等しいかを返すならtrue or falseではないのですか?
★returnで何が返ってくるのかわかりません
まず sum == k
が「式」であることを理解しましょう。
式というのはプログラムにおいては評価され置換されるもののことを指します。以下が例です。(iは宣言済みとします。)
cpp
11 + 1 22 * 2 3i 4i++ 5++i
i
に1が入っていれば、上から順に 2
、 4
、1
、1
、 2
の値に置き換わります。
sum == k
も同様に式です。この式は sum
と k
の値が同じであれば true
、 違う場合は false
に置き換わります。
if文の引数として渡される式に対しても、この「評価」が行われています。変数がくると変数も評価され値が置き換わります。よって以下のような書き方が可能です。
cpp
1#include <iostream> 2 3int main() { 4 5 bool hoge = 1 == 2; // hogeにfalseが代入される 6 7 if (hoge) { // hogeの評価値はfalseなのでelse以降を実行 8 printf("同じ\n"); 9 } else { 10 printf("違う\n"); 11 } 12 13 return 0; 14}
今回の場合、return文によって k == sum
の評価された値が、dfs関数の返り値となります。したがって、 true
または false
の値が返されます。
★dfs(i,sum)はtrue なんですか?falseなんですか?
★何をもってdfs(i+1,sum)の値が決まるのかがこの式から読めません。
まず動くソースコードを。コメント、つけ直してみました(大して変わってないかも)
○1, ○2, ○3, ○4はソースコード以下の説明と対応しています。
wandbox: https://wandbox.org/permlink/AMrTofmWNM2RKDpA
cpp
1#include <iostream> 2 3const int MAX_N = 20; 4int *a; 5int n, k; 6 7bool dfs(int i, int sum) { 8 /* 9 この再帰関数の深さがn(今回は4)であれば、最終的に合計値(sum)が目的の値(k...今回なら13) 10 になっているかを返して終わり。 11 */ 12 if (i == n) return sum == k; // ○1 13 14 /* 15 まだ深さが4ではないときに以下は実行される。 16 もしa[i]を合計値に入れずに目的の値になったならば、真を返す。 17 */ 18 if (dfs(i + 1, sum)) return true; // ○2 19 20 /* 21 もしa[i]を合計値に入れて目的の値になったならば、真を返す。 22 */ 23 if (dfs(i + 1, sum + a[i])) return true; // ○3 24 25 /* 26 a[i]を入れようが入れまいが、もう合計値にすることは不可能であるからfalseを返す。 27 */ 28 return false; // ○4 29} 30 31void solve() { 32 if (dfs(0, 0)) printf("Yes\n"); 33 else printf("No\n"); 34} 35 36int main(void) { 37 n = 4; 38 int b[MAX_N] = {1, 2, 4, 7}; 39 a = b; 40 k = 13; 41 42 solve(); // Yes 43}
i
の役割は配列の何番目の話をしているかで、 sum
の役割は再帰関数がうまく働くようにするための蓄積値といったところです。
この関数は次のように動きます。( https://wandbox.org/permlink/VikAMasmuqES69RS で確認できます。以下の説明文めちゃくちゃ長いですが、実際に動きを追ったほうがわかりやすいので書いてみました。)
dfs(0, 0)
が呼ばれる。
↓
○1 i ≠ nであるから、まだ値は返さない。
○2 dfs(1, 0)
が呼ばれる。
↓
○1 i ≠ nであるから、まだ値は返さない。
○2 dfs(2, 0)
が呼ばれる。
↓
○1 i ≠ nであるから、まだ値は返さない。
○2 dfs(3, 0)
が呼ばれる。
↓
○1 i ≠ nであるから、まだ値は返さない。
○2 dfs(4, 0)
が呼ばれる。
↓
○1 i = nであるから、sum == k
の式の評価値を返す。 0 == 13
は偽なので、 dfs(4, 0)
は false
を返す。
↓
○3 dfs(3, 0)
の中に戻る。○2 が実行されなかったので、次に sum(=0)
に a[3](=7)
が足された
dfs(4, 7)
が呼ばれる。
↓
○1 i = nであるから、sum == k
の式の評価値を返す。 7 == 13
は偽なので、 dfs(4, 7)
は false
を返す。
↓
○4 dfs(3, 0)
の中に戻る。 dfs(3, 0)
は false
を返す。
↓
○3 dfs(2, 0)
の中に戻る。○2 が実行されなかったので、次に sum(=0)
に a[2](=4)
が足された
dfs(3, 4)
が呼ばれる。
↓
○1 i ≠ nであるから、まだ値は返さない。
○2 dfs(4, 4)
が呼ばれる。
↓
○1 i = nであるから、sum == k
の式の評価値を返す。 4 == 13
は偽なので、 dfs(4, 4)
は false
を返す。
↓
○3 dfs(3, 4)
の中に戻る。○2 が実行されなかったので、次に sum(=4)
に a[3](=7)
が足された
dfs(4, 11)
が呼ばれる。
↓
○1 i = nであるから、sum == k
の式の評価値を返す。 11 == 13
は偽なので、 dfs(4, 11)
は false
を返す。
↓
○4 dfs(3, 4)
の中に戻る。 dfs(3, 4)
は false
を返す。
↓
○4 dfs(2, 0)
の中に戻る。 dfs(2, 0)
は false
を返す。
↓
○3 dfs(1, 0)
の中に戻る。○2 が実行されなかったので、次に sum(=0)
に a[1](=2)
が足された
dfs(2, 2)
が呼ばれる。
↓
○1 i ≠ nであるから、まだ値は返さない。
○2 dfs(3, 2)
が呼ばれる。
↓
○1 i ≠ nであるから、まだ値は返さない。
○2 dfs(4, 2)
が呼ばれる。
↓
○1 i = nであるから、sum == k
の式の評価値を返す。 2 == 13
は偽なので、 dfs(4, 2)
は false
を返す。
↓
○3 dfs(3, 2)
の中に戻る。○2 が実行されなかったので、次に sum(=2)
に a[3](=7)
が足された
dfs(4, 9)
が呼ばれる。
↓
○1 i = nであるから、sum == k
の式の評価値を返す。 9 == 13
は偽なので、 dfs(4, 9)
は false
を返す。
↓
○4 dfs(3, 2)
の中に戻る。 dfs(3, 2)
は false
を返す。
↓
○3 dfs(2, 2)
の中に戻る。○2 が実行されなかったので、次に sum(=2)
に a[2](=4)
が足された
dfs(3, 6)
が呼ばれる。
↓
○1 i ≠ nであるから、まだ値は返さない。
○2 dfs(4, 6)
が呼ばれる。
↓
○1 i = nであるから、sum == k
の式の評価値を返す。 6 == 13
は偽なので、 dfs(4, 6)
は false
を返す。
↓
○3 dfs(3, 6)
の中に戻る。○2 が実行されなかったので、次に sum(=6)
に a[3](=7)
が足された
dfs(4, 13)
が呼ばれる。
↓
○1 i = nであるから、sum == k
の式の評価値を返す。 13 == 13
は真なので、 dfs(4, 13)
は true
を返す。
↓
○3のif文以降が実行される。 dfs(3, 6)
は true
を返す。
↓
○3のif文以降が実行される。 dfs(2, 2)
は true
を返す。
↓
○3のif文以降が実行される。 dfs(1, 0)
は true
を返す。
↓
○2のif文以降が実行される。 dfs(0, 0)
は true
を返す。
この説明は蟻本p34の解説絵と同じです。
★なぜ0,0なんですか?
dfs関数の再帰の仕組みを理解すれば自ずとわかるかと思います。
i = 0なのは、配列の0番目から部分和を確認する必要があるためです。これは明白だと思います。(i = 4であればi = 0, 1, 2, 3は無視される...明らかにそれは望んだ操作ではない)
sum = 0なのは、再帰関数がうまく働くようにするには最初の値を0にする必要があるからというのがわかると思います。この変数は、部分和が目的値になっているかを確認するために使われています。
遷移について等、まだよくわからない部分があれば返信ください。m(_ _)m
投稿2020/04/27 18:55
編集2020/04/27 19:34総合スコア2045
0
蟻本は、アマゾンの試し読みで読めました。
if (i == n) return sum == k;
// n個決め終わったら、sumがkと等しいかを返す
★sumが等しいかを返すならtrue or falseではないのですか?
★returnで何が返ってくるのかわかりません
== は、2項演算子で、
第1項と第2項が等しければ、演算結果はtrue。
第1項と第2項が等しくなければ、演算結果は false です。
sum と k が等しければ、true を返し、
sum と k が等しくなければ、false を返します。
if (dfs(0, 0)) printf("Yes\m");
★なぜ0,0なんですか?
bool dfs(int i, int sum) { と定義された関数 dfs は、
a[0]~a[i-1] の部分和が sum に入っているとき、
a[i] を加えない場合と加える場合の 2通りについて、
残りの a[i+1], ... a[n-1] の部分和を sum に加えたものが
k に等しければ true を返し、等しくなければ false を返します。
dfs(0, 0) で dfs を呼び出すと、i = 0, sum = 0 で呼び出すので、
a[0]~a[n-1] の部分和を sum に加えたものが k に等しければ true、
そうでなければ false が返ってきます。
if (dfs(i+1, sum)) return true;
a[i] を加えない場合、残りの a[i+1]~a[n-1] の部分和を sum に
加えたものが k に等しいかどうかが返ってくるので、
true が返ってきたら、探索終了で true で返ります。
if (dfs(i+1, sum + a[i])) return true;
a[i] を加えた場合、残りの a[i+1]~a[n-1] の部分和を sum に
加えたものが k に等しいかどうかが返ってくるので、
true が返ってきたら、探索終了で true で返ります。
a[i] を部分和に加えても加えなくても false だったら、
a[0]~a[i-1] の部分和が間違っていたことになるので
false で戻って、違う組み合わせの部分和に変更してもらいます。
投稿2020/04/27 18:38
総合スコア8224
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/04/27 19:31