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

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

新規登録して質問してみよう
ただいま回答率
85.48%
アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

Python

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

Q&A

解決済

5回答

1859閲覧

数値を組み合わせてある値に近づけるアルゴリズムについて

pyxis

総合スコア16

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

Python

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

1グッド

1クリップ

投稿2020/01/28 00:14

編集2020/01/28 01:03

「Pythonではじめるアルゴリズム入門」という本の幅優先探索や深さ優先探索について書かれている章の
理解度チェック問題として以下のような問題と解答が掲載されていました。
しかし、解答コードに誤りがあるように思えるので自分でコードを書いたのですが正しいでしょうか?

問題:
都道府県の人口を組み合わせて1千万人に近い組み合わせとその人口を求めよ。

本に載っている解答:

Python

1# 近づける値 2goal = 10000000 3 4# 各都道府県の人口 5pref = [ 6 5381733, 1308265, 1279594, 2333899, 1023119, 1123891, 1914039, 7 2916976, 1974255, 1973115, 7266534, 6222666, 13515271, 9126214, 8 2304264, 1066328, 1154008, 786740, 834930, 2098804, 2031903, 9 3700305, 7483128, 1815865, 1412916, 2610353, 8839469, 5534800, 10 1364316, 963579, 573441, 694352, 1921525, 2843990, 1404729, 11 755733, 976263, 1385262, 728276, 5101556, 832832, 1377187, 12 1786170, 1166338, 1104069, 1648177, 1433566 13] 14 15min_total = 0 16def search(total, pos): 17 global min_total 18 if pos >= len(pref): 19 return 20 if total < goal: 21 if abs(goal - (total + pref[pos])) < abs(goal - min_total): 22 min_total = total + pref[pos] 23 search(total + pref[pos], pos + 1) 24 search(total, pos + 1) 25 26search(0, 0) 27print(min_total) #10001495 28

このコードだと答えは10001495となるのですが、

if abs(goal - (total + pref[pos])) < abs(goal - min_total):

で探索されるべきところが間違って枝刈りされているように見えます。

例えば

goal = 10
pref = [3,4,5,1,1,5]

だと、答えは10のはずが、12と明らかに間違った答が出るのですが
[3,4,5]でmin_totalが一度12になったらその後は[1]単体などは
if abs(goal - (total + pref[pos])) < abs(goal - min_total)
ではじかれて探索されなくなっているように見えます。

そこで次の様に書きました。

Python

1# 近づける値 2goal = 10000000 3 4# 各都道府県の人口 5pref = [ 6 5381733, 1308265, 1279594, 2333899, 1023119, 1123891, 1914039, 7 2916976, 1974255, 1973115, 7266534, 6222666, 13515271, 9126214, 8 2304264, 1066328, 1154008, 786740, 834930, 2098804, 2031903, 9 3700305, 7483128, 1815865, 1412916, 2610353, 8839469, 5534800, 10 1364316, 963579, 573441, 694352, 1921525, 2843990, 1404729, 11 755733, 976263, 1385262, 728276, 5101556, 832832, 1377187, 12 1786170, 1166338, 1104069, 1648177, 1433566 13] 14 15total_temp = float('inf') 16 17#total:合計 pos:探索ポジション l:探索ルートを入れるリスト pre:1つ前のtotal 18def search(total, pos, l, pre): 19 global total_temp 20 21 if total >= goal or pos >= len(pref): 22 a = abs(total - goal) 23 b = abs(pre - goal) 24 25 #preの方がgoalに近い場合 26 if a > b: 27 if b < abs(goal - total_temp): 28 total_temp = pre 29 print(total_temp,l[:-1] ) 30 #totalの方がgoalに近い場合 31 else: 32 if a < abs(goal - total_temp): 33 total_temp = total 34 print(total_temp, l) 35 return 36 37 #total < goal 38 else: 39 l1 = l.copy() 40 l1.append(pref[pos]) 41 search(total + pref[pos], pos + 1, l1, total) 42 search(total, pos + 1, l, total) 43 44search(0, 0, [], 0) 45print(total_temp)

これだと答えがぴったり
10000000
で組み合わせが
[1308265, 2333899, 786740, 2031903, 573441, 755733, 832832, 1377187]
と出たのですが、このコードで合っているでしょうか?
またもっと効率の良いアルゴリズムなどはあるでしょうか?

hayataka2049👍を押しています

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

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

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

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

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

quickquip

2020/01/28 00:27

どういったアルゴリズムの説明として出てきていますか? 双方のプログラムの時間(試行回数)の比較はしていますか? (コードの詳細を読む前に知っておきたいだろう情報として追記するとよいかと思います)
pyxis

2020/01/28 00:39

