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

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

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

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

Q&A

解決済

1回答

2476閲覧

「素数の足し算で」の解き方(スタックによる深さ優先探索)を教えてください

kuma_dansyaku

総合スコア18

C

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

0グッド

0クリップ

投稿2017/05/26 04:30

はじめに

いつもお世話になっています。
先日同様の件名について質問をさせて頂きました。

「素数の足し算で」の解き方を教えてください
https://teratail.com/questions/77349

木やグラフをしらみつぶしに探索する深さ優先探索(再帰あり)のアルゴリズムを教えて頂きました。
教えていただいたので実際に実装して確認してみました。

幅優先探索は特に問題なく実装出来ました。
しかし深さ優先探索(再帰なしでスタックを使用する方法)が上手くいきません。

ソースコードを添付しますので、指摘をお願いいたします。

C

1/* 2 ある範囲内の素数の足し算で、ある数Xを表現するパターンがいくつ存在するかを表示するプログラム(ある範囲は100以下とする) 3 4 2-19 までの素数で38を表現するパターンは以下の6通りなので答えは6 5 [2+17+19] 6 [3+7+11+17] 7 [3+5+13+17] 8 [3+5+11+19] 9 [2+5+7+11+13] 10 [2+3+5+11+17] 11*/ 12#include <stdio.h> 13 14#define TRUE (1) /* 成功 */ 15#define FASLE (0) /* 失敗 */ 16 17#define STACK_SIZE (100) /* スタックの数 */ 18 19 int _stack_data[STACK_SIZE]; /* スタック格納バッファ */ 20 int _stack_num; 21 22 int push(int push_data) { 23 if (_stack_num < STACK_SIZE) { 24 _stack_data[_stack_num] = push_data; 25 _stack_num++; /* next elements */ 26 return TRUE; 27 } 28 else { 29 return FASLE; 30 } 31 } 32 33 int pop(int *pop_data) { 34 if (0 < _stack_num) { 35 _stack_num--; 36 *pop_data = _stack_data[_stack_num]; 37 return TRUE; 38 } 39 else { 40 return FASLE; 41 } 42 } 43 44#define N (25) /* elements number */ 45 int prime[N] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97 }; /* 1-100 までの素数たち */ 46 47 int main() { 48 int i, match_cnt; 49 int imin, imax, ival, sum; 50 51 // "6 19 38" を入力 52 printf("input [MIN] [MAX] [TTL]\n"); 53 scanf_s("%d%d%d", &imin, &imax, &ival); 54 printf("input no %d %d %d\n", imin, imax, ival); 55 56 for (i = 0; imin > prime[i]; i++); 57 push(prime[i++]); // 加算した値 58 push(0); // 加算しない値 59 match_cnt = 0; 60 61 while (_stack_num ) { // スタックが空っぽになるか 62 pop(&sum); 63 64 if (sum == ival || (sum + prime[i]) == ival) { 65 match_cnt++; // 一致した値 66 continue; 67 } 68 if (sum > ival || (sum + prime[i]) > ival) { 69 continue; 70 } 71 if (prime[i] > imax) { 72 continue; 73 } 74 75 push(sum + prime[i++]); // 加算した値 76 push(sum); // 加算しない値 77 } 78 printf("match=%d\n", match_cnt); // 6が出力されて欲しい 79 80 return 0; 81 }

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

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

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

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

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

t_obara

2017/05/26 07:25

そもそもの疑問ですが、貴殿はこの問題をなぜ解決したいのですか?学校などの課題解決のため?プログラム学習のため?などなどあると思いますが、
guest

回答1

0

ベストアンサー

深さ優先探索は再帰を使うと分かりやすいですが、再帰を使わずにやりたいなら「あたかも再帰をしているかのように」スタックの使い方を工夫すればよいと思います。

wikipedia:深さ優先探索に再帰を使う例とスタックを使う例の考え方が疑似コードで示されています。疑似コードを参考に論理を把握し、それを参考に考えてみると訓練になると思います。

こうした問題は自分で考えることに価値があると思います。答えがでることそのものが目的の場合(例えば仕事とか)だと違うでしょうが、勉強が目的なら自分で考えるべきです。

ちなみに自分が実装した例を挙げてみます。残念ながらwikiの疑似コードによく対応しているようには見えないかも知れませんが、自分なりに再帰の代わりにスタックを使うとしたら・・・と考えたものです。

push, popにデバッグプリントの後が残ってますがあえて残してみました。つまり以下のコードは一発で動かせてません。動かなかったのでデバッグしたのです。自分が書いたコードが動かない場合、大抵のプログラマーはデバッグします。

C

1#include <stdio.h> 2 3#define N (sizeof(primes)/sizeof(primes[0])) 4 5int primes[] = { 6 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 7 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97 8}; 9 10int stack[N]; 11int sp = 0; 12 13void push(int d) { 14 stack[sp++] = d; 15 //printf("push: %d sp=%d\n", d, sp); 16} 17 18int pop() { 19 //printf("pop: %d sp=%d\n", stack[sp - 1], sp); 20 return stack[--sp]; 21} 22 23int isEmpty() { 24 return sp == 0; 25} 26 27void printAnswer() { 28 printf("["); 29 for (int i = 0; i < sp; i++) { 30 printf("%s%d", (i > 0) ? " " : "", primes[stack[i]]); 31 } 32 printf("]\n"); 33} 34 35void solve(int d) { 36 int rest = d; 37 int candidateIndex = 0; 38 int answerCount = 0; 39 print("target = %d\n", d); 40 do { 41 if (rest == 0) { 42 printAnswer(); 43 answerCount++; 44 } 45 if (rest > 0 && candidateIndex < N && primes[candidateIndex] <= rest) { 46 push(candidateIndex); 47 rest -= primes[candidateIndex]; 48 candidateIndex++; 49 } else if (!isEmpty()) { 50 candidateIndex = pop(); 51 rest += primes[candidateIndex]; 52 candidateIndex++; 53 } 54 } while (!isEmpty() || (candidateIndex < N && primes[candidateIndex] <= d)); 55 printf("number of answers = %d\n", answerCount); 56} 57 58int main() { 59 solve(38); 60 return 0; 61} 62 63==> 結果 64target = 38 65[2 3 5 11 17] 66[2 5 7 11 13] 67[2 5 31] 68[2 7 29] 69[2 13 23] 70[2 17 19] 71[3 5 7 23] 72[3 5 11 19] 73[3 5 13 17] 74[3 7 11 17] 75[7 31] 76number of answers = 11

ちなみに、識別子をキャメルケースで書いてしまっていますがご容赦を。

投稿2017/05/26 07:47

KSwordOfHaste

総合スコア18394

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

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

kuma_dansyaku

2017/05/26 14:54

回答ありがとうございます。 もう少しデバッグに対する粘りが足りなかったかなと反省しています。 頂いたソースコードはもう少し頑張ってみてから参考にさせていただきます。 自分はスタックを使った実装の方が再帰を使うよりは分かりやすくて簡単だと思っていたのですが、頂いた内容から察すると、実は全くの逆でスタックで実装する方が物凄く分かりづらかったりするのでは?と自分の読み取りの甘さを感じています。
KSwordOfHaste

2017/05/26 22:31

アルゴリズムに対する慣れにより違うかもですが・・・ スタックだとバックトラック(一つ前の時点に状態を戻して次の探索を継続する制御)が少々「ややこしい」あるいは「トリッキー」に見えますが、再帰では単に関数からリターンするだけで済むため「制御論理が単純になりやすい」と思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問