質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

for

for文は、様々なプログラミング言語で使われている制御構造です。for文に定義している条件から外れるまで、for文内の命令文を繰り返し実行します。

Q&A

解決済

4回答

2020閲覧

C言語でナップザック問題 総当たりで悩んでいます

Senri551853

総合スコア12

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

for

for文は、様々なプログラミング言語で使われている制御構造です。for文に定義している条件から外れるまで、for文内の命令文を繰り返し実行します。

0グッド

2クリップ

投稿2019/07/05 12:19

編集2019/07/08 01:35

前提・実現したいこと

c言語でナップザック問題を解きたい
重さと価値がそれぞれ w_i, v_i であるような n 個の品物があり、重さの総和がWを超えないように選んだときの価値の総和のの最大値を求める
例1)n = 4、(w, v) = { (2, 3), (1, 2), (3, 4), (2, 2) }、W = 5 → 出力 7
例2)n = 3、(w, v) = { (2, 2), (2, 1), (1, 1), }、W = 5 → 出力 4

発生している問題

組み合わせを総当たりするという部分で躓いています

上記の(例1)でいえば(2,3)(3,4)で済んでいますが、
(例2)ならば(2,2), (2,1), (1,1)になります。取る値が入力次第で変わるのでfor文をどのように回せばいいかがわかりません

考えている流れとしては
総和を超えない組み合わせを探す→価値の最大値を求める→出力です
今回は総当たりの部分のヒントをいただきたいです

該当のソースコード

#include <stdlib.h> int main(void) { int n=0;//品物の個数 int w=0;//求める総和 int w_i[100000];//重さ int v_i[100000];//価値 printf("品物の数"); scanf("%d",&n); if(1 <= n && n <= 100){ }else{ printf("ERROR!\n"); exit(0); } printf("重さと価値\n"); for(int i=0; i<n; i++ ){ scanf("%d , %d",&w_i[i],&v_i[i]); if( 1 <= w_i[i],v_i[i] && w_i[i],v_i[i] <= 100){ }else{ printf("ERROR!\n"); exit(0); } } printf("求める総和"); scanf("%d",&w); if(1 <= w && w <= 10000){ }else{ printf("ERROR!\n"); exit(0); } /* 本体部分何も書けてないです */ return 0; }

試したこと

for(int j=0;j<n;j++){ for(int k=1;k<n;k++){ for(int l=2;l<n;l++){ ・・・・ } } }

のようにforで総当たりを考えましたがこれだと取る値の数が限られるので没

補足情報(FW/ツールのバージョンなど)

Visual Studio Codeを使用しています。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

n個のアイテムに対し、持つ、持たないの組合せは全部で何通りありますか?

そう、2^n通りですね。

後はループを回すだけです。

ググるためのキーワードは、ビット操作、インクリメントです。

追記:具体化するとどうなる? とご指摘を頂いたので、あまり本人のためにならないかもですが、コードを書きます。

C

1#include <stdlib.h> 2 3int main(void) 4{ 5 int n;// up to 32 6 int maxweight; 7 int weight[32]; 8 int value[32]; 9 int maxvalue = 0; 10 int i, e, j, w, v; 11 12 printf("品物の数"); 13 scanf("%d", &n); 14 if(n < 1 || 32 < n){ 15 printf("ERROR!\n"); 16 exit(0); 17 } 18 printf("重さと価値\n"); 19 for(i = 0; i < n; i++){ 20 scanf("%d , %d", &weight[i], &value[i]); 21 if( weight[i] < 1 || 100 < weight[i] || value[i] < 1 || 100 < value[i]){ 22 printf("ERROR!\n"); 23 exit(0); 24 } 25 } 26 printf("求める総和"); 27 scanf("%d", &maxweight); 28 if(maxweight < 1 || 10000 < maxweight){ 29 printf("ERROR!\n"); 30 exit(0); 31 } 32 33 e = 1 << n; 34 for(i = 1; i != e;){ 35 w = 0; 36 v = 0; 37 for(j = 0; j < n; j++){ 38 if(i & (1 << (n - 1 - j))){ 39 w += weight[j]; 40 v += value[j]; 41 } 42 } 43 if(w > maxweight){ 44 i += (i & -i); 45 } 46 else{ 47 if(v > maxvalue){ 48 maxvalue = v; 49 } 50 i++; 51 } 52 } 53 54 return 0; 55}

投稿2019/07/05 15:38

編集2019/07/06 22:54
majiponi

総合スコア1722

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Senri551853

2019/07/08 02:08 編集

回答ありがとうございます。キーワードを検索してもパッとしなかったので具体的に書いてくださって助かりました 追記 ビットを1ずらす理由がよくわからないのですがご教授していただけませんか…? 追記追記 2^n 回を実現?しているということであってますか?
majiponi

2019/07/10 00:40

rubato6809さんがほぼ補足してくださいましたが、概ね予想通りです。一言だけ補足すると、例えば「1番と3番」で制限重量を超えていたら、「1番と3番と4番」は調べる必要がありませんよね? そのためにi += (i & -i);という一行があります。
rubato6809

2019/07/10 04:23

Senri551853さん、majiponiさん、評価ありがとうございます。 for ループで ksack++ とすべき所、「++」が抜けてたことに、BAいただいてから気づきました。修正しました。
guest

0

ベストアンサー

majiponiさんの反応が無いまま、BAが付いてないので出しゃばります。

n個のアイテムに対し、持つ、持たないの組合せは全部で 2^n通り、後はループを回すだけ