幅優先探索や深さ優先探索について書かれた章末の理解度チェックとして出題されていました。
quickquip

2020/01/28 00:48

この欄に書くのではなくて質問を編集しましょう。
pyxis

2020/01/28 01:04

質問の方を編集しました。
guest

回答5

0

直接の回答ではありませんが

このコードで合っているでしょうか?

厳密解と比較してみるとよいかと思います。
以下は厳密解を求めるコード例です。枝刈などはしていないのでとても遅いですが。

Python

1import itertools 2import random 3 4# 厳密解を求める 5def search(goal, pref): 6 min_total,min_combi = 0, [] 7 8 # 全組合せを列挙 9 for bit in itertools.product([0,1], repeat=len(pref)): 10 combi = [p for b,p in zip(bit, pref) if b == 1] 11 total = sum(combi) 12 13 # 完全一致 14 if total == goal: 15 return combi 16 17 # 最近値を記録 18 if abs(goal-total) < abs(goal-min_total): 19 min_total, min_combi = total, combi 20 21 return min_combi 22 23 24# ランダムテスト 25random.seed(110) 26for i in range(5): 27 28 # ランダムにデータ生成 29 N = random.randint(2, 20) # 最大20個程度が限度 30 pref = [random.randint(10, 100) for _ in range(N)] 31 goal = int(sum(pref) * random.random()) 32 33 min_combi = search(goal, pref) 34 35 print('-----') 36 print( 'goal={}, pref={}'.format(goal, pref)) 37 print( 'min_total={}, min_combi={}'.format( sum(min_combi), min_combi)) 38 39""" 40----- 41goal=101, pref=[86, 41, 62, 71, 45, 91, 79, 98, 19, 79, 63, 74, 50, 15] 42min_total=101, min_combi=[41, 45, 15] 43----- 44goal=791, pref=[20, 29, 66, 90, 11, 77, 64, 85, 94, 39, 45, 46, 56, 56, 74, 23, 62, 81] 45min_total=791, min_combi=[90, 77, 64, 85, 94, 45, 46, 56, 56, 74, 23, 81] 46----- 47goal=130, pref=[45, 97, 26, 34, 43, 62, 70, 39, 32, 67, 38, 20, 73, 52, 52, 41] 48min_total=130, min_combi=[43, 67, 20] 49----- 50goal=186, pref=[35, 44, 32, 10, 86, 44] 51min_total=184, min_combi=[44, 10, 86, 44] 52----- 53goal=26, pref=[69, 61] 54min_total=0, min_combi=[] 55"""

投稿2020/01/28 08:54

can110

総合スコア38262

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

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

pyxis

2020/01/28 11:38

確かに答えが正しいかどうかは全探索した答えと比較するのが確実ですね。
guest

0

ベストアンサー

この問題に関しては半分全列挙が効率よさそうです。
ただしこの類の問題は、パラメーターの値やサイズによって効率のいいアルゴリズムが変わってくるので、今の段階でここを掘り下げてもあまり得るものはないと思います。

Python

1# 近づける値 2goal = 10000000 3 4# 各都道府県の人口 5pref = [ 6 5381733, 1308265, 1279594, 2333899, 1023119, 1123891, 1914039, 7 2916976, 1974255, 1973115, 7266534, 6222666, 13515271, 9126214, 8 2304264, 1066328, 1154008, 786740, 834930, 2098804, 2031903, 9 3700305, 7483128, 1815865, 1412916, 2610353, 8839469, 5534800, 10 1364316, 963579, 573441, 694352, 1921525, 2843990, 1404729, 11 755733, 976263, 1385262, 728276, 5101556, 832832, 1377187, 12 1786170, 1166338, 1104069, 1648177, 1433566 13] 14 15#二分探索でvalue以上の要素のインデックスを求める。 16def lowerBound(vec, value): 17 left = 0 18 right = len(vec) 19 while left < right: 20 mid = (left + right) // 2 21 if vec[mid][0] < value: 22 left = mid + 1 23 else: 24 right = mid 25 return right 26 27def restoreSet(vec, bit): 28 result = [] 29 for i in range(len(vec)): 30 if (1 << i) & bit != 0: 31 result.append(vec[i]) 32 return result 33 34#prefの前半の組み合わせのうち明らかに最適な組み合わせに含まれないものを除いたもの 35#[(合計、組み合わせのビット表現)] 36first = [(0, 0)] 37for i in range(len(pref)//2): 38 for j in range(len(first)): 39 if first[j][0] < goal: 40 first.append((first[j][0] + pref[i], first[j][1] | (1 << i))) 41 42#prefの後半の組み合わせのうち明らかに最適な組み合わせに含まれないものを除いたもの 43#[(合計、組み合わせのビット表現)] 44second = [(0, 0)] 45for i in range(len(pref)//2, len(pref)): 46 for j in range(len(second)): 47 if second[j][0] < goal: 48 second.append((second[j][0] + pref[i], second[j][1] | (1 << i))) 49 50second.sort() 51min_total = 0 52min_set = 0 53for f in range(len(first)): 54 i = first[f][0] 55 #first[f][0] + second[s][0] >= goalとなる最小のs。 56 s = lowerBound(second, goal - i) 57 #first[f][0]と組み合わせてgoalに最も近くなる可能性があるのはsecond[s][0]かsecond[s - 1][0]のみ 58 if len(second) > s and abs(goal - min_total) > abs(goal - i - second[s][0]): 59 min_total = i + second[s][0] 60 min_set = first[f][1] | second[s][1] 61 if s > 0 and abs(goal - min_total) > abs(goal - i - second[s - 1][0]): 62 min_total = i + second[s - 1][0] 63 min_set = first[f][1] | second[s - 1][1] 64 65set = restoreSet(pref, min_set) 66print(sum(set)) 67print(set)

