Файл: Практикум по информатике рекомендовано в качестве учебного пособия.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 730
Скачиваний: 14
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
95
ЛАБОРАТОРНАЯ РАБОТА № 14.
РЕКУРСИЯ
Цель лабораторной работы:
изучить рекурсивные методы, напи- сать программу с использованием рекурсии.
14.1. Общие понятия
Рекурсивным
называют метод, если он вызывает сам себя в качестве вспомогательного. В основе рекурсивного метода лежит так называемое
рекурсивное определение
какого-либо понятия. Классическим примером рекурсивного метода является метод, вычисляющий факториал.
Из курса математики известно, что 0!
=
1!
=
1,
n
!
=
1*2*3…*
n
С другой стороны
n
!
=
(
n
–
1)!*
n
. Таким образом, известны два частных случая параметра
n
, а именно
n
=
0 и
n
=
1, при которых мы без каких- либо дополнительных вычислений можем определить значение факто- риала. Во всех остальных случаях, то есть для
n
>
1, значение факториа- ла может быть вычислено через значение факториала для параметра
n
–
1. Таким образом, рекурсивный метод будет иметь вид: long
F(
int n)
{
// Дошли до 0 или 1?
if
(n == 0 || n == 1)
// Нерекурсивная ветвь return
1; else
// Шаг рекурсии: повторный вызов
// метода с другим параметром return n * F(n ‐ 1);
}
// Пример вызова рекурсивного метода long f = F(3);
MessageBox
.Show(f.ToString());
Рассмотрим работу описанного выше рекурсивного метода для n
=
3.
Первый вызов метода осуществляется из основной программы, в нашем случае командой f = F(3)
. Этап вхождения в рекурсию обо- значим стрелками с подписью «шаг». Он продолжается до тех пор, пока значение переменной n
не становится равным 1. После этого начинается
96 выход из рекурсии (стрелки с подписью «возврат»). В результате вы- числений получается, что
F(3) = 3 * 2 * 1
Рис. 14.1. Структура рекурсивных вызовов
Рассмотренный вид рекурсии называют прямой. Метод с прямой рекурсией обычно содержит следующую структуру: if
(<условие>)
<оператор>; else
<вызов этого же метода с другими параметрами>;
В качестве
<условия>
обычно записываются некоторые граничные случаи параметров, передаваемых рекурсивному методу, при которых результат его работы заранее известен, поэтому далее следует простой оператор или блок, а в ветви else происходит рекурсивный вызов дан- ного метода с другими параметрами.
Что необходимо знать для реализации рекурсивного процесса?
Со входом в рекурсию осуществляется вызов метода, а для выхода не- обходимо помнить точку возврата, т. е. то место программы, откуда мы пришли и куда нам нужно будет возвратиться после завершения метода.
Место хранения точек возврата называется
стеком вызовов
, и для него выделяется определенная область оперативной памяти. В этом стеке за- поминаются не только адреса точек возврата, но и копии значений всех параметров. По этим копиям восстанавливается при возврате вызываю- щий метод. При развертывании рекурсии за счет создания копий пара-
97 метров возможно переполнение стека. Это является основным недос- татком рекурсивного метода. С другой стороны, рекурсивные методы позволяют перейти к более компактной записи алгоритма.
Следует понимать, что любой рекурсивный метод можно преобра- зовать в обычный метод с использованием циклов. И практически лю- бой метод можно преобразовать в рекурсивный, если выявить рекур- рентное соотношение между вычисляемыми в методе значениями.
Рассмотрим пример кода для создания набора
самоподобных
структур
. В нашем случае это будет набор увеличивающихся квадра- тов (рис. 14.2).
Рис. 14.2. Набор квадратов
При проектировании данной программы были созданы два метода: private void
MyDraw(
Graphics g, int
N, int x, int y)
{ if
(N == 0) return
; else
{
// Отрисовка прямоугольника g.DrawRectangle(
new
Pen
(
Brushes
.Blue, 2),
0, 0, x, y);
// Увеличение x и y на 50
x += 50; y += 50;
N‐‐;
// Рекурсивный вызов с новыми параметрами
MyDraw(g, N, x, y);
}
}
98 private void
Form1_Paint(
object sender,
PaintEventArgs e)
{
Graphics g = e.Graphics;
// Первый вызов метода и вход в рекурсию
MyDraw(g, 7, 50, 50);
}
Координаты левого верхнего угла всех прямоугольников неизмен- ны и находятся в точке (0, 0). Поэтому в параметрах метода
MyDraw дос- таточно передавать
x
и
y
для правого нижнего угла. Также в параметрах передается
N
, значение которой определяет текущую вложенность ре- курсии (сколько вызовов рекурсии еще будет).
1 ... 4 5 6 7 8 9 10 11 12
14.2. Формирование задержки с помощью таймера
Графические конструкции иногда требуется рассматривать дина- мически в процессе их построения. Поэтому зачастую используется та- кая схема вывода графики:
1.
Вывод графического элемента.
2.
Задержка на n миллисекунд.
3.
Повторение 1 и 2 этапа до вывода всех графических элементов.
Реализация задержки с помощью таймера возможна следующим способом:
// Глобальное поле flag private bool flag = false
;
// Далее следует часть программы,
// где необходимо организовать задержку
// Включаем таймер timer1.Enabled = true
;
// Устанавливаем flag в значение true flag = true
;
// Организуем бесконечный цикл while
(flag);
// Выключаем таймер после выхода из цикла timer1.Enabled = false
;
// Обработчик тика таймера private void timer1_Tick_1(
object sender,
EventArgs e)
{
// Сбрасываем flag в значение false flag = false
;
}
99
Идея данного подхода заключается в организации бесконечного цик- ла, который будет проверять значение некого флага. Однако значение фла- га может измениться при наступлении события
Tick таймера, то есть через заданный в таймере промежуток времени. Однако бесконечный цикл, опи- санный выше, останется бесконечным, и программа просто зависнет. В чем же дело? Дело в том, что при такой организации цикла программа не мо- жет опросить очередь сообщений, в которое и будет поступать, в том чис- ле, и событие
Tick от таймера. Тем самым мы не попадем никогда в обра- ботчик события timer1_Tick_1
. Чтобы решить данную проблему, надо в теле цикла написать
Application.DoEvents()
, что фактически будет за- ставлять приложение опрашивать очередь сообщений и в свою очередь приведет к срабатыванию обработчика события timer1_Tick_1
Перед выполнением индивидуального задания по лабораторной ра- боте разработайте приложение, строящее ряд увеличивающихся квадра- тов (рис. 14.2). Квадраты выводятся последовательно через одну секунду.
Индивидуальное задание
1.
Напишите приложение, которое строит ряд окружностей.
Центр окружностей совпадает с центром экрана. Число окружностей за- дается при первом вызове рекурсивного метода.
2.
Напишите приложение, которое строит ряд квадратов. Центр квадратов совпадает с центром экрана. Число квадратов задается при первом вызове рекурсивного метода.
100 3.
Напишите приложение, которое строит ряд окружностей по диагонали. Число окружностей задается при первом вызове рекур- сивного метода.
4.
Напишите приложение, которое строит ряд увеличивающихся окружностей по диагонали. Число окружностей задается при первом вызове рекурсивного метода.
5.
Напишите приложение, которое строит ряд окружностей, цен- тры которых лежат на окружности. Число окружностей задается при первом вызове рекурсивного метода.
101 6.
Напишите приложение, которое строит ряд квадратов, центры которых лежат на окружности. Число квадратов задается при первом вызове рекурсивного метода.
7.
Напишите приложение, которое строит ряд увеличивающихся окружностей, центры которых лежат на окружности. Число окружно- стей задается при первом вызове рекурсивного метода.
8.
Напишите приложение, которое строит ряд увеличивающихся окружностей, центры которых лежат на спирали. Число окружностей задается при первом вызове рекурсивного метода.
9.
Вычислить, используя рекурсию, выражение:
9 4
8 3
7 2
6
102 10.
Напишите приложение, которое строит ряд окружностей. Чис- ло окружностей удваивается на каждом шаге (в рекурсивном методе происходит два рекурсивных вызова). Центры окружностей выбираются каждый раз произвольно (случайно). Линии связывают центры окруж- ностей «предка» и «порожденных» от нее. Число рекурсий задается при первом вызове рекурсивного метода.
11.
Напишите приложение, которое строит ряд увеличивающихся ок- ружностей. Число окружностей удваивается на каждом шаге (в рекурсивном методе происходит два рекурсивных вызова). Центры окружностей выбира- ются каждый раз произвольно (случайно). Толщина линий также увеличива- ется. Число рекурсий задается при первом вызове рекурсивного метода.
12.
Напишите приложение, которое строит ряд уменьшающихся окружностей. Число окружностей удваивается на каждом шаге (в рекур- сивном методе происходит два рекурсивных вызова). Число рекурсий задается при первом вызове рекурсивного метода.
103 13. Напишите приложение, которое строит приведенное ниже изо- бражение. Число рекурсий задается при первом вызове рекурсивного метода. На каждом шаге число окружностей увеличивается в четыре раза (в рекурсивном методе происходит четыре рекурсивных вызова).
14. Постройте ковер Серпинского.
15. Разработайте программу построения треугольника Серпинского.
16. Реализуйте программу визуализации построения первых n ша- гов множества Кантора.
104 17.
Реализуйте рекурсивный алгоритм вычисления n-го числа Фи- боначчи.
18.
Реализуйте рекурсивный алгоритм вычисления n-го факториала.
19.
Реализуйте рекурсивный подсчет суммы всех элементов масси- ва. Сумма элементов массива считается по следующему алгоритму: массив делится пополам, подсчитываются и складываются суммы эле- ментов в каждой половине. Сумма элементов в половине массива под- считывается по тому же алгоритму, то есть снова путем деления попо- лам. Деления происходят, пока в получившихся кусках массива не окажется по одному элементу и вычисление суммы, соответственно, не станет тривиальным.
20.
Дана монотонная последовательность, в которой каждое нату- ральное число k встречается ровно k раз: 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, …
По данному натуральному n выведите первые n членов этой последова- тельности.
105
ЛАБОРАТОРНАЯ РАБОТА № 15.
СОРТИРОВКА И ПОИСК
Цель лабораторной работы:
освоить основные алгоритмы сорти- ровки, написать программу с использованием этих алгоритмов.
15.1. Общие понятия
Сортировка
– это процесс упорядочения элементов массива или списка по возрастанию или убыванию.
Существует много алгоритмов сортировки, отличающихся по ряду характеристик:
Время работы
, или
вычислительная сложность
, – количество опе- раций, затрачиваемых алгоритмом. Обычно оценивается худший сценарий, когда исходный массив оказывается максимально неупо- рядочен с точки зрения алгоритма.
Затрачиваемая память
(помимо исходного массива) – некоторые ал- горитмы требуют выделения дополнительной памяти для временного хранения данных или формирования нового выходного массива.
Кроме того, алгоритмы можно разделить по типу доступа к данным:
Алгоритмы
внутренней сортировки
применяются для сортировки данных, целиком находящихся в оперативной памяти.
Алгоритмы
внешней сортировки
оперируют данными, не поме- щающимися в оперативную память. Такие алгоритмы используют внешнюю память, доступ к которой требует существенно большего времени, поэтому требуются специальные алгоритмические реше- ния, чтобы каждый элемент использовался алгоритмом минималь- ное количество раз.
15.2. Алгоритмы сортировки. Метод пузырька
Данный алгоритм является достаточно простым и поэтому получил широкое распространение. Вычислительная сложность алгоритма квад- ратичная – O(n
2
), поэтому алгоритм эффективен только на небольших массивах данных.
Алгоритм проходит все элементы массива и попарно сравнивает их друг с другом. Если порядок сравниваемых элементов неверный, алго- ритм меняет элементы местами:
// Сортировка пузырьком
106 void
BubbleSort(
ref int
[] Array)
{
// Перебираем элементы массива (без последнего)
for
(
int i = 0; i < Array.Length – 1; i++)
// Перебираем все элементы справа от i for
(
int j = i + 1; j < Array.Length; j++)
// Правильный ли порядок элементов?
if
(Array[i] > Array[j])
{
// Нет – меняем порядок int t = Array[i];
Array[i] = Array[j];
Array[j] = t;
}
}
15.3. Сортировка выбором
Сортировка выбором имеет квадратичную сложность O(n
2
) и, как и предыдущий метод пузырька, эффективен лишь на небольших объе- мах данных.
Алгоритм находит номер минимального значения в текущем спи- ске, меняет этот элемент со значением первой неотсортированной по- зиции (если минимальный элемент не находится на данной позиции), а затем сортирует хвост списка, исключив из рассмотрения уже отсор- тированные элементы:
// Сортировка выбором void
SelectionSort(
ref int
[] Array)
{
// Перебираем все элементы массива (безпоследнего)
// i – позиция первого неотсортированного элемента for
(
int i = 0; i < Array.Length – 1; i++)
{
// Позиция минимального элемента справа от i int min = i;
// Перебираем все элементы справа от i for
(
int j = i + 1; j < Array.Length; j++)
// Меньше ли очередной элемент минимального?
if
(Array[j] < Array[min])
// Да – теперь это минимальный элемент min = j;
// Минимальный элемент не первый?
// Меняем местами!
if
(min != i)
{