アイテム数4の { (2, 3), (1, 2), (3, 4), (2, 2) } を例にすると、2 ^ 4 = 16 です。ここで、

アイテム(2, 3)(1, 2)(3, 4)(2, 2)
対応する値1248

と対応させます。すると、 (2, 3) と (3, 4) をナップサックに詰めることを 5 (== 1 + 4) という値で表すことができます。値が0ならナップサックは空、15(==1+2+4+8)なら4アイテム全てを詰める、という意味になります。
つまり、4個のアイテム全ての組合せは0~15という16種類の値で表現できることになり

for (ksack = 0; ksack < 16; ksack++) { // ksackの値=アイテムの組合せを評価する

という格好の、一重の for ループが総当たり検査になるのです。

ビットを1ずらす理由がよくわからない

1, 2, 4, 8 という値は、それぞれ2のべき乗です。
bit = 1 から始めて、bit <<= 1 でビットをひとつずつずらすと、
bit の値は 1, 2, 4, 8 ... と変化します。そこで
if ((ksack & bit) != 0) と、AND演算(というビット演算)によって、ksack にどのアイテムが含まれるかを判定することに使えます。

※以上、私は majiponi さんと違うコードで試したので、説明中のコード表記が異なりますが、言いたいことは伝わるだろうと思います。

投稿2019/07/09 16:35

編集2019/07/10 04:20
rubato6809

総合スコア1382

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

majiponi

2019/07/10 00:37

説明上手いなあ…(現役国語講師が負けててどうするって気もしますが)
rubato6809

2019/07/10 04:24

for ループで ksack++ とすべき所、「++」が抜けてたことに、BAいただいてから気づきました。修正しました。
guest

0

例2)n = 3、(w, v) = { (2, 2), (2, 1), (1, 1), }、W = 5 → 出力 7

この場合、出力は 4 ではないのですか?

取る値が入力次第で変わるのでfor文をどのように回せばいいかがわかりません

再帰呼出しを使うといいでしょう。

各品物を選ぶかどうかを総当たりでやると、すごい数になります。
選んだものの重さの総和が W を超えたら、次の品物には行かない
ようにすることで、場合の数を減らすことができます。

C

1#include <stdio.h> 2 3typedef struct { int w_i, v_i; } Goods; 4 5Goods *g; 6int n, W, w, v, max_v; 7 8void knapsack(int i) 9{ 10 if (i < n) { 11 w += g[i].w_i; 12 if (w <= W) 13 v += g[i].v_i, knapsack(i + 1), v -= g[i].v_i; 14 w -= g[i].w_i; 15 knapsack(i + 1); 16 } 17 else if (v > max_v) max_v = v; 18} 19 20int main(void) 21{ 22 Goods g1[4] = { {2, 3}, {1, 2}, {3, 4}, {2, 2} }; 23 g = g1, n = 4, W = 5, max_v = 0; 24 knapsack(0); 25 printf("%d\n", max_v); 26 27 Goods g2[3] = { {2, 2}, {2, 1}, {1, 1} }; 28 g = g2, n = 3, W = 5, max_v = 0;; 29 knapsack(0); 30 printf("%d\n", max_v); 31}

投稿2019/07/05 21:05

編集2019/07/05 21:08
kazuma-s

総合スコア8224

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Senri551853

2019/07/08 01:29

回答ありがとうございます。for文を使うことにとらわれていたので、こういうやり方もあるのかと驚いています。再帰について改めて勉強したいと思います >>この場合、出力は4ではないのですか? 仰る通りです、記載ミスでした…
guest

0

for文でやることにとらわれているようですがfor文は使いません。やり方はkazuma-s様と同じになりますが、ナップザック問題は再帰で解くのがいいようです。また、majiponiさんがビット操作を使うと仰っていますが、これだけで分かったらあなたは天才だと思います。

やり方としましては、まず答えを返してくれる予定の関数を宣言して、その中で、i番目の商品をナップザックに「入れる」or「入れない」の2通りに分けます。これがおそらくmajiponiさんの仰るビット操作だと思います。この関数を再帰させます。ナップザックは総当たりしか方法がないという説が主流なようです。この先は答えなのでヒントだけ欲しいならばこの先は読まないでください。

lang

1//関数だけ 2 3int knapsack(int i, int w){ //iはi番目の商品、wはi番目の商品の処理の時の残りの重さ 4 if(i >= n) return 0; //要素数を超えた商品は存在しない 5 if(w - w_i[i] < 0) return knapsack(i + 1, w); //重さの制限を超えたときは商品iは選ばない 6 7 int v1, v2; 8 v1 = knapsack(i + 1, w); //商品iは取らなかった時の最大価値 9 v2 = v_i[i] + knapsack(i + 1, w - w_i[i]); //商品iを取った時の最大価値 10 11 return v1 > v2 ? v1 : v2; 12}

投稿2019/07/06 14:21

編集2019/07/07 00:06
tometome

総合スコア27

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kazuma-s

2019/07/06 14:56

そのプログラムでは、 n = 3、(w, v) = { (1, 2), (4, 1), (2, 3), }、W = 3 → 出力 5 となるべきところが 2 になりませんか?
tometome

2019/07/07 00:07

ご指摘ありがとうございます。気を付けなければいけませんね...
Senri551853

2019/07/08 01:30

回答ありがとうございます。 forで総当たりしなくても実現できるのですね。再帰の考え方がいまいちわからなくて触れずにいたのですが改めて勉強したいと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問