- •Программирование и алгоритмические языки
- •Основные понятия процедурного программирования. Области и процедуры
- •Способы обозначения и определения процедур
- •Языки блок-схем
- •Трассировка программы
- •Структурное программирование
- •Программы, как файловые процедуры
- •Итерационные и рекуррентные последовательности
- •Восходящее и нисходящее программирование
- •Язык программирования Pascal
- •Процедурный Паскаль Синтаксис Паскаль-программы
- •Основные операторы
- •Классификация типов Простые (скалярные) типы
- •Стандартные типы
- •Пользовательские скалярные типы данных
- •Алгоритмическое определение булевских операций
- •Стратегия вычисления условий
- •Вычисление кванторов
- •Символьный тип
- •Производные(структурные) типы Массивы
- •Операции над массивами
- •Расширенный синтаксис. Массивы размерности n.
- •Строки. Динамические массивы.
- •Комбинированные типы и записи
- •Оператор присоединения
- •Типизированные файлы.
- •Упорядоченные файлы
- •Поиск в упорядоченном файле
- •Арифметика упорядоченных файлов
- •Дихотомический поиск в упорядоченном массиве
- •Простые алгоритмы сортировки
- •Множества
- •Операции над множествами
- •Решето Эратосфена
- •Подпрограммы. Пользовательские процедуры и функции
- •Синтаксис обращения к процедурам
- •Синтаксис использования или обращения к процедурам.
- •Семантика
- •Правила построения модифицированного тела:
- •Пользовательские процедуры (продолжение)
- •Подпрограммы и функции
- •Описание функции
- •Обращение к функции
Итерационные и рекуррентные последовательности
Знакомый нам способ определения последовательности – формула общего члена последовательности.
Пример: последовательность четных чисел
f(n)=2n
В этом случае задается зависимость члена последовательности от номера
Итерационным (рекуррентным) определением последовательности называется определение зависимости очередного члена от предыдущего (или в общем случае рекуррентной последовательности – от нескольких предыдущих).
Итерация – повторение. Рекуррентность – возвратность, зависимость от предыдущего.
Первый член в этом случае (в общем случае - несколько первых) задаются независимо.
f(0)=0 f(n+1)=f(n)+2
f(0)=0 f(n+1)=f(n)+an+1
f(0)=1 f(n+1)=f(n)• an+1
f(n)=max(a1, …, an)
f(1)=a1 f(n+1)=max(f(n),an+1)
Понятие рекуррентности тесно связано с определением цикла.
Трасса цикла – есть рекуррентно задаваемая последовательность (состояний).
Найти n-ые члены определенных выше последовательностей
f(n)=max(a1, …, an)
в предположении доступности обычных арифметических операций (функций двух аргументов) и сравнений.
Сам оператор цикла задает лишь итерационную зависимость текущего состояния всех переменных от их предыдущего состояния. Начальное состояние (значение 1-го члена итерации) задается независимо перед выполнением цикла – инициализация цикла.
Обратите внимание на последний пример. Сначала мы, как и в предыдущих примерах, определяем наибольшее среди n чисел в терминах максимума двух чисел (итерационное определение). Но последняя функция не допустима – ее собственной определение является подзадачей (условное определение). Здесь мы подходим к важной методике построения программ - программированию «сверху вниз».
Восходящее и нисходящее программирование
Фактически, мы определили бесконечное число языков блок-схем, различающихся наборами конкретных базовых операторов и предикатов.
В одном языке может быть больше обозначений, то есть любой допустимый в нем оператор и предикат может быть определен в виде программы на некотором другом языке. В этом случае говорят, что первый язык имеет более высокий уровень, чем второй.
Восходящее программирование – от средств к цели. Отталкиваясь от данного языка определений строить все более крупные блоки, то есть подниматься вверх по уровням языков программ.
Нисходящее программирование – от цели к средствам. В этом случае начиная от цели (спецификации задачи на каком-то языке высокого уровня) мы спускаемся вниз по уровням языков ко все более мелким деталям конечной программы.
Сначала, восходящее программирование кажется более естественным. Начинающие программисты обычно пишут операторы программы в порядке вычисления их компьютером.
Но восходящее программирование – скорее тактика, пригодная лишь для небольших задач. Главная стратегия при решении сложных задач – нисходящее проектирование. Фактически, нисходящее программирование не зависит от конкретного языка программирования.
Простой пример.
Сначала определи (итерацией) главную (последнюю в смысле вычисления) функцию bi, выделяя задачу вычисления bi = aij в качестве отдельной подзадачи.
Итак, идея нисходящего программирования:
Начинай со спецификации – изложи то, что нужно хоть на каком-нибудь (достаточно) точном языке.
Далее, придумывай все более ограниченные языки спецификаций с тем, чтобы решение задачи, поставленной на языке L2, сформулированное в терминах языка Li+1 было достаточно простым, надежным, контролируемым.
Программа обычно считается сложной, если содержит более 2-х, 3-х циклов. Однако надежно отслеживать можно только один цикл.
В общем случае, задача спецификации, точной постановки задачи предметной, т.е. реальной моделируемой области очень сложна. Здесь мы ограничиваемся уже хорошо специфицированными задачами.
Пример с чуть менее очевидной спецификацией.
Дана последовательность положительных чисел a1, …, an; ai>0, an=0.
Ступенькой назовем произвольную возрастающую подпоследовательность ai, …, ai+k. Найти длину наибольшей ступени.
Спецификация. Max(lk), где lk - (подзадача вычисления) длины очередной ступеньки. Путаница здесь возникает из-за того, что функции привычнее обозначать существительными. Но длин много, наибольшая – одна (однозначность функции).