Опубликован: 09.12.2017 | Доступ: свободный | Студентов: 741 / 32 | Длительность: 02:06:00
Специальности: Программист
Лекция 10:

Возведение в степень

< Лекция 1 || Лекция 10: 12

Эффективный алгоритм возведения в целую степень

Представим себе, что нам необходимо возвести число X в целую степень n, полагая, что нам доступна операция умножения. Существует очевидный алгоритм, позволяющий написать собственную реализацию функции Pow.

Работа в классе.

        /// <summary>
        /// Возведение числа в целую степень
        /// </summary>
        /// <param name="x">число</param>
        /// <param name="n">степень</param>
        /// <returns>x в степени n</returns>
        static double Pow_Simple(double x, int n)
        {
            double res = 1;
            for (int i = 0; i < n; i++)
                res *= x;
            return res;
        }

А есть ли лучший по сложности алгоритм? Да, такой алгоритм существует. Если наш Simple алгоритм требует n умножений, то эффективный алгоритм требует не более 2 * log n умножений. Нужно понимать, что для больших n логарифм от n значительно меньше n. Например, при n = 220 (n больше миллиона), log n = 20.

Приведу функцию, реализующую эффективный алгоритм. Комментарии позволяют понять идею эффективного алгоритма. Сам по себе алгоритм весьма элегантен. Заодно он демонстрирует способ доказательства корректности алгоритма:

        /// <summary>
        /// Возведение числа в целую степень
        /// эффективный алгоритм
        /// </summary>
        /// <param name="x">число</param>
        /// <param name="n">степень</param>
        /// <returns>x в степени n</returns>
        static double Pow_Effective(double x, int n)
        {
            double z = x;
            double y = 1;
            int m = n;
            //выполняется условие (инвариант цикла):
            // Invariant: z ^ m * y == x ^ n 
            while (m != 0)
            {
                if( m % 2 == 0) //IsEven(m)
                { m /= 2; z *= z; } //инвариант выполняется
                else
                { m -= 1; y *= z; }
            }
             //Invariant && (m == 0) => y == x ^ n  
            return y;
        }

Пример:

n = 33; z = x; y = 1; m = 33; => m = 32; z = x; y = x; => m = 16; z = x ^2; y = x; =>

m = 8; z = x ^ 4; y = x; => m = 4; z = x ^ 8; y = x; => m = 2; z = x ^ 16; y = x; =>

m = 1; z = x ^ 32; y = x; => m = 0; z = x ^ 32; y = x ^ 33; (Всего 6 умножений, а не 33)

Этот алгоритм важен для чисел, но еще в большей степени, когда операции умножения и возведения в степень выполняются для более сложных объектов, чем числа, например, при работе с матрицами или при работе с длинной арифметикой, когда числа могут содержать сотни значащих цифр, что выходит за пределы возможности стандартных типов int и double.

Программа на миллион долларов. Числа "градины" или проблема 3x + 1

Рассмотрим короткую программу в несколько строчек:

        /// <summary>
        /// Знаменитая программа
        /// Пока никому не удалось доказать ее завершаемость
        /// для любого целого n
        /// Объявлена награда в миллион долларов
        /// </summary>
        /// <param name="n">целое</param>
        /// <returns>число проходов по циклу</returns>
        static int Grad(long n)
        {
            int res = 0;
            while (n != 1)
            {
                if (n % 2 == 0) //IsEven(n)
                    n /= 2;
                else
                    n = 3 * n + 1;
                res++;
            }
            return res;
        }

Доказано, что программа завершается для любых nN, где N – большое число. Но пока никому не удалось доказать ее завершаемость для любого n.

Можно написать программу, проверяющую завершаемость для всех n из заданного интервала. Приведу программу, которая находит максимальную "градину" - число, у которого число колебаний максимально:

        /// <summary>
        /// Наибольшая "градина"
        /// в интервале [3, N]
        /// </summary>
        /// <param name="N">верхняя граница</param>
        /// <param name="Max">наибольшая градина в интервале</param>
        /// <param name="K_max">число колебаний градины</param>
        public static void MaxGrad(int N, out int Max, out int K_max )
        {
            K_max = 0;
            int n;
            Max = 0;
            for (int i = 3; i < N; i += 2 )
            {
                n = Grad(i);
                if (n > K_max)
                {
                    K_max = n;
                    Max = i;
                }
            }
        }

Приведу интерфейс проекта, анализирующего поведение чисел "градин":


Этой замечательной программой мы завершим эту часть нашего курса.

Итоги

На заключительном занятии практически все школьники представили свой вариант игры "Однорукий бандит", что свидетельствует о том, что основами программирования они овладели. Все они продолжили обучение в следующем учебном году. В трейлере, подготовленном для этого курса, можно услышать, как школьники оценивают прослушанный ими курс.

https://youtu.be/-ooc_rJIe6o

< Лекция 1 || Лекция 10: 12