Дробь с положительными числителем и знаменателем называется правильной, если . Мы можем сразу сосредоточиться на правильных дробях. Если дробь неправильная, разделим числитель на знаменатель с остатком. Целую часть дроби запишем отдельно. Если остаток от деления отличен от нуля, запишем десятичную запятую, после чего займёмся правильной дробью . Так что общий случай легко сводится к случаю правильной дроби.
Вспомним правила преобразования простой дроби в десятичную. Они прямо вытекают из правил деления в столбик. Если делимое меньше делителя, к нему приписывают ноль справа (умножают на ) и делят нацело на делитель. В результате получается очередная цифра дроби. Остаток от деления служит в качестве очередного делимого, он опять умножается на , опять делится нацело на делитель, и так далее. Процесс останавливается, когда делимое обратится в ноль. Этот момент, как мы знаем, может и не наступить, и тогда деление будет продолжаться до бесконечности, порождая всё новые и новые цифры в десятичной записи дроби.
Для примера рассмотрим дробь . Составим таблицу с тремя колонками. В первой будем записывать числители. Во второй и третьей окажутся целое частное и остаток от деления числителя, умноженного на , на знаменатель. Напомним, что означает целую часть числа . Так что целое частное при делении чисел и () получается как , а остаток от деления () — как . Число из третьей колонки переносится в первую колонку следующей строки таблицы:
Пора заметить, что все числа из первой колонки строго меньше . Действительно, самое первое обладает этим свойством в силу предположения о правильности дроби . Для всех последующих это также верно, потому что они являются остатками от деления чего-то на . Целых неотрицательных чисел, меньших , — конечное число, поэтому рано или поздно числа в первой колонке начнут повторяться.
В частности, если там встретится ноль, все последующие числа будут также нулевыми (это потому что ). Это как раз случай конечной десятичной дроби: в какой-то момент наш алгоритм будет добавлять новые и новые незначащие нули, если его не прервать.
Второе наблюдение состоит в том, что одинаковым числителям из первой колонки соответствуют одинаковые десятичные цифры во второй, поскольку цифры однозначно определяются этими числителями. Это значит, что, начиная с некоторого момента, десятичная дробь будет содержать повторяющиеся группы цифр (периоды). Вот мы и доказали, что любое положительное рациональное число представляется в виде конечной или бесконечной десятичной дроби. В первом случае период состоит из единственной цифры — нуля, а во втором период содержит и другие цифры.
Итак, мы выяснили, как получать новые и новые десятичные цифры десятичного представления обыкновенной дроби. Нужно построить рекуррентную последовательность первого порядка, в начале которой находится — числитель дроби, а каждый следующий элемент получается из предыдущего по формуле (последовательность числителей). Тогда -я цифра после запятой находится по формуле (та цифра, которая следует сразу за запятой, идёт под номером ).
Особо отметим, что последовательность , в отличие от , рекуррентной не является. Каждый элемент рекуррентной последовательности однозначно определяется предшествующим элементом, иначе дробь была бы невозможной. Если бы цифры образовывали рекуррентную последовательность, после одинаковых цифр стояли бы одинаковые, чего не наблюдается в примере: после первого ноля находится ноль, а после второго — единица.
Обобщим сделанные наблюдения. Рассмотрим функцию , определённую на некотором конечном множестве и принимающую значения также из этого множества. Возьмём какой-нибудь элемент и для него построим последовательность , , , . Получившаяся последовательность называется орбитой элемента для функции . Мы утверждаем, что, начиная с некоторого номера , орбита зациклится. Действительно, число различных элементов орбиты не больше, чем размер множества, и рано или поздно значения последовательности повторятся. Обозначим два первых одинаковых элемента как и . Тогда , и вообще, для всех . Иными словами, если два элемента последовательности равны, то равны также и элементы, следующие за этими двумя, поскольку они получаются из предыдущих по одному и тому же правилу. Природа функции совершенно не важна, важно лишь то, что множество её значений конечно. Положительное число называется наименьшим периодом последовательности, начиная с номера .
Главный вывод, который нам следует принять к сведению, звучит так: рекуррентная последовательность элементов конечного множества зацикливается, причём длина непериодической части последовательности в сумме с длиной периода не превосходит количества элементов множества.
Разумеется, вся трудность задачи заключается в определении периода. Перед началом периодической части последовательности цифр следует вывести открывающую скобку, а по завершении — закрывающую, после чего поиск новых цифр должен быть остановлен.
Напрашивается следующее решение: каждый вновь найденный элемент
последовательности
добавлять в массив, прежде пытаясь его отыскать в этом массиве. Если элемент
уже присутствует в массиве, периодическая часть последовательности начнётся
с этого индекса. Если же элемент не найден, он в массив не добавляется, и
вычисление рекуррентной последовательности завершается. К этому моменту мы
располагаем массивом
и индексом в нём, обозначающим
начало периодической части. Теперь всё просто. Для всех элементов массива
вычисляем
и тут же выводим, не забывая вставить открывающую скобку перед -м элементом, и закрывающую в самом конце.
Здесь, правда, есть одно осложнение, возникающее в том случае, если десятичная
дробь конечна. Наш алгоритм всё равно сочтёт её периодической, и выведет
в конце нулевой период (0)
. Нам это совершенно не нужно. Но
такая ситуация возникает только тогда, когда последний вычисленный (и не
добавленный в массив) элемент последовательности числителей равен нулю. Так что
в том месте программы, где должен быть выведен период в скобках, следует
сделать соответствующую проверку, и вообще не выводить периодическую часть,
если это не нужно.
Увы, это решение совершенно ужасно. Заметим, что каждый числитель, попавший в массив, подвергался безуспешному сравнению со всеми предшествующими элементами. Общее количество сравнений равняется количеству всевозможных пар, которые можно составить из элементов массива. Мы знаем из комбинаторики, что для -элементного множества количество пар, составленных из элементов, равно , что при больших приближённо равно . Вспомним, что в нашей задаче может достигать значения , так что для знаменателя дроби порядка миллиона может потребоваться массив миллионной длины, да и вряд ли мы дождёмся его заполнения, которое потребует полтриллиона сравнений.
Остроумный метод Черепахи и Зайца, который также называют в честь первооткрывателя методом Флойда, позволяет выявлять циклы в последовательностях, то есть находить числа и .
Мысленно проведём стрелки от каждого элемента последовательности к следующему за ним. Черепаха и Заяц, герои басни Эзопа, могут сдвигаться по стрелкам. Выявление цикла, согласно методу Флойда, происходит в три этапа, каждый из которых завершается, как только Черепаха и Заяц встречаются. Разберём в подробностях каждый этап, но прежде введём нужные обозначения.
Два целых числа и , дающих при делении на целое положительное число одинаковые остатки, будем называть сравнимыми по модулю . Этот факт обычно записывают так: а выписанное выражение называют сравнением по модулю.
Сравнения во многом похожи на равенства. В частности, к обеим частям сравнения можно прибавить одно и то же целое число, и сравнение останется верным. Этот факт следует из того, что при увеличении или уменьшении на единицу обеих частей сравнения остатки от деления на одновременно либо увеличиваются (уменьшаются) на единицу, либо одновременно обращаются в нуль. Несложно доказать также, что сравнение останется верным при умножении обеих частей на одно и то же целое число. Достаточно показать это для сравнений вида (к такому виду можно привести любое сравнение, перенося правую часть налево с изменением знака). Полученное сравнение означает, что делится нацело на , и этот факт не перестанет быть верным при умножении обеих частей сравнения на произвольное целое число.
На первом этапе Черепаха и Заяц находятся в начале последовательности. На каждом шаге Черепаха сдвигается по стрелке один раз, а Заяц — дважды (рисунок 15.1. «Черепаха и Заяц: первый этап»). Мы утверждаем, что их встреча обязательно состоится, и не позже, чем Черепаха пройдёт первый круг цикла. И в самом деле, рано или поздно Черепаха достигнет начала цикла. Пронумеруем элементы последовательности в цикле числами . Как только Черепаха вступит в цикл (то есть окажется в положении ), Заяц уже будет где-то на цикле в положении . Дальнейшее движение Черепахи по циклу подчиняется закону , а Зайца — . Условием встречи героев является сравнение , или , или просто , или . Наименьшим неотрицательным решением этого сравнения будет , это и будет номером позиции, где произойдёт встреча.
Интересно, что сделанное Черепахой количество шагов на первом этапе делится на длину периода . К моменту, когда Черепаха пройдёт по стрелкам путь от начала дистанции, Заяц пройдёт путь . В момент встречи они оба будут находиться на цикле, и поэтому выполнится сравнение , или (конечно же по модулю ).
Целью второго этапа является обнаружение начала цикла. Теперь Черепаха становится в начало дистанции, а Заяц остаётся там, где закончился первый этап. На втором этапе герои на каждом шаге сдвигаются вперёд на одну позицию, и так происходит до их встречи (рисунок 15.2. «Черепаха и Заяц: второй этап»). Оказывается, место встречи будет как раз в начале цикла. Шаги на втором этапе подсчитываются, и после окончания забега их количество будет равно длине непериодической части орбиты.
Мы уже знаем, что Заяц после первого забега и перед началом второго прошёл по стрелкам путь и ещё плюс какое-то количество полных кругов, то есть пробег Зайца сравним с нулём по модулю . Если сдвинуть Зайца раз, его пробег увеличится на , и выполнится сравнение . Пробег Черепахи после шагов будет равен . В этот момент она как раз окажется в начале цикла (Заяц к этому времени уже будет там). Вот тут-то они и встретятся, поскольку их пробеги будут сравнимы по модулю .
Третий этап нужен для определения конца цикла и он совершенно тривиален. Черепаха остаётся неподвижно там, где она встретились с Зайцем в конце второго этапа, то есть в начале цикла. Заяц же на каждом шаге сдвигается на одну позицию, пока не встретится с Черепахой, пройдя один круг. Шаги Зайца подсчитываются, и после окончания третьего забега счётчик покажет длину цикла (рисунок 15.3. «Черепаха и Заяц: третий этап»).
Мы провели испытание обеих версий программы на дроби . Наивная версия работала чуть больше 270 секунд, а версия с методом Флойда — 0,154 секунды. Мы уже молчим о том, что вторая версия требует несравнимо меньший объём памяти. Дроби с шестизначными знаменателями — это, похоже, предел возможностей наивной программы; в то же время версия Флойда справлялась за разумное время и с семи-, и с восьмизначными знаменателями. Это очень, очень красноречивый результат.