Flat Preloader Icon

Project Euler (answers)

Проект Эйлера: Ответ и решение 46 задачи (Другая проблема Гольдбаха)

Проект Эйлера (46 задача). Даже в такой простой задаче возможно реализовать динамический массив, “хеширование” данных и битовые операции.

Узнать больше в телеграм-канале: ProjectEuler++

Условия задачи

Кристиан Гольдбах показал, что любое нечетное составное число можно записать в виде суммы простого числа и удвоенного квадрата.

  • 9 = 7 + 2×12
  • 15 = 7 + 2×22
  • 21 = 3 + 2×32
  • 25 = 7 + 2×32
  • 27 = 19 + 2×22
  • 33 = 31 + 2×12

Оказалось, что данная гипотеза неверна.

Каково наименьшее нечетное составное число, которое нельзя записать в виде суммы простого числа и удвоенного квадрата?

Добро пожаловать на сайт))

Решение (проект Эйлера - 46 задача)

Структура программы
Структура программы

Структура программы

В данном случае решил не  загромождать код и разделить его на три файла:

  • header.h – вынесем сюда структуры, характерные для этой программы и объявление используемых функций;
  • main.c – интерфейс программы;
  • funcs.h – описание реализации функций, в том числе скрытых с помощью const и не являющихся частью интерфейса.

Алгоритм работы программы

Чтобы определить ответ, будем перебирать нечетные числа (+=2) от меньшего к большему. Те из них, что не являются простыми, будем проверять на удовлетворение условиям Гольдбаха, следующим образом:

  • из проверяемого числа (например, 15) вычитаем удвоенный квадрат первого натурального числа:
    • 9 – 2 * (1^2) = 7;
  • поскольку полученная разность является простым числом, потому проверяемое число удовлетворяет условиям Гольдбаха;
  • если бы нет, то проверяли бы разность числа и удвоенных квадратов следующих натуральных чисел:
    • 9 – 2 * (2^2)
  • и так далее до тех пор, пока в итоге очередная разность не оказалась бы простым числом;

Если для проверяемого числа мы перебрали все удвоенные квадраты чисел до него самого (для 9 это 2 * (2^2) = 8) и ни одна полученная разность не была бы простым числом, то проверяемое число не удовлетворяет условиям Гольдбаха. Поскольку перебор проверяемых чисел мы ведем от меньшего к большему, потому найденное число будет искомым ответом.

(проект Эйлера - 46 задача)

Структуры данных
Структуры данных

Структура данных "struct list"

Поскольку в процессе вычисления нам понадобится многократно определять простые ли числа, потому вычисленные простые числа будем сохранять структуре struct list. Структура содержит ссылку на массив и его длину. Т.к. как заранее неизвестна необходимая длина массива, потому память для него будем выделять динамически с помощью calloc().

Перечисление "enum bit_nums"

Числа в массиве будут храниться по их индексу, однако чтобы уменьшить длину массива на порядок, в ячейке будет храниться не одно значение, а несколько.

Т.е. в массиве будет храниться не два возможных значения (1 / 0 : простое / составное число):

  • массив[101] = 1 // 101 – простое число;
  • массив[100] = 0 // 100 – составное число;

… а больше.

Простые числа в нашем случае будут храниться в массиве в виде включенных / выключенных битов:

  • массив[10] = 1 (т.е. 0000 0001 в двоичном виде – включен первый бит) означает, что число 101 – простое;
  • при этом массив[10] = 2 (т.е. 0000 0010 двоичном виде) будет означать 103;
  • а массив[10] = 4 (0000 0100) – 105.

Для внесения отдельных битов в ячейку как раз и используем перечисление enum bit_nums.

Если в ячейке включено несколько битов, например:

  • массив[10] = 5 (0000 0101)

