Вот улучшение ответа aix . Рассмотрите возможность использования трех «слоев» для структуры данных: первый - это константа для первых пяти цифр (17 бит); Таким образом, с этого момента для каждого телефонного номера остались только оставшиеся пять цифр. Мы рассматриваем эти оставшиеся пять цифр как 17-битные двоичные целые числа и сохраняем k этих битов одним методом и 17 - k = m другим методом, определяя k в конце, чтобы минимизировать требуемое пространство.
Сначала мы сортируем телефонные номера (все сокращаются до 5 десятичных знаков). Затем мы подсчитываем, сколько телефонных номеров у которых двоичное число, состоящее из первых m битов, все равно 0, для скольких телефонных номеров первые m битов не более 0 ... 01, для скольких телефонных номеров первые m биты составляют не более 0 ... 10 и так далее, вплоть до количества телефонных номеров, для которых первые m битов равны 1 ... 11 - последнее количество равно 1000 (десятичное). Таких счетчиков 2 ^ m , и каждое из них не превышает 1000. Если мы опустим последний (потому что мы все равно знаем, что он равен 1000), мы можем сохранить все эти числа в непрерывном блоке (2 ^ m - 1) * 10 бит. (10 бит достаточно для хранения числа меньше 1024.)
Последние k бит всех (сокращенных) телефонных номеров непрерывно хранятся в памяти; поэтому, если k , скажем, 7, то первые 7 бит этого блока памяти (биты с 0 по 6) соответствуют последним 7 битам первого (сокращенного) телефонного номера, биты с 7 по 13 соответствуют последним 7 битам второго (сокращенного) номера телефона и т. д. Для этого требуется 1000 * k бит, всего 17 + (2 ^ (17 - k ) - 1) * 10 + 1000 * k , что достигает минимума 11287 для k = 10. Таким образом, мы можем хранить все телефонные номера в ceil ( 11287/8) = 1411 байт.
Дополнительное пространство можно сэкономить, заметив, что ни одно из наших чисел не может начинаться, например, с 1111111 (двоичное), потому что наименьшее число, которое начинается с этого, - 130048, а у нас только пять десятичных цифр. Это позволяет нам сократить количество записей в первом блоке памяти: вместо 2 ^ m - 1 counts нам нужен только ceil (99999/2 ^ k ). Это означает, что формула становится
17 + ceil (99999/2 ^ k ) * 10 + 1000 * k
что на удивление достигает своего минимума 10997 как для k = 9, так и для k = 10, или ceil (10997/8) = 1375 байт.
Если мы хотим узнать, есть ли в нашем наборе определенный телефонный номер, мы сначала проверяем, соответствуют ли первые пять двоичных цифр пяти сохраненным нами цифрам. Затем мы разбиваем оставшиеся пять цифр на его верхние m = 7 бит (что, скажем, m- битное число M ) и его младшие k = 10 бит (число K ). Теперь мы находим число a [M-1] сокращенных телефонных номеров, для которых первые m цифр не более M - 1, и число a [M] сокращенных телефонных номеров, для которых первые m цифр не более M , оба из первого блока бит. Теперь мы проверяем между a[М-1] й и [М] е последовательность K бит во втором блоке памяти , чтобы увидеть , если мы находим K ; в худшем случае таких последовательностей 1000, поэтому, если мы используем двоичный поиск, мы можем закончить за O (log 1000) операций.
Псевдокод для печати всех 1000 номеров следует, где доступ к К -му к -битный вводу первого блока памяти , как в [K] и М 'я м записи битовой второго блока памяти в качестве Ь [М] (в обоих случаях потребуется несколько битовых операций, которые утомительно записывать). Первые пять цифр входят в число c .
i := 0;
for K from 0 to ceil(99999 / 2^k) do
while i < a[K] do
print(c * 10^5 + K * 2^k + b[i]);
i := i + 1;
end do;
end do;
Возможно, что-то пойдет не так с граничным случаем для K = ceil (99999/2 ^ k ), но это достаточно легко исправить.
Наконец, с точки зрения энтропии, невозможно сохранить подмножество из 10 ^ 3 натуральных чисел меньше 10 ^ 5 в меньшем количестве, чем ceil (log [2] (binomial (10 ^ 5, 10 ^ 3)) ) = 8073. Включая 17 нужных нам для первых 5 цифр, все равно остается пробел в 10997 - 8090 = 2907 бит. Это интересная задача - увидеть, есть ли лучшие решения, позволяющие относительно эффективно получать доступ к номерам!