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

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

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

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

Q&A

3回答

4657閲覧

Pythonの計算量について

babbleman

総合スコア107

Python 3.x

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

0グッド

0クリップ

投稿2019/01/03 09:52

2つの要素a,bがn回与えられるとします(もちろんabは毎回異なる値)。
これを2次元配列Xから探します
探し方としては
for i in X:
If a in i
のような要領です。
ab両者とも絶対に多次元配列の中で重複していません。
このような時、aが見つかった場合次からの繰り返しでaを探す必要がないです。
なのでbだけを探すようにしています。
もし、a,b両者とも見つかればp回目(1<=p<=n)の探索は終了でfor文をbreakします。

このような時のp回目の探索において、計算量を減らす目的で、最初に
Aflag=False
Bflag=False
と設定して、見つかったらそれぞれをTrueにしているのですが、
for文の中で毎回これらがTrueかFalseかで4通りの場合分けをして探索時間を減らすようにしたのですが、計算量が多いらしく時間をオーバーしてしまいました。
私の考えでは、
if
elif
elif
else
の中で条件に合わないのはさっと飛ばして条件にあった物だけ処理をするので、人間が一生懸命場合分けをすればその分コンピュータは楽になると思ったのですが違いますでしょうか?

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

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

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

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

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

guest

回答3

0

計算量がオーダーのことであれば、定数倍の改善なので効きません。

まず1要素探す場合を考えます。擬似コードで適当に示します。

python

1ターゲット = ... 2 3for 要素 in 対象の配列(1次元): 4 if 要素がターゲットなら: 5 indexでも記録しておく 6 break

これが平均で対象の配列の要素数/2でできることはおわかりかと思います。

2要素探す場合は、まあ質問文の方式だとこんな感じですか。

python

1ターゲットA = ... 2ターゲットB = ... 3フラグA = True 4フラグB = True 5 6for 要素 in 対象の配列(1次元): 7 if フラグA and 要素がターゲットAなら: 8 indexでも記録しておく 9 フラグA = False 10 elif フラグB and 要素がターゲットBなら: 11 indexでも記録しておく 12 フラグB = False 13 elif フラグAとBがともにFalse: 14 break

発想としては適切なフラグを立てて無駄な比較を減らそうということですね。

実は下のコードでもまったく同じです。

python

1ターゲットA = ... 2ターゲットB = ... 3 4for 要素 in 対象の配列(1次元): 5 if 要素がターゲットAなら: 6 indexでも記録しておく 7 break 8 9for 要素 in 対象の配列(1次元): 10 if 要素がターゲットBなら: 11 indexでも記録しておく 12 break 13

平均が要素数/2のところこれの2倍ですから、要素数と等しい比較回数になります。いずれにせよO(Xの要素数)です。

「計算コスト」のことであれば、確かに比較回数は1/2にできていますが、余計な条件分岐と真理値計算が入りますからどちらの効果が勝るかで決まります。一般論としては、実測してみないとなんともいえないですね。

投稿2019/01/03 19:58

hayataka2049

総合スコア30933

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

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

0

質問者さんが2次元の配列といっておられるのは実際には「入れ子になったlist」なはずなのでそう想定します。例えばnumpy.ndarrayだとそもそも行ごとにin演算子で検査せずともa in xで結果がでてしまい、ご質問の工夫の意味が失われてしまいますので。

それはともかく...

人間が一生懸命場合分けをすればその分コンピュータは楽になると思ったのですが違いますでしょうか?

「そのとおり」です。ゆえに「どうしたら計算の手間を少しでも減らせるか」を工夫することは論理を考えるよい訓練になると思います。

他の方が少々否定的に見えるコメントをしておられますが、それはおそらく「かけた手間に対して充分効果があるか」というより進んだ段階のプログラマーの感覚からくるコメントだと思います。

つまり**質問者さんが問いかけたことの答えは「YES」**なんですが、その効果の程度についてよく考えた方がよいということではないでしょうか?

細かい考え方などについては他の方が既に回答しておられるので、自分は「とりあえず実際動かしてみよう」というノリでやってみたことをご紹介しようと思います。

4つの実装例を挙げて実際にやってみます。

python

