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

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

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

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

4回答

3169閲覧

再帰で漸化式を解こうとしたが、引数が大きくなると実行時間が長くなりすぎる

pupperccino

総合スコア17

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2019/03/13 11:39

前提

以下の問題を解いています。漸化式を立て、再帰で解くことを試しております。

dice_number個の、sides面ダイスを振ったとき、出目の総和がtargetとなる確率を出力するプログラムを作成せよ

※再帰についての理解はあまり深くないです。「関数のreturn文に関数自身を書いておくと、プログラムが勝手に何回も走って答を出してくれる」くらいに理解しております。

発生している問題

dice_numberが小さいうちはすぐに答えが出るのですが、10前後になるとプログラムが終了しません。
また、出力そのものの正しさについては、小さいdice_numberで確認しております。

###方針
dice_numberをn, sidesをs, targetをtと書きます。問題の条件を満たす場合の数N=N(n,s,t)は漸化式
N(n,s,t)=N(n-1,s,t-1)+N(n-1,s,t-2)+...+N(n-1,s,t-s)
を満たすので、再帰で計算できる(はず)。

該当のソースコード

python3.7.1

1# 場合の数Nを求める 2def N(dice_number: int, sides: int, target: int) -> int: 3 # 初期条件 4 if dice_number == 1: 5 if 1 <= target <= sides: 6 return 1 7 else: 8 return 0 9 # 漸化式の本体 10 else: 11 return sum([N(dice_number-1, sides, target-i) for i in range(1, sides+1)]) 12 13 14# 場合の数Nを全組み合わせのsides**dice_numberで割ると確率になる 15def probability(dice_number, sides, target): 16 return N(dice_number, sides, target)/sides**dice_number

教えていただきたいこと

  1. そもそも再帰で解くのが最適な問題なのでしょうか?
  2. (もしそうだとすれば)どこを直せばもっと高速に動作するプログラムになるのでしょうか?

ご教示お願い致します。

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

出典はこちら

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

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

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

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

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

guest

回答4

0

そもそも再帰で解くのが最適な問題なのでしょうか?

いいえ

(もしそうだとすれば)どこを直せばもっと高速に動作するプログラムになるのでしょうか?

全部

再帰って避けられるなら避け計算量を減らす方向性が正しいです。
今回のケースだと、計算結果をキャッシュしておくのが定石かと。

投稿2019/03/13 12:02

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

退会済みユーザー

退会済みユーザー

2019/03/13 13:06

自前実装前提で回答してました^^; この辺が整理されているのはさすが python って感じですね。 フォローありがとうございます。
hayataka2049

2019/03/13 13:11

コメントしてから気づきましたが、Lhankor_Mhyさんの「メモ化」のリンクが思いっきりこの話でした。かぶった
guest

0

とりあえず、

python

1def N(dice_number: int, sides: int, target: int) -> int: 2 print(dice_number, sides, target) 3 ...

として、probability(3,6,7)のログをとってみた結果が以下です。

3 6 7 2 6 6 1 6 5 1 6 4 1 6 3 1 6 2 1 6 1 1 6 0 2 6 5 1 6 4 1 6 3 1 6 2 1 6 1 1 6 0 1 6 -1 2 6 4 1 6 3 1 6 2 1 6 1 1 6 0 1 6 -1 1 6 -2 2 6 3 1 6 2 1 6 1 1 6 0 1 6 -1 1 6 -2 1 6 -3 2 6 2 1 6 1 1 6 0 1 6 -1 1 6 -2 1 6 -3 1 6 -4 2 6 1 1 6 0 1 6 -1 1 6 -2 1 6 -3 1 6 -4 1 6 -5

よく見ると、同じパラメータでの呼び出しが何度も行われています。これをメモ化すれば、かなり計算量を減らせるでしょう。
また、targetが負になっているものはそれ以上ダイスロールがいらないはずなので、それ以上の呼び出しを刈ることができるはずです。

投稿2019/03/13 12:00

編集2019/03/13 12:02
Lhankor_Mhy

総合スコア36117

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

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

0

ベストアンサー

メモ化した例を示します。
x.py

python3

1from functools import lru_cache 2import time 3 4# 場合の数Nを求める 5def N(dice_number, sides, target): 6 if dice_number < 1 or target < dice_number or dice_number * sides < target: 7 return 0 8 if dice_number == 1: 9 if 1 <= target <= sides: 10 return 1 11 else: 12 return 0 13 return sum([N(dice_number - 1, sides, target - i) for i in range(1, sides + 1)]) 14 15@lru_cache() 16def N_X(dice_number, sides, target): 17 if dice_number < 1 or target < dice_number or dice_number * sides < target: 18 return 0 19 if dice_number == 1: 20 if 1 <= target <= sides: 21 return 1 22 else: 23 return 0 24 return sum([N_X(dice_number - 1, sides, target - i) for i in range(1, sides + 1)]) 25 26loop = 4 27 28 29for _x in range(loop): 30 start = time.time() 31 print(N(10, 6, 30)) 32 print("elapsed N :{:f}".format(time.time() - start)) 33 34 start = time.time() 35 print(N_X(10, 6, 30)) 36 print("elapsed N_X:{:f}".format(time.time() - start))

実行例

イメージ説明

参考情報

  • Pythonで再帰フィボナッチのメモ化

https://qiita.com/takey/items/4b1767af0f0652ef8764

  • Pythonのベンチマークライブラリ「Benchmarker」

http://dragstar.hatenablog.com/entry/2015/04/21/193025

投稿2019/03/13 14:46

katoy

総合スコア22324

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

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

0

10面ダイスを10回振った場合の確率を求めるテストケースもあるようなので、全ての組み合わせを求める方法では制限時間をオーバーします。動的計画法などを使う必要があると思います。

参考: 複数のサイコロの目の和がある値になる確率を求める

投稿2019/03/13 11:53

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問