
- •Курс лекций по дисциплине “Основы программирования и алгоритмические языки” Бондарев в.М., Марченко ю.С. Введение
- •Основы алгоритмизации
- •1 Основные этапы решения задачи на эвм
- •1.1 Постановка задачи
- •1.2 Проектирование программы
- •1.3 Разработка алгоритма
- •1.4 Кодирование
- •1.5 Отладка и тестирование программы
- •2 Элементарные алгоритмические структуры
- •2.1 Последовательная алгоритмическая структура
- •2.2 Алгоритмическая структура выбора
- •2.3 Алгоритмическая структура повторения
- •2.4 Комбинация структур
- •2.5 Пошаговая детализация алгоритма
- •2.6 Разработка алгоритма вычисления Sin X
- •2.7 Разработка алгоритма подсчета простых чисел
- •3 Алгоритмы поиска
- •3.1 Поиск наибольшего среди вводимых чисел
- •3.2 Поиск наибольшего числа в массиве
- •3.3 Поиск заданного числа в массиве
- •3.4 Поиск с порогом
- •3.5 Двоичный поиск
- •4 Алгоритмы сортировки
- •4.1 Обменная сортировка
- •4.2 Сортировка слиянием
- •4.3 Сравнение двух алгоритмов сортировки
- •5 Рекурсивные алгоритмы
- •5.1 Простая рекурсия
- •5.2 Ханойские башни
- •5.3 Быстрая обменная сортировка
- •6.2 Простейшая программа
- •6.3 Составление простой программы
- •6.4 Программа сложение двух чисел
- •6.5 Организация повторений
- •6.6 Условный оператор
- •If (выражение) оператор [else оператор].
- •6.7 Оператор цикла for
- •7 Указатели и массивы
- •7.1 Указатели
- •7.2 Разыменование и разадресация
- •7.3 Операции new и delete
- •7.4 Массивы
- •7.5 Многомерные массивы
- •7.6 Связь между массивами и указателями
- •7.7 Массивы в динамической памяти
- •8 Строки и структуры
- •8.1 Встроенный тип char
- •8.2 Строки символов как массивы
- •8.3 Строковые библиотечные функции
- •8.4 Структуры
- •8.5 Объявление структур
- •8.6 Битовые поля
- •8.7 Объединения
- •9 Функции
- •9.1 Функция для сложения чисел
- •9.2 Ссылки
- •9.3 Выходные параметры функции
- •9.4 Ссылка — возвращаемое значение
- •9.5 Одномерные массивы как параметры
- •9.6 Двумерные массивы как параметры
- •10 Еще о функциях
- •10.1 Параметры по умолчанию
- •10.2 Произвольное число параметров
- •10.3 Неиспользуемые параметры
- •10.4 Перегруженные функции
- •10.5 Указатель на функцию
- •Void error(char* p) { /*тело ф-ции*/} ,
- •10.6 Спецификатор inline
- •Inline void error(char* p) { /*тело ф-ции*/}
- •10.7 Макросы
- •11 Ввод и вывод
- •11.1 Разновидности ввода и вывода
- •11.2 Открытие и закрытие потока
- •11.3 Ввод и вывод символов
- •11.4 Ввод и вывод строк
- •11.5 Ввод и вывод записей
- •11.6 Управление указателем файла
- •11.7 Состояние потока
- •11.8 Форматированный вывод
- •11.9 Форматированный ввод
- •11.10 Другие функции форматного ввода и вывода
- •12 Уточнение понятий языка
- •12.1 Объявление, определение, инициализация
- •12.2 Область видимости и время жизни
- •12.3 Типы
- •12.4 Производные типы
- •12.5 Числовые константы
- •12.6 Именные константы
- •12.7 Перечисление
- •12.8 Порядок вычисления выражений
- •13 Операции и операторы
- •13.1 Сводка операций
- •13.2 Сводка операторов
- •13.3 Оператор выражения
- •13.4 Оператор switch
- •13.5 Операторы break и continue
- •13.6 Оператор goto и метки
- •14 Классы
- •14.1 Определение класса
- •14.2 Инкапсуляция
- •14.3 Конструктор
- •14.4 Деструктор
- •14.5 Пример класса — список в динамической памяти
- •14.6 Указатель на себя
- •15 Производные классы
- •15.1 Простое наследование
- •15.2 Списки инициализации
- •15.3 Ключи доступа
- •15.4 Виртуальные функции
- •15.5 Реализация виртуальных функций
- •15.6 Полиморфизм
- •16 Еще о класcах
- •16.1 Статические элементы
- •16.2 Друзья класса
- •16.3 Перегрузка операций
- •16.4 Множественное наследование
- •17.1 Библиотека потоков
- •17.2 Предопределенные потоки
- •17.3 Операции помещения в поток и извлечения из потока
- •17.4 Форматирующие функции-элементы
- •17.5 Флаги форматирования
- •17.6 Манипуляторы
- •18 Еще о потоках
- •18.1 Ошибки потока
- •18.2 Опрос состояния потока
- •18.3 Файловый ввод-вывод с применением потоков
- •18.4 Конструкторы файловых потоков
- •18.5 Функции для открытия и закрытия файлов
- •18.6 Замена буфера потока
- •18.7 Текстовый и бинарный ввод-вывод
- •18.8 Бесформатный ввод и вывод
- •18.9. Часто применяемые функции потока
- •18.10 Форматирование в памяти
- •18.11 Дополнительные возможности ostrstream
- •19 Шаблоны
- •19.1 Шаблоны функций
- •19.2 Перегрузка и специализация шаблонов
- •19.3 Шаблоны классов
- •20 Директивы препроцесора
- •20.1 Директива #define
- •20.2 Директива #include
- •20.3 Условная компиляция
- •20.4 Директива #error
- •20.5 Директива #line
- •20.6 Директива #pragma
- •21.1 Адресация памяти
- •21.2 Модели памяти
- •21.3 Спецификация указателей
- •Int near* var_name;
- •Int* near var_name;
- •21.4 Макросы для указателей
- •21.5 Модификаторы переменных
- •21.6 Модификаторы функций
- •21.7 Соглашения о вызове
- •21.8 Встроенный код ассемблера
- •21.9 Псевдорегистры
- •Литература
- •Содержание
- •21.8 Встроенный код ассемблера ........................................................
- •21.9 Псевдорегистры ............................................................................
5 Рекурсивные алгоритмы
5.1 Простая рекурсия
В процессе разработки алгоритма может случиться, что какая-то его часть сводится к выполнению этого же алгоритма, но над более простыми данными. Такой алгоритм называется рекурсивным.
Вспомним алгоритм двоичного поиска из раздела 3.
Начало
Установить левую границу области поиска, Left = 1;
Установить правую границу области поиска, Right = n;
Пока левая граница не больше правой, Left <= Right, повторять
Начало
Найти середину области поиска, Middle = (Left + Right) / 2;
Если X < M[Middle], то
изменить правую границу, Right = Middle-1;
Если X > M[Middle], то
изменить левую границу, Left = Middle+1;
Если X = M[Middle], то
вывести значение Middle и закончить работу;
Конец
Сообщить, что числа X в массиве нет
Конец
Сформулируем его в рекурсивной форме.
Процедура Двоичный Поиск (Left, Right)
Начало
Еслилевая граница не больше правой, Left <= Right,то
Начало
Найти середину области поиска, Middle = (Left + Right) / 2;
Если X < M[Middle],тоДвоичныйПоиск (Left, Middle-1);
ЕслиX > M[Middle],тоДвоичныйПоиск (Middle+1, Right);
Если X = M[Middle],товывести значение Middle;
Конец иначе сообщить, что числа X в массиве нет;
Конец
Чтобы разыскать число X в массиве M надо выполнить оператор процедуры
Двоичный Поиск (1, n);
где n — количество элементов массива М.
Сравнивая два алгоритма, мы замечаем, что в рекурсивном алгоритме нет оператора цикла, следовательно, рекурсию можно рассматривать как альтернативу алгоритмической конструкции повторения, как еще один способ организовать повторение в программе. Не совсем точно, но наглядно работу рекурсивного алгоритма можно изобразить в виде поочередного запуска все новых и новых копий одного и того же алгоритма.
Как и в операторе цикла, при неправильной организации программы возможны бесконечные самовызовы алгоритма. В нашем примере это не происходит, так как область поиска с каждым вызовом сокращается не менее, чем вдвое, а это не может продолжаться долго.
Количество одновременно работающих копий называется глубиной рекурсии. Для алгоритма двоичного поиска максимальная глубина рекурсии равна логарифму двоичному от n. Придумывая рекурсивные алгоритмы, надо следить, чтобы глубина рекурсии не была большой, поскольку для выполнения таких алгоритмов понадобится много памяти.
Мы рассмотрели самый простой вид рекурсии, когда в процессе выполнения алгоритм вызывает себя ровно один раз (хотя в алгоритме два оператора процедуры, выполняется всегда лишь один из них). Такой вид рекурсии легко заменяется оператором цикла, и нам он понадобился лишь для знакомства с этим приемом программирования.
5.2 Ханойские башни
Программистам давно известна следующая задача.
З а д а ч а.
Имеется три стержня: 1-й, 2-й и 3-й. На первом стержне находится пирамида из n дисков, внизу — самый большой, на нем — меньший, затем еще меньший и т.д., вверху — самый маленький. Требуется переместить все диски с 1-го стержня на 3-й, перекладывая их по одному таким образом, чтобы ни на каком стержне меньший диск не оказался ниже большего. Говоря точнее, нужно предложить алгоритм такого перемещения.
Р е ш е н и е.
Искомый алгоритм, должен переместить пирамиду с первого стержня на третий, соблюдая известные из условия правила. Поскольку алгоритм будет рекурсивным, нужно заранее позаботиться о его названии и параметрах. Назовем его “Ханой”, а параметрами сделаем 3 числа: N — количество дисков в пирамиде, s — номер стержня-источника и d — номер стержня-приемника:
Процедура Ханой (N, s, d) .
Чтобы переместить пирамиду из N дисков со стержня s на стержень d, используя в качестве промежуточного, пустой стержень m, достаточно сделать следующее.
Начало
Переместить пирамиду из N-1 диска с s на m.
Перенести один оставшийся диск с s на d.
Переместить пирамиду из N-1 диска с m на d.
Конец
Разумеется, все эти действия имеют смысл, если N > 0. Что делать, когда N = 0?
Ответ — ничего. Для переноса пустой пирамиды никаких дисков перекладывать не нужно.
Поскольку мы говорили о перекладывании дисков не с первого стержня на третий, а со стержня s на стержень d, не лишним будет узнать, какой номер у промежуточного стержня. Иными словами, как определить значение m по значениям s и d?
Для этого послужит простая формула: m = 6 - s - d .
И, наконец, как заставить компьютер выполнить пункт 2, т.е. переложить один диск с s на d?
Компьютер не робот и перекладывать диски не будет. Ему достаточно вывести на экран или принтер номер стержня-источника и номер стержня-приемника. Последовательность таких пар, как запись шахматной партии, покажет все манипуляции с дисками..
Теперь можно переписать решение набело.
Процедура Ханой (N, s, d)
Начало
Если N > 0, то Начало
m = 6 - s - d;
Ханой (N-1, s, m);
Напечатать s и d;
Ханой (N-1, m, d).
Конец
Конец
Чтобы переложить n дисков с первого стержня на третий, надо выполнить оператор процедуры
Ханой (n, 1, 3) .