- •2. Структура и основные элементы программы
- •3.Общее понятие типов данных
- •4. Переменные и константы
- •5.Основные типы данных
- •6. Спецификаторы типов данных
- •7. Определение переменных и констант в программе
- •8. Инициализация переменных различных типов
- •9.Целочисленные типы данных
- •10. Вещественные типы данных
- •11. Особенности представления вещественных типов данных
- •12.Логический тип данных
- •13. Символьный тип данных
- •14. Управляющие последовательности
- •15. Операции и выражения
- •16. Операция присваивания, составные операции присваивания
- •17. Понятие l-значения
- •18. Преобразование типов данных
- •19. Арифметические операции
- •20. Операции инкремента и декремента, их разновидности
- •21. Операции отношения
- •22. Логические операции
- •23. Побитовые операции сдвига
- •24. Побитовые логические операции
- •25. Примеры применения побитовых операций
- •26. Условная операция и ее использование
- •27. Определение объема памяти, необходимого для размещения объектов
- •28. Понятие приоритета операций и его влияние на результаты вычислений
- •31.Флаги форматирования потоков ввода-вывода
- •32. Форматирование ввода-вывода с помощью манипуляторов
- •33.Форматирование ввода-вывода с помощью функций потоков ввода-вывода
- •34. Управление шириной поля вывода и выравниванием данных при выводе
- •35. Управление форматом вывода вещественных значений
- •36. Основные понятия структурного программирования
- •37. Базовый набор управляющих структур
- •39.Условная инструкция (if)
- •40. Инструкция множественного выбора (switch)
- •42. Цикл с постусловием (do while)
- •43. Итерационный цикл (for)
- •46. Инструкция перехода goto
- •47. Понятие рекуррентных вычислений, примеры
- •48. Понятие инварианта цикла
- •49. Понятие и определение массива
- •52. Ввод элементов массивов с клавиатуры
- •53. Декларативная и программная инициализация массивов
- •54. Копирование массивов
- •55. Нахождение минимальных и максимальных значений в массивах
- •56. Сдвиг элементов массивов
- •57. Перестановка элементов в массивах
- •58. Поиск данных в массивах
- •59. Сортировка данных в массивах
- •60. Вычисление сумм и произведений элементов массивов
- •61. Представление текстовых строк в виде массива символов
- •62. Ввод-вывод символьных строк
- •63. Определение фактической длины строки
- •64. Копирование символьных строк
- •65. Основные функции обработки строк библиотеки cstring
- •66. Массивы текстовых строк (двумерные массивы символов)
- •67. Указатели Понятие указателя
- •Работа с указателями
- •68. Арифметика указателей
- •69. Индексирование указателей
- •70. Ссылки
- •71. Определение функции
- •72. Инструкция return
- •73. Завершение работы функции
- •74. Механизмы передачи данных через параметры функций
- •75. Передача данных по значению
- •76. Передача данных через указатели
- •77. Передача данных по ссылке
- •78. Параметры по умолчанию
- •79. Функции с переменным числом параметров
- •80. Inline функции
- •81. Перегрузка функций
- •82. Рекурсия
- •83. Прототипы функций
48. Понятие инварианта цикла
Инвариантом называется логическое выражение, истинное перед началом выполнения цикла и после каждого проходатела цикла, зависящее от переменных, изменяющихся в теле цикла.
Инварианты используются для доказательства правильности выполнения цикла, а также при проектировании и оптимизации циклических алгоритмов.
Порядок доказательства работоспособности цикла с помощью инварианта сводится к следующему:
Доказывается, что выражение инварианта истинно перед началом цикла сразу после инициализации параметров цикла.
Доказывается, что выражение инварианта сохраняет свою истинность до и после выполнения тела цикла; таким образом, по индукции, доказывается, что по завершении цикла инвариант будет выполняться.
Доказывается, что при истинности инварианта после завершения цикла переменные примут именно те значения, которые требуется получить (это элементарно определяется из выражения инварианта и известных конечных значениях переменных, на которых основывается условие завершения цикла).
Доказывается, что цикл завершится, то есть условие завершения рано или поздно будет выполнено.
Истинность утверждений, доказанных на предыдущих этапах, однозначно свидетельствует о том, что цикл выполнится за конечное время и даст желаемый результат.
Инварианты используют не только для доказательства корректности циклов, но и при проектировании и оптимизации циклических алгоритмов.
Рассмотрим использование инварианта на примере реализации алгоритма Евклида для нахождения наибольшего общего делителя двух чисел.
Постановка задачи. Требуется найти наибольший общий делитель d двух целых чисел n и m: d = НОД(n, m).
Сформулируем инвариант цикла для нахождения НОД(n, m) следующим образом: пусть имеется пара чисел a и b таких, что НОД(a, b) = НОД(n, m). На каждом шаге цикла будем переходить к другой паре чисел a и b таких, что НОД(a, b) = НОД(n, m). И так будем продолжать до тех пор, пока значение НОД не станет очевидным. Таким образом, инвариант цикла сформулируем так: НОД(a, b) = НОД(n, m). Теперь стоит задача: как найти очередную пару чисел a и b, при которых значение инварианта не изменится.
Из математики (теория чисел) известно, что если d = НОД(n, m), то это же значение d является и НОД(m, r), где r – остаток от деления n на m, то есть НОД(n, m) = НОД(m, r).
Например: НОД(126, 12) = НОД(12, 6) = НОД(6, 0) = 6
Алгоритм решения задачи можно представить так:
Начальная инициализация: пусть a = n, b = m. Очевидно, что НОД(a, b) = НОД(n, m).
Находим r и делаем a = b и b = r. При этом выражение НОД(a, b) = НОД(n, m) остается справедливым.
Как только b станет равно 0, тогда НОД(a, 0) = НОД(n, m) = a.
Программа, реализующая этот алгоритм:
int r, a = n, b = m;
// Инвариант: НОД(a, b) = НОД(n, m)
// Цикл заканчивается при b = 0, тогда НОД(a, 0) = a
while (b)
{
// Инвариант: НОД(a, b) = НОД(n, m) выполняется
r = a % b;
a = b;
b = r;
// Инвариант: НОД(a, b) = НОД(n, m) выполняется
}
// Инвариант: НОД(a, b) = НОД(n, m) выполняется
cout << “НОД (“ << n << “, ” << m << “) = ” << a << endl;
Можно предложить еще один алгоритм решения этой задачи, основанный на том же инварианте, но использующий другой способ нахождения следующей пары a и b: известно, что НОД(n, m) = НОД(n - m, m) при n > m и НОД(n, m) = НОД(n, m - n) при m > n. Например: НОД(126, 12) = НОД(114, 12) = НОД(102, 12) = … = НОД(18, 12) = НОД(6, 12) = НОД(6, 6) = 6. Но этот алгоритм является более затратным по сравнению с предыдущим.
Еще одним наглядным примером использования инварианта для проектирования цикла является реализация быстрого возведения чисел в целую положительную степень. Пример его реализации приведен в Приложении. Предлагается разобрать его самостоятельно.
В Приложении приведены программы, реализующие различные варианты итерационных вычислений, основанных на использовании рекуррентных соотношений и инвариантов.
Очень часто используются, так называемые, вложенные циклы. Примеры использования таких конструкций будут рассмотрены при изучении массивов.