1def contained_both1(a, b, x): 2 a_exists = b_exists = False 3 for row in x: 4 if a in row: 5 a_exists = True 6 if b in row: 7 b_exists = True 8 return a_exists and b_exists 9 10def contained_both2(a, b, x): 11 a_exists = b_exists = False 12 for row in x: 13 if not a_exists and a in row: 14 a_exists = True 15 if not b_exists and b in row: 16 b_exists = True 17 return a_exists and b_exists 18 19def contained_both3(a, b, x): 20 a_exists = b_exists = False 21 for i in range(len(x)): 22 row = x[i] 23 if a in row: 24 a_exists = True 25 b_exists = b in row 26 break 27 if b in row: 28 b_exists = True 29 break 30 else: 31 return false 32 if a_exists: 33 if b_exists: 34 return True 35 for i in range(i, len(x)): 36 if b in x[i]: 37 return True 38 else: 39 for i in range(i, len(x)): 40 if a in x[i]: 41 return True 42 return false 43 44def contained_both4(a, b, x): 45 for row in x: 46 if a in row: 47 break 48 else: 49 return False 50 for row in x: 51 if b in row: 52 return True 53 return False
  • (1) contained_both1

何も工夫してない。途中で見つかっても必ず最後までa, b両方を各行から探す

  • (2) contained_both2

aやbが以前の行で見つかったらそれ以降の行ではin演算子の判定をスキップ

  • (3) contained_both3

aかbが見つかったらいったんループを中断し、それ以降の行から見つかってない方(a or b)だけを探す

  • (4) contained_both4

何も工夫してない。ループを2回に分けa, bそれぞれを別のループで探す。ただし途中で見つかったとき単純にbreakできるので(1)よりは早いかも知れないが2回ループを実行するのが遅くなる心配もある

これを100x100の入れ子のlistの中からa, bの見つかる行をいろいろに変えて(1)~(4)をそれぞれ100回実行してみた結果が以下です(1回あたりの検索時間。単位は秒)。

(1) (2) (3) (4) 0.195643 0.099513 0.100657 0.097728 平均 0.002405 0.000975 0.000948 0.000967 標準偏差

hayataka2049さん回答にあるように(2)の工夫により(1)の半分の実行時間で済んでいます。つまり実際に効果がありそうだといえましょう。ただし、少しでもオーバーヘッドを減らそうと意図した(3)は(2)と大差ないです。却ってちょっと遅くなっているようにも見えますが誤差のレベル。そしてなんだかがっかりする結果が(4)です。ほとんど工夫してないのに(2)や(3)より(この測定では)早いという結果が出てしまってますね。

要するに人間が考えると手間を減らしたように見えることでも、実際にコードにすると計算機(言語)の様々な言語機能の性能特性(効率)のバランスによって効果が出たり出なかったりするといえましょう。


最後に・・・(というよりここが本題だったりしますが)

計算量が多いらしく時間をオーバーしてしまいました。

質問者さんの工夫は何割かの削減の効果が望めることが実測によっても確認できました。しかしながら(hayataka2049さんがほのめかしておられる?)ように、一般的に最も注目すべき点は計算量のオーダーを下げる工夫であるといえます。

本件の条件をよく見ると「同じXに対して複数回異なるa, bの組に対して検索をする」というのがポイントだと思います。その点に着目してある工夫(5)をした結果を測定してみますと

text

1 (1) (2) (3) (4) (5) 2 0.195643 0.099513 0.100657 0.097728 0.000477 平均 3 0.002405 0.000975 0.000948 0.000967 0.000017 標準偏差

このように何割というレベルの削減ではなく何桁というレベルの削減になります。
「時間をオーバー」したというのはどこかのプログラミング問題サイトでの話ではないかと想像します。そのようなサイトでは「計算量のオーダーを下げることで何桁もの削減を工夫すること」がクリアの前提になるだろうと思います。

質問者さんは既に「論理を工夫して計算コストを下げる」取り組みをしておられるわけですが、計算量の考え方を学びもう一歩だけ進んだ(計算量オーダーが低い)アルゴリズムを狙うとよいと思う次第です。

投稿2019/01/14 15:45

編集2019/01/14 22:54
KSwordOfHaste

総合スコア18394

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

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

0

ロジックを見てないので、正確には言えませんが、
たぶん意味がないか、遅くなってるかと想像します。

配列の要素数が、わかりませんがシーケンシャルに検索するより、
ソートしたあとに検索する方が効率的になる可能性があるかと思います。

例えば、要素が1000の場合
シーケンシャルの場合は、500回ですが
ソート済みであれば、10回になります。

場合分け自体に効果が無いとはいいませんが、計算量が減るロジックに、なってるかどうかですね。

投稿2019/01/03 11:14

momon-ga

総合スコア4820

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問