Я читаю книгу по программированию жемчуга.
Вопрос: учитывая последовательный файл, который содержит не более четырех миллиардов
32-разрядные целые числа в случайном порядке, найдите 32-разрядное целое число, которое не находится в
файл (и там должен быть хотя бы один отсутствует). Эта проблема должна
решить, если у нас есть несколько сотен байтов основной памяти и несколько
последовательные файлы.Решение: чтобы настроить это как бинарный поиск, мы должны определить диапазон,
представление для элементов в пределах диапазона, и исследование
метод, чтобы определить, какая половина диапазона содержит отсутствующее целое число.
как нам это сделать?Мы будем использовать в качестве диапазона последовательность целых чисел, которые, как известно, содержат по крайней мере
один отсутствующий элемент, и мы представим диапазон с помощью файла
содержащий все целые числа в нем. Понимание того, что мы можем исследовать
диапазон путем подсчета элементов выше и ниже его средней точки: либо
верхний или нижний диапазон имеет максимум половину элементов в общем
спектр. Поскольку в общем диапазоне отсутствует элемент, меньшая половина
также должен иметь элемент затуманивания. Это большинство ингредиентов
бинарный алгоритм поиска для вышеуказанной задачи.
Над текстом авторское право Джона Бентли из книги по программированию жемчужин.
Некоторая информация предоставляется по следующей ссылке
"Программирование Жемчуг" бинарный поиск
Как мы ищем проходы, используя бинарный поиск, а также не следуя примеру, приведенному в ссылке выше? Пожалуйста, помогите мне понять логику всего с 5 целыми числами, а не миллионами, чтобы понять логику.
Почему бы вам не перечитать ответ в посте "Программирование Жемчуг" бинарный поиск. Это объясняет процесс на 5 целых чисел, как вы просите.
Идея состоит в том, что вы анализируете каждый список и разбиваете его на 2 (отсюда бинарная часть) отдельные списки на основе значения в первом бите.
То есть показывает двоичное представление фактических чисел
Исходный список «»: 001, 010, 110, 000, 100, 011, 101 => (взломан)
(удаляем первый бит и добавляем его к «имени» нового списка)
Для формирования каждого из приведенных ниже списков мы взяли значения, начинающиеся с [0 или 1] из списка выше.
Список «0«: 01, 10, 00, 11 (формируется из подмножества 001, 010, 000, 011 Списка», удаляя первый бит и добавляя его к «имени» нового списка)
Список «1«: 10, 00, 01 (формируется из подмножества 110, 100, 101 списка», удаляя первый бит и добавляя его к «имени» нового списка)
Теперь возьмите один из полученных списков по очереди и повторите процесс:
Список «0«становится вашим первоначальным списком, и вы разбиваете его на
Список «0 *** 0 **» и
Список «0 *** 1 **» (жирным шрифтом снова является 1 [оставшийся] бит номеров в списке, который разбит)
Продолжайте, пока вы не получите пустой список (ы).
РЕДАКТИРОВАТЬ
Процесс шаг за шагом:
Список «»: 001, 010, 110, 000, 100, 011, 101 =>
Список «0»: 01, 10, 00, 11 (из подмножества 001, 010, 000, 011 Списка «») =>
Список «00»: 1, 0 (из подмножества 01, 00 списка «0») =>
Список «000»: 0 [окончательный результат] (из подмножества 0 списка «00»)
Список «001»: 1 [окончательный результат] (из подмножества 1 списка «00»)
Список «01»: 0, 1 (из подмножества 10, 11 списка «0») =>
Список «010»: 0 [окончательный результат] (из подмножества 0 списка «01»)
Список «011»: 1 [окончательный результат] (из подмножества 1 списка «01»)
Список «1»: 10, 00, 01 (из подмножества 110, 100, 101 Списка «») =>
Список «10»: 0, 1 (из подмножества 00, 01 списка «1») =>
Список «100»: 0 [окончательный результат] (из подмножества 0 списка «10»)
Список «101»: 1 [окончательный результат] (из подмножества 1 списка «10»)
Список «11»: 0 (из подмножества 10 списка «1») =>
Список «110»: 0 [окончательный результат] (из подмножества 0 списка «11»)
Список «111»: нет на месте [конечный результат] (из подмножества EMPTY списка «11»)
Положительным моментом этого метода является то, что он позволит вам найти Любой номер пропущенных номеров в наборе — то есть, если пропущено более одного.
Постскриптум AFAIR для 1 пропущенный номер вне полный ассортимент есть еще более элегантное решение XOR для всех чисел.
Идея состоит в том, чтобы решить более простую проблему:
Отсутствует значение в диапазоне [minVal, X] или (X, maxVal).
Если вы знаете это, вы можете переместить X и проверить еще раз.
Например, у вас есть 3, 4, 1, 5 (2 отсутствует).
Вы знаете, что minVal = 1, maxVal = 5.
РЕДАКТИРОВАТЬ: Некоторые псевдо-C ++ код:
minVal = 1, maxVal = 5; //choose correct values
while(minVal < maxVal){
int X = (minVal + maxVal) / 2
int leftNumber = how much in range [minVal, X]
int rightNumber = how much in range [X + 1, maxVal]
if(leftNumber < (X - minVal + 1))maxVal = X
else minVal = X + 1
}
Вот простое C-решение, которое должно иллюстрировать технику. Чтобы абстрагироваться от каких-либо утомительных файловых операций ввода-вывода, я предполагаю наличие следующих трех функций:
unsigned long next_number (void)
читает число из файла и возвращает его. При повторном вызове возвращается следующий номер в файле и т. Д. Поведение при обнаружении конца файла не определено.
int numbers_left (void)
возвращает истинное значение, если доступно больше чисел для чтения с использованием next_number()
, false, если достигнут конец файла.
void return_to_start (void)
перематывает позицию чтения в начало файла, чтобы следующий вызов next_number()
возвращает первое число в файле.
Я также предполагаю, что unsigned long
имеет ширину не менее 32 бит, как требуется для соответствия реализациям ANSI C; современные программисты C могут предпочесть использовать uint32_t
от stdint.h
вместо.
Учитывая эти предположения, вот решение:
unsigned long count_numbers_in_range (unsigned long min, unsigned long max) {
unsigned long count = 0;
return_to_start();
while ( numbers_left() ) {
unsigned long num = next_number();
if ( num >= min && num <= max ) {
count++;
}
}
return count;
}
unsigned long find_missing_number (void) {
unsigned long min = 0, max = 0xFFFFFFFF;
while ( min < max ) {
unsigned long midpoint = min + (max - min) / 2;
unsigned long count = count_numbers_in_range( min, midpoint );
if ( count < midpoint - min + 1 ) {
max = midpoint; // at least one missing number below midpoint
} else {
min = midpoint; // no missing numbers below midpoint, must be above
}
}
return min;
}
Обратите внимание на одну деталь: min + (max - min) / 2
это безопасный способ рассчитать среднее min
а также max
; это не даст ложных результатов из-за переполненный промежуточные значения, такие как, казалось бы, проще (min + max) / 2
может быть.
Кроме того, хотя было бы заманчиво решить эту проблему с помощью рекурсии, я выбрал итеративное решение вместо этого по двум причинам: во-первых, потому что оно (возможно) более четко показывает, что на самом деле делается, и во-вторых, потому что задача состояла в том, чтобы минимизировать память использование, которое также включает в себя стек.
Наконец, было бы легко оптимизировать этот код, например, вернувшись, как только count
равно нулю, считая числа в и то и другое половин диапазона за один проход и выбирая тот, у которого больше пропущенных чисел, или даже путем расширения двоичного поиска до N-поиск некоторых N > 2, чтобы уменьшить количество проходов. Однако, чтобы сделать пример кода как можно более простым, я оставил такие оптимизации без изменений. Если хотите, вы можете, скажем, попробовать изменить код так, чтобы он требовал не более восьми проходов по файлу вместо текущих 32. (Подсказка: используйте массив из 16 элементов.)
На самом деле, если у нас есть диапазон целых чисел от а до б. Образец: [a..b].
И в этом диапазоне у нас есть целые числа b-a. Это означает, что отсутствует только один.
И если отсутствует только один, мы можем вычислить результат, используя только один цикл.
Сначала мы можем вычислить сумму всех целых чисел в диапазоне [a..b], которая равна:
sum = (a + b) * (b - a + 1) / 2
Затем мы вычисляем сумму всех целых чисел в нашей последовательности:
long sum1 = 0;
for (int i = 0; i < b - a; i++)
sum1 += arr[i];
Тогда мы можем найти отсутствующий элемент как разность этих двух сумм:
длинный результат = sum1 — сумма;
когда вы увидели 2 ^ 31 нулей или единиц в месте с i-й цифрой, тогда в вашем ответе будет один или ноль на i-м месте. (Пример: 2 ^ 31 в 5-й двоичной позиции означает, что ответ имеет ноль в 5-й двоичной позиции.
Первый набросок кода c:
uint32_t binaryHistogram[32], *list4BILLION, answer, placesChecked[32];
uint64_t limit = 4294967296;
uint32_t halfLimit = 4294967296/2;
int i, j, done
//General method to point to list since this detail is not important to the question.
list4BILLION = 0000000000h;//Initialize array to zero. This array represents the number of 1s seen as you parse through the list
for(i=0;i<limit;i++)
{
binaryHistogram[i] = 0;
}
//Only sum up for first half of the 4 billion numbers
for(i=0;i<halfLimit;i++)
{
for(j=0;j<32;j++)
{
binaryHistogram[j] += ((*list4BILLION) >> j);
}
}
//Check each ith digit to see if all halfLimit values have been parsed
for(i=halfLimit;i<limit;i++)
{
for(j=0;j<32;j++)
{
done = 1; //Dont need to continue to the end if placesChecked are all
if(placesChecked[j] != 0) //Dont need to pass through the whole list
{
done = 0; //
binaryHistogram[j] += ((*list4BILLION) >> j);
if((binaryHistogram[j] > halfLimit)||(i - binaryHistogram[j] == halfLimit))
{
answer += (1 << j);
placesChecked[j] = 1;
}
}
}
}