0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

パーティションベースの選択アルゴリズム ~売上トップ100をO(n)で求めよう~

Last updated at Posted at 2022-03-08

※ディスクレーマー:$O(n\log{n})$で問題ない場合も、たくさんあると思います。

(↑計算量の一覧。画像元はこちら

はじめに

素朴にソートすると$O(n\log{n})$な問題が、$O(n)$で求まるアルゴリズムについて解説する。
「パーティションベースの選択アルゴリズム」という。

例えば以下のような問題だ。

問題

数列からk 個の最大要素を求めよ。
(つまり、最大値~k番目に大きいものまでを求めよ)
※ただし、要素がソートされている必要はない

【例】

n=6, k=3
入力:[3,4,6,5,1,2]
出力:[4,6,5]

→上位3位までに入る数列を返す(ソートの必要なし)

解法0:素朴すぎる解法

大きい順のランキングを作ってから、k番目までを返す。
大きい順に並び変える際に、

  • 1番大きいもの...
  • 2番目に大きいもの...
  • 3番目...

と探していく(「選択ソート」)
これにかかる計算量は$O(n^2)$である。これでは少し遅い。

解法1:素朴な解法

上のカイゼン。
大きい順のランキングを作ってから、k番目までを返す。

大きい順に並び変える際に、「マージソート」を利用する。

(↑マージソートのイメージ。画像元はこちら

これにかかる計算量は$O(n\log{n})$である。

十分に思えるが、さらに早くするにはどうしたらいいか?

解法2:ちょっと早い解法

この解法に至るヒントは
「全てソートする必要はない(大きいk個が分かればいい)」
ということである

「優先度付きキュー」を利用する。

(↑優先度付きキューのイメージ。画像元はこちら

まず、配列から適当なk個を優先度付きキューに入れる(この時点でk個をソートし、最小値が先頭になるようにする)
以下の操作を繰り返す。

元の配列の、残りの要素のうち1つを見て
- キューの最小値より小さければ何もしない
- 最小値より大きければ、キューの最小値を削除し、この要素をキューにソートして挿入

この挿入にはO(logk)かかる

操作の繰り返しは n 回程度発生するので、
全体にかかる計算量は$O(n\log{k})$である。

解法3:最適な解法

上の解法では、k個については並べ替えが発生してしまっており、$O(\log{k})$かかっている。
問題ではTOPが分かればいいのであって、TOPをランキング化する必要はない。
このランキング化をどうにか辞め、全体の計算量を$O(n)$にできないか考えたい。

そこで、「パーティションベースの選択アルゴリズム」の登場。
これは、「クイックソート」にかなり似ているアルゴリズムだ。

3-1. まず、一番左の値を基準(ピボット)として、左側に[その値未満の群]と[その値以上の群]に分かれるようにする

例えば冒頭の例では
基準:3(一番左の値)なので
[3,4,6,5,1,2]

[3未満の群]-[3以上の群]
に分解したい。その際に以下のような手順を踏む

3-2. 左端から見てピボット以上の値と、右端から見てピボット未満の値の場所を交換する

[3,4,6,5,1,2]

[3,4,6,5,1,2]
3と2、4と1が交換され
[2,1,6,5,4,3]
となる。群で言うと(2,1)-(6,5,4,3)になっているイメージだ。(3-1で述べたように左側に[その値未満の群]と[その値以上の群]に分かれるようになっている!)

3-3. この操作を再帰的に繰り返す。ただし、右からk番目までの要素が入っている群だけ繰り返せばよい

クイックソートでは
[2,1]
に対しても3-2と同じことをして
[1,2]とするのだが、このソートは必要ない。

なぜなら、冒頭の問題ではk=3であり、3番目までに大きいモノを返せばいいからだ。
右側の[6,5,4,3]の群が3個以上ある時点で、[2,1]の要素がトップ3に入ってくる可能性は
ゼロなのである。

したがって、
[6,5,4,3]についてだけ3-2と同じ操作を繰り返すと
今度は、基準:6(一番右の値)
なので、3と6が交換され
[3,5,4,6]
となる。群で言うと(2,1)-(3,5,4)-(6)になっているイメージだ。
真ん中の群について繰り返すと
[3,5,4][3,5,4]のままだ。この場合(3)-(5,4)に分解する。

3-4. 右から「ちょうど」k番目の要素まで分解出来たら、そこで終わり。

今回は(2,1)-(3)-(5,4)-(6)となっているので、[5,4,6]を返せばよい

まとめ

クイックソートに近いが、「パーティションベースの選択アルゴリズム」の場合「いらない場所はソートしない」と切り捨てることができるので、平均$O(n)$で計算することが可能なのだ!

参考

「選択アルゴリズム」と「中央値の中央値」
http://www.flint.jp/blog/?entry=109

中央値を線形時間で選択するアルゴリズムについて
https://techblog.nhn-techorus.com/archives/15289

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?