O(N)インプレースアルゴリズムが可能です。すべての文字を配列の先頭に、すべての数字を最後に並べ替えることから始めることができます。次に、2x(N / 2)行列をインプレース転置して、配列の前半から偶数の位置にすべての要素を配置し、他の要素を奇数の位置に配置します。この質問は、この転置を行う方法を説明しています(実際には、そこで説明されているアルゴリズムは、逆に適用する必要があります)。
最も難しい部分は並べ替えです。安定していて、所定の位置にある必要があります。
O(N 2)時間でソートするのは簡単です。挿入ソートまたはバブルソートがそれを行います。
O(N log N)時間はもっと複雑です。この回答で説明されている、安定したインプレースマージソートを使用します。この回答は、O( N )アルゴリズムを構成するために使用できるアイデアを説明するリンクのペアを提供します。これは、安定したインプレースマージソートよりも単純ですが、それでもかなり複雑なので、スケッチのみを示します。
アレイを2つの領域に分割します。それらの1つは、アルゴリズムの他の部分に必要なデータを一時的に格納するために使用されます(この領域には引き続き配列要素が格納されるため、追加情報はこれらの要素の順序でエンコードされます)。その他の領域は正方行列として解釈され、最初に行、次に列の順に要素を並べ替える必要があります。両方の領域がソートされた後、転置アルゴリズムがそれぞれに適用され、文字と数字が適切な場所に配置されます。
1.アレイの一部から一時ストレージを作成する
配列のほとんどの要素は、サイズKの正方行列として編成されます。Kは最大の偶数であり、 2 * 8 * K + K2はNより大きくありません。一時ストレージのサイズM = N - K 2 、これは2 * 8 * sqrt( N )にほぼ等しくなります。
一時ストレージ用にM個の要素を準備するには、配列の最初のM個の要素をスキャンして、この範囲で最も少なく表されている要素の種類を判別します。たとえば、「数字」とします。サイズM /2のグループに数字をパックします。これを行うには、次の例に示すように、文字と数字のブロックを繰り返し交換します。
ddaaaddddadddaaaaad
aaaDDddddadddaaaaad
aaaaDDDDDDdddaaaaad
aaaaaaaaaDDDDDDDDDd
1つのグループに少なくともM /2桁が収集されたら停止します。ブロック交換手順(インプレースサブアレイローテーションとしても示される場合があります)は、この回答の最初のアルゴリズムとして、または次のように実装できます。
- 数字の逆ブロック
- 数字の逆ブロック
- 両方のブロックを一緒に反転します
例:
123abcd
321abcd
321dcba
abcd123
サイズM /2のグループに数字をパックするには、最大(M / 2)2桁、最大N / 2文字移動する必要があるため、これはO(N)時間で実行できます。
正確には、O(N log 2 U)時間です。ASCII文字を並べ替えても問題ありませんが、代わりに他の種類の値を並べ替える必要がある場合は大きすぎます(Uは値セットのサイズです)。実行時間をO(N * log U * log log U )に改善するには、サイズ2 * Kの小さな一時ストレージから開始し、それを使用してlog U範囲を並べ替えてから、これらの範囲をログのブロック交換手順とペアごとにマージします適切なサイズの一時ストレージを取得するためのUステップをログに記録します。
この時点で、M / 2サイズの数字のブロックがあり、その前に文字のブロック(おそらくより大きなサイズ)があります。ブロック交換手順を使用して、同じサイズの2つのブロックM /2を作成します。
データのビットを一時ストレージに格納するために、位置iおよびi + M /2で要素を交換する場合があります。位置iが文字を格納し、位置i + M / 2が数字を格納する場合、ゼロビットになります。数字が最初に来る場合、ゼロ以外のビットがあります。8つの連続するビットが1つの文字をエンコードします。つまり、一時ストレージに最大K文字を記憶できます。
一時ストレージにビットを格納する他の代替手段は、その「数」の半分です。各数値は、「0」..「9」(ビット0を意味する)または「a」..「j」(ビット1を意味する)のいずれかとして格納できます。可能な文字数が桁数の3倍以上であれば、各値に2ビットを格納できます。各値から4ビットを絞り出すために、2桁ごとに1バイトにパックする場合があります。これにより、M /4の空きバイトが得られます。したがって、Mは4* Kに減少する可能性があります。
2.行列の行の並べ替え
この時点から、追加メモリとして一時ストレージを使用して、アレイのメイン領域を再配置する必要があります。サイズ2* Kのサブ配列で最も少なく表されている要素の種類を決定します。たとえば、「数字」とします。配列を順番にスキャンし、数字を一時的なストレージに移動し、文字を連続した順序でパックします。K桁を収集すると、L文字が順番に表示され、L>= Kになります。最後のL%K文字を空いている領域の最後に移動し、空いている領域の残りの部分を一時ストレージからの数字で埋めます。次に、アレイを順番にスキャンし続けます。
このプロシージャは、配列の各要素を1回または2回だけコピーするため、完了するにはO(N)時間が必要です。最後に、文字/数字のすべてのブロックが整列され、マトリックスの各行に1種類の要素が含まれるようになります。
3.行列の列の並べ替え
これは、前の手順を簡略化したものです。マトリックス列ごとに、数字を一時ストレージに移動し、文字を連続した行にパックしてから、数字を一時ストレージから空いている行にコピーします。
この手順を完了するには、O(N)時間も必要です。最後に、配列のメイン領域が完全にソートされます。次に、一時ストレージをソートします(各「ビット」にゼロを書き込みます)。
4.配列の転置
残りの唯一のステップは、文字と数字が適切な場所に配置されるように、一時ストレージと配列のメイン領域の両方を転置することです。( K x K行列ではなく、両方のサブ配列から作成された2つの2 x J行列が、リンクされた回答の1つに記載されているアルゴリズムで置き換えられることに注意してください)。
この手順とアルゴリズム全体には、O(N)時間が必要です。
2番目のアルゴリズム。
ASCIIには10桁と52文字しか含まれていないため、次の方法でアルゴリズムを大幅に簡略化できます。
- BASE64エンコーディングを使用して値の最後の1/3をパックします。これにより、N / 12バイトの空き領域が提供されます(一時ストレージとして使用されます)。
- このスペースを一時的なストレージとして使用して、値の最初の1/3を並べ替えます。サイズN /6のサブ配列で最も少なく表されている要素の種類を決定します。たとえば、「数字」とします。配列を順番にスキャンし、数字を一時的なストレージに移動し、文字を連続した順序でパックします。次に、一時ストレージを空いているスペースにコピーします。次のN /6要素に対してこれを繰り返します。
- 値の最後の1/3を解凍し、値の最初の1/3をパックします。
- 値の最後の2/3を並べ替えます(N / 6要素を4回並べ替えます)。
- 値の最初の1/3を解凍します。
- この時点で、12グループの文字(可変サイズ)があります。ブロック交換手順を使用して、これらのグループの断片をそれらの間で移動し、適切な順序(文字、数字など)で同じサイズの12のグループを作成します。
- 6組のキャラクターのグループを転置します。実際、ここでは単純な転置アルゴリズムが可能です。マークされていないすべての要素(ビット7はゼロ)にサイクルリーダーアルゴリズムを適用し、要素が移動している間、そのビット7を1に設定します。完了したら、すべてのマークをクリアします。
3番目のアルゴリズム。
この問題を解決するための他のアプローチは、すべての文字を配列の先頭に、すべての数字を、このpdfで説明されているインプレースの安定した基数ソート(の一部の変更)でソートすることです。ソート後、前述の転置アルゴリズムを適用します。
ここで最も難しいのは、この基数ソートアルゴリズムの圧縮部分です。各値から1ビットを節約することはできません。代わりに、算術符号化を使用する必要があります。