web-dev-qa-db-ja.com

K Closest Points to Originのソートがヒープより遅いのはなぜですか?

コーディングタスクは ここ です。

ヒープソリューション:

import heapq
class Solution:
    def kClosest(self, points: List[List[int]], K: int) -> List[List[int]]:
        return heapq.nsmallest(K, points, key = lambda P: P[0]**2 + P[1]**2)

並べ替えソリューション:

class Solution(object):
    def kClosest(self, points: List[List[int]], K: int) -> List[List[int]]:
        points.sort(key = lambda P: P[0]**2 + P[1]**2)
        return points[:K]

説明によると here 、Pythonのheapq.nsmallestはO(n log(t))、Python List.sort()はO(n log(n ))しかし、私の提出結果は、ソートがheapqよりも速いことを示しています。これはどのようにして起こったのですか?

17
Yan Yang

既に説明したように、高速実装のティムソートを使用したソートは、pythonでの1つの要因です。ここでのもう1つの要因は、ヒープの操作がマージソートや挿入ソートほどキャッシュに適していないことです。ティム・ソートはこれら2つのハイブリッドです)。

ヒープ操作は、離れたインデックスに格納されているデータにアクセスします。

Pythonは、0インデックスベースの配列を使用して、ヒープライブラリを実装します。したがって、k番目の値の場合、その子ノードのインデックスはk * 2 + 1およびk * 2 + 2です。

ヒープに要素を追加またはヒープから要素を削除した後、パーコレートアップ/ダウン操作を実行するたびに、現在のインデックスから離れた親/子ノードへのアクセスを試みます。これはキャッシュフレンドリーではありません。これは、ヒープのソートが両方とも漸近的に同じであるにもかかわらず、一般にクイックソートよりも遅い理由でもあります。

2
Lyn

Big-O表記の定義を選択しましょう Wikipediaから

Big O表記は、引数が特定の値または無限大に向かう傾向がある場合の関数の制限動作を説明する数学表記です。

...

コンピュータサイエンスでは、ビッグO表記を使用して、入力サイズが大きくなるにつれて実行時間またはスペース要件がどのように大きくなるかに従ってアルゴリズムを分類します。

したがって、Big-Oは次のようになります。

enter image description here

したがって、小さい範囲/数値で2つのアルゴリズムを比較する場合、Big-Oに強く依存することはできません。例を分析してみましょう:

2つのアルゴリズムがあります。1つ目はO(1)で、正確に10000ティックで動作し、2つ目はOです(n ^ 2)。したがって、1から100の範囲では、2番目の方が最初のものよりも速くなります(100^2 == 10000 so (x<100)^2 < 10000)。ただし、100から2番目のアルゴリズムは最初のアルゴリズムよりも遅くなります。

関数にも同様の動作があります。さまざまな入力長で時間を計り、タイミングプロットを作成しました。大きな数値での関数のタイミングを次に示します(黄色はsort、青色はheap)。

enter image description here

sortheapよりも多くの時間を消費しており、時間はheap'sよりも速く増加していることがわかります。しかし、より低い範囲を詳しく調べる場合:

enter image description here

小さい範囲ではsortheapより速いことがわかります! heapには「デフォルト」の時間消費があるようです。したがって、Big-Oがより悪いアルゴリズムがBig-Oがより良いアルゴリズムよりも速く機能することは間違いありません。これは、範囲の使用率が小さすぎて、より良いアルゴリズムがより悪いアルゴリズムよりも高速であることを意味します。

最初のプロットのタイミングコードは次のとおりです。

import timeit
import matplotlib.pyplot as plt

s = """
import heapq
def k_heap(points, K):
    return heapq.nsmallest(K, points, key = lambda P: P[0]**2 + P[1]**2)

def k_sort(points, K):
    points.sort(key = lambda P: P[0]**2 + P[1]**2)
    return points[:K]
"""

random.seed(1)
points = [(random.random(), random.random()) for _ in range(1000000)]
r = list(range(11, 500000, 50000))
heap_times = []
sort_times = []
for i in r:
    heap_times.append(timeit.timeit('k_heap({}, 10)'.format(points[:i]), setup=s, number=1))
    sort_times.append(timeit.timeit('k_sort({}, 10)'.format(points[:i]), setup=s, number=1))

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
#plt.plot(left, 0, marker='.')
plt.plot(r, heap_times, marker='o')
plt.plot(r, sort_times, marker='D')
plt.show()

2番目のプロットの場合、以下を置き換えます。

r = list(range(11, 500000, 50000))  -> r = list(range(11, 200))
plt.plot(r, heap_times, marker='o') -> plt.plot(r, heap_times)
plt.plot(r, sort_times, marker='D') -> plt.plot(r, sort_times)
26
vurmux