web-dev-qa-db-ja.com

ほぼ並べ替えられた配列の並べ替え(要素の配置がkを超えない)

私は最近このインタビューの質問をされました:

N要素のそれぞれが、正しい並べ替え順序からk以下の位置だけ誤配置される可能性があるという点で、ほとんど並べ替えられた配列が与えられます。空間と時間の効率的なアルゴリズムを見つけて、配列をソートします。

次のようなO(N log k)ソリューションがあります。

インデックス_arr[0..n)_(包括的)からN(排他的)までの配列の要素を意味するように_0_を示しましょう。

  • ソート_arr[0..2k)_
    • これで、_arr[0..k)_が最終的なソート位置にあることがわかりました...
    • ...しかし、arr[k..2k)_はkによって置き忘れられる可能性があります!
  • ソート_arr[k..3k)_
    • これで、_arr[k..2k)_が最終的なソート位置にあることがわかりました...
    • ...しかし_arr[2k..3k)_は依然としてkによって置き忘れられる可能性があります
  • 並べ替え_arr[2k..4k)_
  • ....
  • _arr[ik..N)_をソートするまで、これで完了です!
    • 残りの_2k_要素より少ない場合、この最終ステップは他のステップよりも安くなる可能性があります

各ステップでは、O(k log k)内の最大_2k_要素をソートし、各ステップの最後の最終ソート位置に少なくともk要素を配置します。 O(N/k)ステップがあるため、全体的な複雑さはO(N log k)です。

私の質問は:

  • O(N log k)は最適ですか?これは改善できますか?
  • 同じ要素を(部分的に)再ソートせずにこれを実行できますか?
63

Bob Sedgewick が彼の博士論文(および後続)で示したように、挿入ソートはcrushes「ほぼソートされた配列「。この場合、あなたの漸近はよく見えますが、k <12であれば、挿入ソートが毎回勝つことに賭けます。 why挿入ソートがうまく機能するのに良い説明があることはわかりませんが、見る場所はSedgewickの教科書Algorithms(彼はさまざまな言語の多くのエディションを作成しました)。

  • O(N log k)が最適であるかどうかはわかりませんが、それ以上のことは気にしません。kが小さい場合、それは重要な一定の要因であり、kが大きい場合は、配列をソートします。

  • 挿入ソートは、同じ要素を再ソートせずにこの問題を解決します。

Big-O表記はすべてアルゴリズムクラスに非常に適していますが、現実の世界では定数が重要です。これを見失うのは簡単すぎます。 (そして、私はこれをBig-O記法を教えた教授として言っています!)

36
Norman Ramsey

比較モデルのみを使用する場合、O(n log k)が最適です。 k = nの場合を考えます。

他の質問に答えるには、はい、ヒープを使用することで、ソートせずにこれを行うことができます。

2k要素の最小ヒープを使用します。最初に2k個の要素を挿入してから、minを削除し、次の要素を挿入するなど。

これにより、O(n log k)時間とO(k)スペースおよびヒープには通常、十分に小さい隠し定数があります。

19
Aryabhatta

漸近的に最適なソリューションの1つが最小ヒープを使用することが既に指摘されており、Javaでコードを提供したかっただけです。

public void sortNearlySorted(int[] nums, int k) {
  PriorityQueue<Integer> minHeap = new PriorityQueue<>();
  for (int i = 0; i < k; i++) {
    minHeap.add(nums[i]);
  }

  for (int i = 0; i < nums.length; i++) {
    if (i + k < nums.length) {
      minHeap.add(nums[i + k]);
    }
    nums[i] = minHeap.remove();
  }
}
8
Ivaylo Toskov

kが十分に大きい場合、あなたのソリューションは良いものです。時間の複雑さという点では、これ以上の解決策はありません。各要素はkの場所によってずれている可能性があります。つまり、正しく配置するには_log2 k_ビットの情報を学習する必要があります。つまり、少なくとも_log2 k_比較を行う必要があります。 -そのため、少なくともO(N log k)の複雑さが必要です。

しかし、他の人が指摘しているように、kが小さい場合、定数項はあなたを殺します。その場合、挿入ソートなど、操作ごとに非常に高速なものを使用します。

本当に最適にしたい場合は、両方のメソッドを実装し、kに応じて一方から他方に切り替えます。

7
Rex Kerr

kは明らかにかなり小さいはずなので、挿入ソートはおそらく最も明白で一般的に受け入れられているアルゴリズムです。

ランダム要素の挿入ソートでは、N個の要素をスキャンする必要があり、各要素を平均N/2の位置に移動して、合計で〜N * N/2の操作を行う必要があります。 「/ 2」定数は、big-O(または同様の)特性評価では無視され、O(N2)複雑さ。

提案している場合、予想される操作の数は〜N * K/2です。ただし、kは定数なので、k/2用語はbig-O特性評価では無視されるため、全体的な複雑さはO(N)です。

7
Jerry Coffin