スライディングウィンドウの中央値を計算するための非自明なアルゴリズム


25

実行中の中央値を計算する必要があります:

  • 入力: nk、vector (x1,x2,,xn)

  • 出力: vector (y1,y2,,ynk+1)、ここでyiの中央値です(xi,xi+1,,xi+k1)

(近似による不正行為はありません。正確な解を求めます。要素xiは大きな整数です。)

サイズ探索木を維持する簡単なアルゴリズムがありkます。合計実行時間はO(nlogk)です。(ここで「検索ツリー」とは、対数時間での挿入、削除、および中央値クエリをサポートする効率的なデータ構造を指します。)

しかし、これは私には少し愚かなようです。中央値だけでなく、サイズkのすべてのウィンドウ内のすべての順序統計を効果的に学習します。さらに、特にkが大きい場合、これは実際にはあまり魅力的ではありません(大きな検索ツリーが遅くなる傾向があり、メモリ消費のオーバーヘッドが自明ではなく、キャッシュ効率が悪いことが多いなど)。kk

大幅に改善できることはありますか?

下限はありますか(たとえば、単純なアルゴリズムは比較モデルに漸近的に最適ですか)。


編集:デビッドエップスタインは、比較モデルのための素敵な下限を与えました!それにもかかわらず、些細なアルゴリズムよりも少し賢いことをすることは可能だろうか?

たとえば、これらの線に沿って何かを行うことができます。入力ベクトルをサイズ部分に分割します。各部分をソートします(各要素の元の位置を追跡します)。そして、区分的にソートされたベクトルを使用して、補助データ構造なしで実行中の中央値を効率的に見つけますか?もちろん、これはまだO n log kkですが、実際には、配列のソートは検索ツリーを維持するよりもはるかに高速になる傾向があります。O(nlogk)


編集2: Saeedは、検索ツリー操作よりもソートの方が速いと思う理由をいくつか見たいと思っていました。以下は、n = 10 8の非常に簡単なベンチマークです。k=107n=108

  • ≈8s:それぞれk個の要素を持つベクトルをソートn/kk
  • ≈10s:要素を持つベクトルの並べ替えn
  • ≈80s:サイズkのハッシュテーブルでの挿入と削除nk
  • ≈390s:サイズkのバランスの取れた検索ツリーでの挿入と削除nk

ハッシュテーブルは比較のためだけにあります。このアプリケーションでは直接使用しません。

要約すると、ソートとバランスの取れた検索ツリー操作のパフォーマンスにはほぼ50倍の差があります。そして、を増やすと事態はさらに悪化します。k

(技術的詳細:データ=ランダムな32ビット整数。コンピューター=典型的な最新のラップトップ。テストコードはC ++で記述され、標準ライブラリルーチン(std :: sort)とデータ構造(std :: multiset、std ::を使用) unsorted_multiset)。2つの異なるC ++コンパイラ(GCCおよびClang)、および標準ライブラリの2つの異なる実装(libstdc ++およびlibc ++)を使用しました。伝統的に、std :: multisetは高度に最適化された赤黒木として実装されています。)


1
を改善できるとは思わない。あなたは、ウィンドウを見ればその理由は、あるのx トンx t + k 1x t + kの数字を除外することはできませんnlogkxt,...,xt+k1は将来のウィンドウの中央値であることから。これは、少なくともkを保持する必要があることを意味しますxt+k2,...,xt+k1データ構造内の 2つの整数。ログ時間よりも短い時間で更新されないようです。k2
RB 14年

私にはあなたの些細なアルゴリズムがあると思われるないO N ログKが、私は何かを誤解していますか?このため、大きなkに問題があると思います。そうでなければ、対数係数は実用的なアプリケーションでは何もありません。また、このアルゴリズムには大きな隠れた定数はありません。O((nk)klogk)O(nlogk)k
サイード14年

@Saeed:単純なアルゴリズムでは、要素を1つずつ処理します。ステップでは、検索ツリーにx iを追加し、(i > kの場合)検索ツリーからx i kも削除します。これはnステップで、各ステップはO log k 時間かかります。ixii>kxiknO(logk)
ユッカスオメラ14年

つまり、カジュアルな検索ツリーではなく、バランスの取れた検索ツリーがあるということですか?
サイード14年

1
@Saeed:ベンチマークでは、中央値を見つけようとさえしなかったことに注意してください。私はやった挿入およびnは、サイズの探索木で削除をK、およびこれらの操作をすることが保証されているO ログK の時間を。ソートと比較して、検索ツリーの操作は実際には非常に遅いことを受け入れる必要があります。バランスのとれた検索ツリーに要素を追加することで機能するソートアルゴリズムを作成しようとすると、これは簡単にわかります。確かにO n log n 時間で機能しますが、実際にはとてつもなく遅くなり、また多くの時間を浪費しますメモリの。nnkO(logk)O(nlogn)
ユッカ・スオメラ

回答:


32