…значит, в ячейку занесены два числа:

  • 101 (массив[10] = 1 // 0000 0001
  • 105 (массив[10] = 4 // 0000 0100

Перечисление "enum check_sign"

Перечисление сделано для передачи аргумента в функцию check().

При передаче more функция будет срабатывать при превышении аргументом заданного лимита, а less – при уменьшении.

Объявления функций
Объявления функций

Объявления функций

Также выносим в этот файл объявления функций, использующихся в функции main().

К каждой из них пишем комментарий по ее использованию посторонним пользователем (возможно это будете именно Вы же, но через полгода). В качестве аргументов функции будут принимать указатели на структуры вне зависимости от того, изменяет ли функция ее содержание. Это сделано для того, чтобы пользователь не путался, где нужно использовать указатели, а где нет. Там, где данные в структуре изменяться не будут, просто пишем const.

Описание работы программы (Проект Эйлера - 46 задача)

Проект Эйлера (46 задача) - решение, часть 1
Решение, часть 1

Разбил main() на два скриншота, чтобы не мельчить.

Проект Эйлера (46 задача) - решение, часть 2
Решение, часть 2

При начале работы пользователю предлагается ввести, до какого числа искать ответ. Это нужно для того, чтобы выделить соответствующее количество памяти для структуры с простыми числами и массива удвоенных квадратов чисел.

Поскольку для проверки условий Гольдбаха достаточно знать ряд простых чисел и удвоенных квадратов натуральных чисел до проверяемого числа включительно, потому вычислять простые числа и удвоенные квадраты будем не сразу все, а постепенно.

Далее, если число не является простым, оно проверяется на удовлетворение условиям Гольдбаха, как уже описано в алгоритме.

Первое найденное число не удовлетворяющее этим условиям будет искомым ответом.

Измеряем время работы программы

Для измерения времени работы программы подключаем библиотеку time.h:

  • #include <time.h> 

С помощью функции clock() измеряем время начала (begin) и окончания (end) работы программы. В итоге время работы будем находить как разность между ними.

* Примечания:

clock_t это тип данных, определенный через typedef. По сути это обычный целочисленный long

(double) приводит число (в данном случае – результат вычислений end – begin) к вещественному типу данных double (числа с точкой)

CLOCKS_PER_SEC – это константа, сохраненная в библиотеке и зависящая от типа устройства, на котором запускается программа. В данном случае – это просто 1000

Функции, используемые в программе

Все функции, используемые в программе вынес в файл “funcs.c“. Их работа довольно подробно описана в комментариях к коду, но повторюсь.

Встраиваемая функция check() (проект Эйлера - 46 задача)
Встраиваемая функция check()

Встраиваемая (inline) функция для проверки невыхода параметров за пределы

Данная функция в случае выхода контролируемого параметра за назначенные пределы (его превышения или уменьшения) выводит информационное сообщение об этом и возвращает true для дальнейшей обработки ошибки.

Данная функция является служебной и служит частью реализации программы, но не его интерфейса и потому скрыта от использования из других файлов ключевым словом const.

Также в целях ускорения используем ключевое слово inline, указывая, что функция является встраиваемой.

Функция для создания структуры и выделения ей памяти

Функция для создания структуры и выделения ей памяти
Функция для создания структуры и выделения ей памяти

Выделяет память нужного размера для структуры struct list, заполняет все поля нулями и возвращает ее.

В случае неуспешного выделения памяти, поле структуры *arr будет указывать не на массив, а на NULL.

Функция для внесения простого числа в массив (проект Эйлера - 46 задача)

(проект Эйлера - 46 задача) Функция для внесения простого числа в массив
Функция для внесения простого числа в массив

Служит для внесения простого числа в массив. 

Для хранения простых чисел используем массив на порядок меньший хранимому числу. Т.е. для хранения числа 12345 нам не нужен char-массив длиной 12345, а вполне достаточно 1234 байта. 

Так происходит потому, что последний разряд числа зашифрован в виде отдельных битов:

  • Число 1 в десятичном виде (0000 0001 в двоичном – включен первый бит) – будет означать что в массив занесена цифра 1;
  • 2 (0000 0010) – 3;
  • 4 (0000 0100) – 5;
  • 8 (0000 1000) – 7;
  • 16 (0001 0000) – 9;

Четные цифры не шифруем, потому что четные числа заведомо не могут быть простыми.

Такой способ хранения позволяет в одной ячейке хранить несколько цифр, например:

  • 0001 0101 – в ячейке хранятся цифры 9, 5 и 1

…и для ячейки:

  • массив[1234] это числа 12341, 12345 и 12349.

Биты в ячейку заносятся с помощью битового ИЛИ:

  • 0000 0000 | 0000 0001 = 0000 0001 // включили крайний правый бит

Функция для определения простого числа (проект Эйлера - 46 задача)

(проект Эйлера - 46 задача) Функция для определения простого числа
Функция для определения простого числа

Данная функция возвращает является ли число простым, просто проверяя его наличие в заранее заполненном массиве простых чисел с помощью операции побитового И.

  • 0001 0010 & 0000 0010 = 0000 0010 // возвращает только совпавшие биты у зашифрованного числа и флага. При этом любое ненулевое значение, получившееся при операции, функция трактует как true.

Функция для определения следующего простого числа после num

Функция для определения следующего простого числа после num
Функция для определения следующего простого числа после num

Напоминаю, что простое число делится только на единицу и само на себя. Данная функция перебирает нечетные числа, следующие за последним простым числом, проверяет их и возвращает первое же найденное простое число.

Особенности:

  • перебираются нечетные числа, т.к. четные – заведомо не являются простыми;
  • делители, которыми проверяется делимость числа, проверяются только до квадратного корня из проверяемого числа, потому что, если до этого момента делитель не найден, то и дальше его не будет;
  • делители не являющиеся простыми числами пропускаются, потому что составное число само является делимым на меньшие простые числа;

Если по окончании перебора делитель не найден, то число является простым.

Иначе таким же образом проверяется следующее нечетное число.

Если проверяемое число превысило длину массива, то работа функции останавливается и возвращается false (0).

Функция для проверки соответствия числа условиям Гольдбаха

(проект Эйлера - 46 задача) Функция для проверки условия Гольдбаха
Функция для проверки условия Гольдбаха

Данная функция проверяет соответствие числа условиям Гольдбаха.

Т.е. перебирает удвоенные квадраты натуральных чисел от меньшего к большему пока не превысит проверяемое число или пока разность числа и удвоенного квадрата текущего числа не окажется простым числом.

Функция вернет false только в том случае, если в этом диапазоне среди разностей проверяемого числа и удвоенных квадратов простых чисел не было.

Ответ и скорость работы программы (проект Эйлера - 46 задача)

Нет ответа
Нет ответа

В зависимости от того, найден ли на заданном диапазоне ответ, вывод будет разный.

Проект Эйлера (46 задача) - ответ
Ответ на 46 задачу

В итоге ответ на 46 задачу проекта Эйлера составил 5777, программа считает ответ практически мгновенно. 

По традиции хотелось бы выразить глубочайшую признательность Сергею Балакиреву за его титанический труд по созданию бесплатных курсов и в частности, за “Язык программирования C/C++ для начинающих“.

проект Эйлера
Курс "C/C++ для начинающих"

Есть вопросы, господа? Отвечаем спокойно, четко и только по делу))

Есть вопросы, господа?

Проект Эйлера (ответы) | © 2023-2024 | eulerproject.ru

Проект Эйлера (ответы) | © 2023-2024 | eulerproject.ru

© 2023-2024 | eulerproject.ru