Решение возможно только из-за разницы между 1 мегабайтом и 1 миллионом байтов. Существует около 2-х вариантов для 8093729,5 различных способов выбора 1 миллиона 8-значных чисел с разрешенными дубликатами и упорядочением неважно, поэтому на машине с только 1 миллионом байтов ОЗУ недостаточно состояний, чтобы представить все возможности. Но 1М (меньше 2К для TCP / IP) составляет 1022 * 1024 * 8 = 8372224 бит, поэтому решение возможно.
Часть 1, первоначальное решение
Этот подход требует чуть больше 1М, я уточню его, чтобы вписать в 1М позже.
Я буду хранить компактный отсортированный список чисел в диапазоне от 0 до 99999999 как последовательность подсписков 7-битных чисел. Первый подсписок содержит числа от 0 до 127, второй подсписок содержит числа от 128 до 255 и т. Д. 100000000/128 - это точно 781250, поэтому потребуется 781250 таких подсписков.
Каждый подсписок состоит из 2-битного заголовка подсписка, за которым следует тело подсписка. Тело подсписка занимает 7 бит на запись подсписка. Все подсписки объединяются вместе, и формат позволяет определить, где заканчивается один подсписок и начинается следующий. Общий объем памяти, требуемый для полностью заполненного списка, составляет 2 * 781250 + 7 * 1000000 = 8562500 битов, что составляет около 1,021 МБ.
4 возможных значения заголовка подсписка:
00 Пустой подсписок, ничего не следует.
01 Singleton, в подсписке есть только одна запись, и следующие 7 бит содержат ее.
10 Подсписок содержит как минимум 2 разных числа. Записи хранятся в неубывающем порядке, за исключением того, что последняя запись меньше или равна первой. Это позволяет идентифицировать конец подсписка. Например, числа 2,4,6 будут храниться как (4,6,2). Числа 2,2,3,4,4 будут сохранены как (2,3,4,4,2).
11 Подсписок содержит 2 или более повторений одного числа. Следующие 7 бит дают номер. Затем идут ноль или более 7-битных записей со значением 1, за которыми следует 7-битная запись со значением 0. Длина тела подсписка определяет количество повторений. Например, числа 12,12 будут сохранены как (12,0), числа 12,12,12 будут сохранены как (12,1,0), 12,12,12,12 будут (12,1) 1,0) и так далее.
Я начинаю с пустого списка, читаю группу чисел и сохраняю их как 32-битные целые числа, сортирую новые числа на месте (возможно, используя heapsort), а затем объединяю их в новый компактный отсортированный список. Повторяйте до тех пор, пока не останется чисел для чтения, затем еще раз просмотрите компактный список, чтобы сгенерировать вывод.
Строка ниже представляет память непосредственно перед началом операции объединения списков. «O» - это область, в которой хранятся отсортированные 32-битные целые числа. «X» - это область, которая содержит старый компактный список. Знаки «=» - это комната расширения для компактного списка, 7 бит для каждого целого числа в «O». «Z» - это другие случайные издержки.
ZZZOOOOOOOOOOOOOOOOOOOOOOOOOO==========XXXXXXXXXXXXXXXXXXXXXXXXXX
Процедура слияния начинает чтение с самого левого «O» и с самого левого «X», и начинает писать с самого левого «=». Указатель записи не перехватывает указатель чтения компактного списка до тех пор, пока все новые целые числа не будут объединены, поскольку оба указателя передвигают 2 бита для каждого подсписка и 7 битов для каждой записи в старом компактном списке, и для этого достаточно места для 7-битные записи для новых номеров.
Часть 2, втискивая его в 1М
Чтобы сжать решение выше в 1M, мне нужно сделать формат компактного списка немного более компактным. Я избавлюсь от одного из типов подсписков, так что будет только 3 различных возможных значения заголовка подсписка. Затем я могу использовать «00», «01» и «1» в качестве значений заголовка подсписка и сохранить несколько битов. Типы подсписков:
Пустой подсписок, ничего не следует.
B Singleton, в подсписке есть только одна запись, и следующие 7 битов содержат ее.
C Подсписок содержит как минимум 2 разных числа. Записи хранятся в неубывающем порядке, за исключением того, что последняя запись меньше или равна первой. Это позволяет идентифицировать конец подсписка. Например, числа 2,4,6 будут храниться как (4,6,2). Числа 2,2,3,4,4 будут сохранены как (2,3,4,4,2).
D Подсписок состоит из 2 или более повторений одного номера.
Мои 3 значения заголовка подсписка будут «A», «B» и «C», поэтому мне нужен способ представления подсписков D-типа.
Предположим, у меня есть заголовок подсписка типа C, за которым следуют 3 записи, такие как «C [17] [101] [58]». Он не может быть частью действительного подсписка C-типа, как описано выше, поскольку третья запись меньше второй, но больше первой. Я могу использовать этот тип конструкции для представления подсписка D-типа. В двух словах, где бы у меня ни было "C {00 ?????} {1 ??????} {01 ?????}", это невозможный подсписок типа C. Я буду использовать это для представления подсписка, состоящего из 3 или более повторений одного числа. Первые два 7-битных слова кодируют число («N» битов ниже), за которым следуют ноль или более {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 ?????}». В первых двух словах достаточно места для 7 битов числа, но этот шаблон длиннее, чем представленный в нем подсписок, что несколько усложняет ситуацию. Пять знаков вопроса в конце можно считать не частью шаблона, поэтому я имею в качестве: «C {0NNNNNN} {11N ????} 10» в качестве шаблона, с числом, которое будет повторяться, сохраненным в «N» «S. Это 2 бита слишком долго.
Мне придется взять 2 бита и вернуть их из 4 неиспользованных битов в этом шаблоне. При чтении при обнаружении «C {0NNNNNN} {11N00AB} 10» выведите 2 экземпляра числа в «N», перезапишите «10» в конце битами A и B и перемотайте указатель чтения на 2 биты. Деструктивное чтение подходит для этого алгоритма, поскольку каждый компактный список просматривается только один раз.
При записи подсписка из 2 повторений одного числа запишите «C {0NNNNNN} 11N00» и установите счетчик заимствованных битов равным 2. При каждой записи, где счетчик заимствованных битов не равен нулю, он уменьшается для каждого записанного бита и «10» записывается, когда счетчик достигает нуля. Таким образом, следующие 2 записанных бита войдут в слоты A и B, а затем цифра «10» опустится в конец.
С 3 значениями заголовка подсписка, представленными «00», «01» и «1», я могу присвоить «1» наиболее популярному типу подсписка. Мне понадобится небольшая таблица для сопоставления значений заголовка подсписка с типами подсписка, и мне понадобится счетчик вхождений для каждого типа подсписка, чтобы я знал, каково лучшее отображение заголовка подсписка.
В худшем случае минимальное представление полностью заполненного компактного списка происходит, когда все типы подсписков одинаково популярны. В этом случае я сохраняю 1 бит для каждых 3 заголовков подсписка, поэтому размер списка составляет 2 * 781250 + 7 * 1000000 - 781250/3 = 8302083,3 бит. Округление до границы слова 32 бита, то есть 8302112 бит или 1037764 байта.
1M минус 2k для состояния TCP / IP и буферов составляет 1022 * 1024 = 1046528 байт, оставляя мне 8764 байта для игры.
Но как насчет процесса изменения отображения заголовка подсписка? На приведенной ниже карте памяти «Z» - это случайные издержки, «=» - свободное место, «X» - компактный список.
ZZZ=====XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Начните читать с самого левого «X» и начните писать с самого левого «=» и работайте направо. Когда это будет сделано, компактный список будет немного короче, и он будет не в том конце памяти:
ZZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=======
Тогда мне нужно будет шунтировать вправо:
ZZZ=======XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В процессе изменения отображения заголовка до 1/3 заголовков подсписка будет изменяться с 1-битного на 2-битный. В худшем случае все они будут в начале списка, поэтому мне потребуется как минимум 781250/3 бита свободного места перед запуском, что возвращает меня к требованиям к памяти предыдущей версии компактного списка: (
Чтобы обойти это, я разделю 781250 подсписков на 10 групп подсписков по 78125 подсписков в каждой. Каждая группа имеет свое собственное независимое отображение заголовка подсписка. Используя буквы от 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
Наихудшее временное расширение группы подсписков во время изменения отображения составляет 78125/3 = 26042 бит, меньше 4k. Если я позволю 4k плюс 1037764 байта для полностью заполненного компактного списка, то мне останется 8764 - 4096 = 4668 байтов для "Z" в карте памяти.
Этого должно быть достаточно для 10 таблиц отображения заголовков подсписков, 30 вхождений заголовков подсписков и других нескольких счетчиков, указателей и небольших буферов, которые мне понадобятся, и пространства, которое я использовал, не замечая, например, места в стеке для адресов возврата вызовов функций и локальные переменные.
Часть 3, сколько времени потребуется, чтобы бежать?
При пустом компактном списке 1-битный заголовок списка будет использоваться для пустого подсписка, а начальный размер списка будет 781250 битов. В худшем случае список увеличивается на 8 бит для каждого добавленного числа, поэтому 32 + 8 = 40 бит свободного места необходимы для каждого из 32-битных чисел, которые должны быть размещены в верхней части буфера списка, а затем отсортированы и объединены. В худшем случае изменение отображения заголовка подсписка приводит к использованию пространства в 2 * 781250 + 7 * записей - 781250/3 битов.
С политикой изменения сопоставления заголовка подсписка после каждого пятого слияния, когда в списке есть по крайней мере 800000 номеров, запуск наихудшего случая будет включать в себя около 30M операций чтения и записи компактного списка.
Источник:
http://nick.cleaton.net/ramsortsol.html