Here's a lower bound from sorting. Given an input set S of length n to be sorted, create an input to your running median problem consisting of n1 copies of a number smaller than the minimum of S, then S itself, then n1 copies of a number larger than the maximum of S, and set k=2n1. The running medians of this input are the same as the sorted order of S.

そのため、計算の比較モデルでは、時間が必要です。おそらく、入力が整数であり、整数ソートアルゴリズムを使用している場合は、さらに改善できます。Ω(nlogn)


6
この答えは、逆も同様かどうか疑問に思います:効率的な並べ替えアルゴリズムを考えると、効率的な実行中位アルゴリズムを取得できますか?(たとえば、効率的な整数並べ替えアルゴリズムでは、整数の効率的な実行中央値アルゴリズムを意味しますか?または、IO効率的な並べ替えアルゴリズムは、IO効率的な実行中央値アルゴリズムを提供しますか?)
Jukka Suomela

1
もう一度、あなたの答えに感謝します。それは本当に正しい道を歩み、ソートベースのメディアンフィルターアルゴリズムにインスピレーションを与えました!最終的に、1991年の論文で、ここで述べたものと基本的に同じ議論を示したものを見つけることができました。参照を参照してください。[6]と[9]はこちら
ユッカスオメラ14年

9

編集:このアルゴリズムは現在ここに表示されます:http//arxiv.org/abs/1406.1717


はい、この問題を解決するには、次の操作を実行するだけで十分です。

  • それぞれがk個の要素を持つベクトルを並べ替えます。n/kk
  • 線形時間の後処理を行います。

大まかに言うと、アイデアは次のとおりです。

  • 入力の2つの隣接するブロックbを考えます。両方ともk個の要素があります。要素があること聞かせて12KB 1B 2入力ベクトルxの出現順にb kabka1,a2,...,akb1,b2,...,bkxます。
  • これらのブロックを並べ替えて、ブロック内の各要素のランクを学習します。
  • Augment the vectors a and b with predecessor/successor pointers so that by following the pointer chains we can traverse the elements in an increasing order. This way we have constructed doubly linked lists a and b.
  • One by one, delete all elements from the linked list b, in the reverse order of appearance bk,bk1,...,b1. Whenever we delete an element, remember what was its successor & predecessor at the time of deletion.
  • Now maintain "median pointers" p and q that point to lists a and b, respectively. Initialise p to the midpoint of a, and initialise q to the tail of the empty list b.
  • For each i:

    • Delete ai from list a (this is O(1) time, just delete from the linked list). Compare ai with the element pointed by p to see if we deleted before or after p.
    • Put bi back to list b in its original position (this is O(1) time, we memorised the predecessor and successor of bi). Compare bi with the element pointed by q to see if we added the element before or after q.
    • Update pointers p and q so that the median of the joined list ab is either at p or at q. (This is O(1) time, just follow the linked lists one or two steps to fix everything. We will keep track of how many items are before/after p and q in each list, and we will maintain the invariant that both p and q point to elements that are as close to the median as possible.)

The linked lists are just k-element arrays of indexes, so they are lightweight (except that the locality of memory access is poor).


Here is a sample implementation and benchmarks:

Here is a plot of running times (for n2106):

  • Blue = sorting + post-processing, O(nlogk).
  • Green = maintain two heaps, O(nlogk), implementation from https://github.com/craffel/median-filter
  • Red = maintain two search trees, O(nlogk).
  • Black = maintain a sorted vector, O(nk).
  • X axis = window size (k/2).
  • Y axis = running time in seconds.
  • Data = 32-bit integers and random 64-bit integers, from various distributions.

running times


3

Given David's bound it's unlikely you can do better worst case, but there are better output sensitive algorithms. Specifically, if m in the number of medians in the result, we can solve the problem in time O(nlogm+mlogn).

O(logm) time. If the new counts indicate that the median is in one of the Fibonacci heaps, it takes an additional O(logn) to pull the new median out. This O(logn) charge occurs only once per median.

If there was a clean way to delete elements without damaging the nice Fibonacci heap complexity, we'd get down to O(nlogm+mlogk), but I'm not sure if this is possible.


Oops, this doesn't work as written, since if you don't delete elements the counts won't reflect the new window. I'm not sure if it can be fixed, but I will leave the answer in case there is a way.
Geoffrey Irving

So I think this algorithm may in fact take O(nlogm) if you delete nodes from the Fibonacci heaps, since the Fibonacci heap depth increases only when delete-min is called. Does anyone know nice bounds on Fibonacci heap complexity taking the number of delete-min calls into account?
Geoffrey Irving

side note: Question is not clear, underling data structure is not defined, we just know something very vague. how do you want improve something that you don't know what it is? how do you want compare your approach?
Saeed

1
I apologize for the incomplete work. I've asked the concrete question needed to fix this answer here: cstheory.stackexchange.com/questions/21778/…. If you think it's appropriate I can remove this answer until the secondary question is resolved.
Geoffrey Irving
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.