第11章の解答。以下ネタバレなので、読みたくない方は立ち去りましょう。問題文は載せませんので悪しからず。必要なら買いましょう。
1
k回探索するものとすると、ソート+探索と、毎回探索するという計算の量について次の式が成り立つ(定数項がないため、すごく大まかですが)。k*log_2(N) + N*log_2(N) <= k*N*log_2(N)(_2は対数の底が2であることを意味する)。これを満たすkはk >= N/(N-1)である。右辺はN=1のとき最大値2をとり、かつ1より大きい。従ってk>=2であればよく、2回探索した時点で毎回未ソートのリストから探索するよりも、ソートしてから探索を繰り返すほうが高速になる。
2
利用したコードは以下の通り。既存のソートアルゴリズムに毎回print文を挟めばよい。
a)の場合
- [1, 5, 4, 3, 7, 6, 2]
- [1, 2, 4, 3, 7, 6, 5]
- [1, 2, 3, 4, 7, 6, 5]
- [1, 2, 3, 4, 7, 6, 5]
- [1, 2, 3, 4, 5, 6, 7]
- [1, 2, 3, 4, 5, 6, 7]
- [1, 2, 3, 4, 5, 6, 7]
- [1, 2, 3, 4, 5, 6, 7]
b)の場合
- [5, 6, 4, 3, 7, 1, 2]
- [4, 5, 6, 3, 7, 1, 2]
- [3, 4, 5, 6, 7, 1, 2]
- [3, 4, 5, 6, 7, 1, 2]
- [1, 3, 4, 5, 6, 7, 2]
- [1, 2, 3, 4, 5, 6, 7]
- [1, 2, 3, 4, 5, 6, 7]
# -*- encoding: sjis -*-
def selection_sort(L):
''' L[:i]までソート済みとして、
L[i]とL[i:]の最小値の交換を繰り返す
'''
for i in range(len(L)):
smallest = L.index(min(L[i:]))
L[i], L[smallest] = L[smallest], L[i]
print L
return L
def insertion_sort(L):
''' L[:b]までソート済みとして、
L[b]をソート済みのリストに混ぜる
'''
for i in range(1, len(L)):
pos = 0
for j in range(i-1, -1, -1):
# print L[i], " ", L[j]
if L[i] >= L[j]:
pos = j + 1
# print "pos = ", pos, L[i], L[j]
break
L.insert(pos, L[i])
del L[i+1]
print L
return L
3
テストと実装のファイルは分けずに書いていますが、本当なら分けるべきでしょう。
# -*- encoding: sjis -*-
import nose
def swap(L, pos1, pos2):
tmp = L[pos1]
L[pos1] = L[pos2]
L[pos2] = tmp
def bubble_sort(L):
''' 1)先頭から隣り合う要素を比較し、添え字の小さいほうが
大きいようなら値の交換を行う
2)1)をi回繰り返すと後ろからi個の要素が整列済みになっている
要素数-1回繰り返す
'''
for i in range(len(L)-1, 0, -1):
for j in range(i):
if L[j] > L[j+1]:
swap(L, j, j+1)
print L
return L
# run_test とするとテストケースと勘違いされるのでrun_checkとする
def run_check(original, expected):
bubble_sort(original)
assert original == expected
def test_empty():
run_check([], [])
def test_one():
run_check([3], [3])
def test_reverse():
run_check([5, 4, 3, 2, 1], [1, 2, 3, 4, 5])
def test_same():
run_check([2, 2, 2, 2], [2, 2, 2, 2])
def test_random():
run_check([6, 5, 4, 3, 7, 1, 2], [1, 2, 3, 4 ,5 , 6, 7])
if __name__ == '__main__':
nose.runmodule()
4
テストコードは上のテストの使いまわしでよいと思います。
def bubble_sort_2(L):
''' 1)最後尾から先頭へ向けて隣り合う要素を比較し、添え字の
小さいほうが大きいようなら値の交換を行う
2)1)をi回繰り返すと先頭からi個の要素が整列済みになっている。
要素数-1回繰り返す
'''
for i in range(len(L)-1):
for j in range(len(L)-1, i, -1):
if L[j] < L[j-1]:
swap(L, j, j-1)
print L
5
用意したデータは降順データなので、全部のデータを反転させることになる。結果としては、バブルソートは全体的に遅いということが分かる。また、ソートにかかる時間はO(n^2)に従っていることが確認できる。バブルソートが特に遅いのは、比較の回数が多いためと思われる。一方挿入ソート、選択ソートで実行速度に大きな差は見られなかった。大量データをずらすというコストは、交換するコストとほとんど差がないのではないかと推測される。なお、ch11_sort_algo.pyに上に挙げたbubble_sortなどのソートアルゴリズムを実装している。
import time
from ch11_sort_algo import *
def print_times(L):
print len(L),
for func in (bubble_sort, bubble_sort_2, insertion_sort, selection_sort):
L_copy = L[:]
t_from = time.time()
func(L_copy)
t_to = time.time()
print "\t%.1f" % ((t_to - t_from) * 1000.),
print
for list_size in [10, 1000, 2000, 5000, 10000]:
L = range(list_size)
L.reverse()
print_times(L)
6
ソート済みの長さNのリストの場合
| 手法 | 比較回数 | コピー回数 |
| 挿入ソート | N-1回(毎回ソート済みの最終要素に1度アクセスして終了) | 0回(後ろにつけるだけでコピーは発生しない) |
| 選択ソート | N(N-1)/2回(未ソートの部分で最小値探索の必要あり) | 0回(未ソート部分の先頭が最小値と分かるのでコピー不要) |
リストが逆順
| 手法 | 比較回数 | コピー回数 |
| 挿入ソート | N(N-1)/2回(毎回ソート済みのリスト全体の探索の必要あり) | 0回(コピーではなく値の交換がN-1回発生する) |
| 選択ソート | N(N-1)/2回 | N-1回(コピーするリストの長さは可変) |
入力のリストがすべて同じ値の場合
| 手法 | 比較回数 | コピー回数 |
| 挿入ソート | N-1回(毎回ソート済みの最終要素にアクセスして終了) | 0回 |
| 選択ソート | N(N-1)/2回 | 0回 |
7
挿入に伴い発生するリストのコピーにかかるコストを考慮していない。これを考慮するのであれば、オーダーはN^2になる。全体の結論にも影響する。
8
ランダムに並び替えたものが偶然ソート済みになるのは、要素数がNであれば、平均してN!回に1回発生することなのでO(N!)になる。要素が一意であるかが問題になっているのは、一意かそうでないかでオーダーが変化するからである。例えば、N個のうちN-1個は同じ値で1つだけ違う値であれば、O(N)になるし、すべて同じ値であれば、O(1)になるからである。
9
extend呼び出しをなくすにはループ内で結合まで行うしかない。そこで、i1かi2が終端まで着たら他方をすべて結合してループを抜け出すという書き換えを行った。
# -*- encoding: sjis -*-
def merge(L1, L2):
newL = []
i1 = i2 = 0
while i1 != len(L1) or i2 != len(L2): # and から orに変更
if i1 >= len(L1): # 終端に着いたので他方の残りの要素を結合
newL += L2[i2:]
break
elif i2 >= len(L2):
newL += L1[i1:]
break
if L1[i1] <= L2[i2]:
newL.append(L1[i1])
i1 += 1
else:
newL.append(L2[i2])
i2 += 1
return newL
def mergesort(L):
workspace = []
for i in range(len(L)): # 1要素に分解
workspace.append([L[i]])
i = 0
while i < len(workspace) - 1: # マージ開始
L1 = workspace[i]
L2 = workspace[i + 1]
newL = merge(L1, L2)
workspace.append(newL)
i += 2
if len(workspace) != 0:
L[:] = workspace[-1][:]
return L
if __name__ == '__main__':
data = [3, 0, 2, 3, -1]
print mergesort(data)
data = [6, 5, 4, 3, 7, 1, 2]
print mergesort(data)
data = []
print mergesort(data)

0 件のコメント:
コメントを投稿