データセットを取得し、各要素に署名を付けて並べ替えます。署名には、並べ替えによって、重複する可能性のある要素がグループ化されるというプロパティがあります。data_set[j] を data_set[j+1 ...] のアイテムと比較するとき、[j+1 ...] の最初の署名が重複チェックに失敗すると、i を進めます。この「隣接基準」により、これ以上調べる必要がないことが保証されます。これを超える要素は重複できません。
これにより、O(N^2) の比較が大幅に削減されます。どのくらいアルゴリズム アナリストに決定させるかを決定しますが、以下のコードは、単純な O(N^2) の 100m ではなく、約 400k の比較を行います。
署名は、要素をバケット化することから始まります。数値の範囲を N 個の等しいサイズのバケットに分割します: 1..k、k+1..2k、2k+1..3k、...特定のバケット。これにより、フォーム (0,0,0,1,3,0,0,...4,2) の初期署名が生成されます。
署名には、次のプロパティがあります。
sum(min(sig_a[i], sig_b[i]) for i in range(10)) >= 5
その場合、署名に関連付けられた要素に少なくとも 5 つの重複がある可能性があります。さらに、上記が成り立たない場合、要素に 5 つの重複を含めることはできません。これを「署名一致基準」と呼びましょう。
ただし、上記の署名による並べ替えには、上記の adjacency プロパティがありません。ただし、署名を 2 要素形式に変更すると、次のようになります。
(sum(sig[:-1]), sig[-1])
その場合、「署名一致基準」が保持されます。しかし、隣接基準は成り立つでしょうか?はい。その署名の合計は 10 です。列挙すると、次の可能な署名があります。
(0,10) (1, 9) (2, 8) (3, 7) (4, 6) (5, 5) (6, 4) (7, 3) (8, 2) (9, 1) (10,0)
(0,10) を (1,9) .. (10,0) と比較すると、署名テストが失敗すると、二度と真にならないことに注意してください。隣接基準が成り立ちます。さらに、その隣接基準は、「5」だけでなく、すべての正の値に適用されます。
それはいいことですが、署名を 2 つの大きなバケットに分割しても、必ずしも O(N^2) 検索が減るとは限りません。署名は過度に一般的です。sig[:-1] の署名を作成することで、この問題を解決します。
(sum(sig[:-1]), sig[-1]), (sum(sig[:-2]), sig[-2]), ...
等々。この署名はまだ隣接性を満たしていると思いますが、間違っている可能性があります。
私がしなかったいくつかの最適化があります: 署名は最初の値ではなく、各タプルの最後の値のみを必要としますが、並べ替えの手順を修正する必要があります。また、シグネチャの比較は、それ以上のスキャンが成功しないことが明らかになったときに早期に失敗して最適化される可能性があります。
# python 3.0
import random
# M number of elements, N size of each element
M = 10000
N = 10
# Bounds on the size of an element of each set
Vmin,Vmax = 0, (1 << 12)
# DupCount is number of identical numbers required for a duplicate
DupCount = 5
# R random number generator, same sequence each time through
R = random.Random()
R.seed(42)
# Create a data set of roughly the correct size
data_set = [list(s) for s in (set(R.randint(Vmin, Vmax) for n in range(N)) for m in range(M)) if len(s) == N]
# Adorn the data_set signatures and sort
def signature(element, width, n):
"Return a signature for the element"
def pearl(l, s):
def accrete(l, s, last, out):
if last == 0:
return out
r = l[last]
return accrete(l, s-r, last-1, out+[(s-r,r)])
return accrete(l, s, len(l)-1, [])
l = (n+1) * [0]
for i in element:
l[i // width] += 1
return pearl(l, len(element))
# O(n lg(n)) - with only 10k elements, lg(n) is a little over 13
adorned_data_set = sorted([signature(element, (Vmax-Vmin+1)//12, 12), element] for element in data_set)
# Count the number of possible intersections
def compare_signatures(sig_a, sig_b, n=DupCount):
"Return true if the signatures are compatible"
for ((head_a, tail_a), (head_b, tail_b)) in zip(sig_a, sig_b):
n -= min(tail_a, tail_b)
if n <= 0:
return True
return False
k = n = 0
for i, (sig_a, element_a) in enumerate(adorned_data_set):
if not element_a:
continue
for j in range(i+1, len(adorned_data_set)):
sig_b, element_b = adorned_data_set[j]
if not element_b:
continue
k += 1
if compare_signatures(sig_a, sig_b):
# here element_a and element_b would be compared for equality
# and the duplicate removed by adorned_data_set[j][1] = []
n += 1
else:
break
print("maximum of %d out of %d comparisons required" % (n,k))