解決策は、1メガバイトと100万バイトの違いが原因でのみ可能です。重複を許可して重要でない順序で100万個の8桁の数字を選択するには、約2のべき乗8093729.5のさまざまな方法があります。そのため、100万バイトのRAMしかないマシンでは、すべての可能性を表すのに十分な状態がありません。ただし、1M(TCP / IPの場合は2k未満)は1022 * 1024 * 8 = 8372224ビットなので、解決策は可能です。
パート1、初期ソリューション
このアプローチには100万個以上が必要です。後で100万に収まるように調整します。
7ビットの数値のサブリストのシーケンスとして、0から99999999の範囲の数値のコンパクトなソートリストを保存します。最初のサブリストは0から127までの数を保持し、2番目のサブリストは128から255までの数を保持します。100000000/ 128は正確に781250なので、781250のようなサブリストが必要になります。
各サブリストは、2ビットのサブリストヘッダーとそれに続くサブリスト本文で構成されます。サブリストボディは、サブリストエントリごとに7ビットを占めます。サブリストはすべて連結されており、このフォーマットにより、1つのサブリストがどこで終了し、次のサブリストがどこで始まるかがわかります。完全に実装されたリストに必要なストレージの合計は、2 * 781250 + 7 * 1000000 = 8562500ビットであり、約1.021 Mバイトです。
4つの可能なサブリストヘッダー値は次のとおりです。
00空のサブリスト、後に続くものはありません。
01シングルトン、サブリストにはエントリが1つだけあり、次の7ビットがそれを保持します。
10サブリストには、少なくとも2つの異なる番号が含まれます。エントリは、最後のエントリが最初のエントリ以下である場合を除いて、降順で格納されます。これにより、サブリストの終わりを識別できます。たとえば、数値2,4,6は(4,6,2)として格納されます。数値2、2、3、4、4は(2、3、4、4、2)として格納されます。
11サブリストは、1つの数値の2回以上の繰り返しを保持します。次の7ビットは数を示します。次に、値1の0個以上の7ビットエントリが続き、その後に値0の7ビットエントリが続きます。サブリストボディの長さによって、繰り返しの数が決まります。たとえば、数値12,12は(12,0)として格納され、数値12,12,12は(12,1,0)として格納され、数値12,12,12,12は(12,1 、1、0)など。
私は空のリストから始めて、大量の数値を読み込んで32ビット整数として保存し、新しい数値を(おそらくヒープソートを使用して)並べ替えてから、新しいコンパクトな並べ替えリストにマージします。読み取る数値がなくなるまで繰り返し、コンパクトリストをもう一度歩いて出力を生成します。
以下の行は、リストマージ操作の開始直前のメモリを表しています。「O」は、ソートされた32ビット整数を保持する領域です。「X」は、古いコンパクトリストを保持する領域です。「=」記号はコンパクトリストの拡張の余地であり、「O」の各整数に7ビットです。「Z」はその他のランダムなオーバーヘッドです。
ZZZOOOOOOOOOOOOOOOOOOOOOOOOOO==========XXXXXXXXXXXXXXXXXXXXXXXXXX
マージルーチンは、左端の「O」と左端の「X」から読み取りを開始し、左端の「=」から書き込みを開始します。すべての新しい整数がマージされるまで、書き込みポインターはコンパクトリスト読み取りポインターをキャッチしません。両方のポインターが、サブリストごとに2ビット、古いコンパクトリストの各エントリごとに7ビット進み、さらに十分な余地があるためです。新しい番号の7ビットのエントリ。
パート2、1Mに詰め込む
上記のソリューションを1Mに圧縮するには、コンパクトリスト形式をもう少しコンパクトにする必要があります。サブリストタイプの1つを取り除くので、可能なサブリストヘッダー値は3つだけになります。次に、「00」、「01」、および「1」をサブリストヘッダー値として使用して、数ビットを節約できます。サブリストのタイプは次のとおりです。
空のサブリスト。後に何も続きません。
Bシングルトン。サブリストにはエントリが1つしかなく、次の7ビットがそれを保持します。
Cサブリストは、少なくとも2つの異なる番号を保持します。エントリは、最後のエントリが最初のエントリ以下であることを除いて、降順ではなく保存されます。これにより、サブリストの終わりを識別できます。たとえば、数値2,4,6は(4,6,2)として格納されます。数値2、2、3、4、4は(2、3、4、4、2)として格納されます。
Dサブリストは、1つの数値の2回以上の繰り返しで構成されます。
私の3つのサブリストヘッダー値は「A」、「B」、「C」になるため、Dタイプのサブリストを表す方法が必要です。
「C [17] [101] [58]」のように、Cタイプのサブリストヘッダーの後に3つのエントリが続くとします。3番目のエントリは2番目のエントリよりも小さいが、最初のエントリよりも大きいため、これは上記の有効なCタイプのサブリストの一部にすることはできません。このタイプの構成を使用して、Dタイプのサブリストを表すことができます。ビットで言えば、「C {00 ?????} {1 ??????} {01 ?????}」がどこにあっても、それは不可能なCタイプのサブリストです。これを使用して、1つの数値の3回以上の繰り返しで構成されるサブリストを表します。最初の2つの7ビットワードは数値(以下の「N」ビット)をエンコードし、その後に0個以上の{0100001}ワードが続き、その後に{0100000}ワードが続きます。
For example, 3 repetitions: "C{00NNNNN}{1NN0000}{0100000}", 4 repetitions: "C{00NNNNN}{1NN0000}{0100001}{0100000}", and so on.
それはちょうど単一の数の正確に2回の繰り返しを保持するリストを残します。別の不可能なCタイプのサブリストパターン「C {0 ??????} {11 ?????} {10 ?????}」でそれらを表現します。最初の2ワードには7ビットの数値を収める十分な余地がありますが、このパターンはそれが表すサブリストよりも長いため、状況は少し複雑になります。末尾の5つの疑問符はパターンの一部ではないと考えることができるので、「C {0NNNNNN} {11N ????}} 10」をパターンとして使用し、繰り返される番号を「N 「2ビット長すぎます。
このパターンでは、2ビットを借りて、未使用の4ビットから払い戻す必要があります。読み取り時、「C {0NNNNNN} {11N00AB} 10」に遭遇すると、「N」の数値の2つのインスタンスを出力し、最後の「10」をビットAとBで上書きし、読み取りポインタを2だけ巻き戻します。ビット。このアルゴリズムでは、各コンパクトリストが1回しかウォークされないため、破壊的な読み取りは問題ありません。
1つの数値の2回の繰り返しのサブリストを書き込むときは、「C {0NNNNNN} 11N00」と書き込み、借用ビットカウンターを2に設定します。借用ビットカウンターがゼロ以外の書き込みごとに、書き込まれるビットごとにデクリメントされ、カウンタがゼロになると「10」が書き込まれます。したがって、書き込まれた次の2ビットはスロットAとBに入れられ、「10」は最後にドロップされます。
「00」、「01」、「1」で表される3つのサブリストヘッダー値により、最も人気のあるサブリストタイプに「1」を割り当てることができます。サブリストヘッダーの値をサブリストタイプにマップするには小さなテーブルが必要です。また、各サブリストタイプのオカレンスカウンターが必要です。これにより、最良のサブリストヘッダーマッピングがわかるようになります。
完全に入力されたコンパクトリストの最悪の場合の最小限の表現は、すべてのサブリストタイプが同等に人気がある場合に発生します。その場合、3つのサブリストヘッダーごとに1ビットを保存するので、リストのサイズは2 * 781250 + 7 * 1000000-781250/3 = 8302083.3ビットです。32ビットのワード境界、つまり8302112ビット、または1037764バイトに切り上げます。
1MからTCP / IP状態およびバッファーの2kを引いた値は1022 * 1024 = 1046528バイトで、残りの8764バイトが残ります。
しかし、サブリストヘッダーマッピングを変更するプロセスについてはどうでしょうか。以下のメモリマップでは、「Z」はランダムオーバーヘッド、「=」は空き領域、「X」はコンパクトリストです。
ZZZ=====XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
一番左の「X」から読み始め、一番左の「=」から書き始めて、正しく作業します。完了すると、コンパクトなリストは少し短くなり、メモリの終わりが間違っています。
ZZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=======
だから私はそれを右にシャントする必要があります:
ZZZ=======XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
ヘッダーマッピング変更プロセスでは、サブリストヘッダーの最大1/3が1ビットから2ビットに変更されます。最悪の場合、これらはすべてリストの先頭にあるため、開始する前に少なくとも781250/3ビットの空きストレージが必要です。これにより、以前のバージョンのコンパクトリストのメモリ要件に戻ります。 (
それを回避するために、781250サブリストをそれぞれ78125サブリストの10個のサブリストグループに分割します。各グループには、独自の独立したサブリストヘッダーマッピングがあります。グループにA〜Jの文字を使用します。
ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
各サブリストグループは、サブリストヘッダーマッピングの変更中に縮小または変更されません。
ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAA=====BBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABB=====CCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCC======DDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDD======EEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEE======FFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFF======GGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGG=======HHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHH=======IJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHI=======JJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ=======
ZZZ=======AAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
マッピング変更中のサブリストグループの最悪の場合の一時的な拡張は、4k未満で78125/3 = 26042ビットです。完全に入力されたコンパクトリストに4kプラス1037764バイトを許可すると、メモリマップの "Z"に8764-4096 = 4668バイトが残ります。
これは、10個のサブリストヘッダーマッピングテーブル、30個のサブリストヘッダーオカレンスカウント、その他の必要なカウンター、ポインター、小さなバッファー、および関数呼び出しの戻りアドレスのスタックスペースなど、気付かずに使用したスペースには十分なはずです。ローカル変数。
パート3、実行にどのくらいかかりますか?
空のコンパクトリストでは、1ビットのリストヘッダーが空のサブリストに使用され、リストの開始サイズは781250ビットになります。最悪の場合、リストは追加される数値ごとに8ビット増加するため、32ビットの数値のそれぞれをリストバッファーの上部に配置し、並べ替えてマージするには、32 + 8 = 40ビットの空き領域が必要です。最悪の場合、サブリストヘッダーマッピングを変更すると、2 * 781250 + 7 * entries-781250/3ビットのスペース使用になります。
リストに少なくとも800000個の数値が存在する場合、5回のマージごとにサブリストヘッダーマッピングを変更するポリシーでは、最悪の場合、合計約30Mのコンパクトリストの読み取りおよび書き込みアクティビティが発生します。
ソース:
http://nick.cleaton.net/ramsortsol.html