投稿2020/01/28 06:33

yudedako67

総合スコア2047

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

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

pyxis

2020/01/28 11:47

このコードだとかなり速く答えが出て驚きました。 半分全列挙というのは初めて知りました。 ソートなどもそうですが、単純そうに見える問題も奥が深く色々なアルゴリズムがあり難しいですね。
guest

0

まず本の解答の間違いですが、最初の再帰呼び出しのインデントが間違っています。

Python

1min_total = 0 2def search(total, pos): 3 global min_total 4 if pos >= len(pref): 5 return 6 if total < goal: 7 if abs(goal - (total + pref[pos])) < abs(goal - min_total): 8 min_total = total + pref[pos] 9 search(total + pref[pos], pos + 1) # <- ここ 10 search(total, pos + 1)

この解答と、あなたの解答とは、ざっと見たところ本質的に同じようです。なので正しいはずです。

もっと効率の良いアルゴリズムがあるかどうかは、知りません。

投稿2020/01/28 03:18

Bearded-Ockham

総合スコア430

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

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

pyxis

2020/01/28 11:40

確かにインデントをずらすと正しい答えを出しますね。 ずらして修正した模範解答のコードの方が自分が書いたコードよりスマートです。
guest

0

模範解答の
search(total + pref[pos], pos + 1)
のインデントを1減らせばよいかと。

新しく書かれた方は挙動はさておきcopyとappendに無駄があるのであまりよろしくないかと思います。

投稿2020/01/28 03:17

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

退会済みユーザー

退会済みユーザー

2020/01/28 03:28

組み合わせ情報も欲しければ現在の組み合わせと暫定最適の組み合わせを0,1で表現した値をglobalでもたせ、再帰呼び出しの前後で値を修正すればよいかと思います。
pyxis

2020/01/28 11:41

たしかにグローバル変数を利用すれば余計なcopyを繰り返さなくて済みますね。
guest

0

質問内に書いた自分のコードは
a = abs(total - goal)
b = abs(pre - goal)
を比較しているのですが、その比較は必要なく以下のように書けば十分であると気付きました。
また、同値であった場合の組み合わせも出力するようにしました。

Python

1# 近づける値 2goal = 10000000 3 4# 各都道府県の人口 5pref = [ 6 5381733, 1308265, 1279594, 2333899, 1023119, 1123891, 1914039, 7 2916976, 1974255, 1973115, 7266534, 6222666, 13515271, 9126214, 8 2304264, 1066328, 1154008, 786740, 834930, 2098804, 2031903, 9 3700305, 7483128, 1815865, 1412916, 2610353, 8839469, 5534800, 10 1364316, 963579, 573441, 694352, 1921525, 2843990, 1404729, 11 755733, 976263, 1385262, 728276, 5101556, 832832, 1377187, 12 1786170, 1166338, 1104069, 1648177, 1433566 13] 14 15temp = float('inf') 16ary = [] 17 18def search(total, pos): 19 global ary, temp 20 21 # total >= goal にならなかった組み合わせも必ず最後はpos == len(pref)となりここの分岐に入る 22 if pos == len(pref) or total >= goal: 23 # <=にすることで同値であっても表示 24 if abs(goal - total) <= abs(goal - temp): 25 print(total, ary) 26 temp = total 27 28 return 29 30 else: 31 ary.append(pref[pos]) 32 search(total + pref[pos], pos + 1) 33 del ary[-1] 34 search(total, pos + 1) 35 36search(0, 0) 37 38 39

投稿2020/01/28 11:55

編集2020/01/28 15:05
pyxis

総合スコア16

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問