Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
4 основи програмування книга.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
1.77 Mб
Скачать

I стержень j стержень 6-I-j стержень

Відмітимо тепер, що на стержні J лежить кільце з найбільшим діаметром, тобто цей стержень можна використовувати без порушення обмежень, пов’язаних з величинами діаметрів. Тому можна тепер переставити всю піраміду з N-1 кільця з стержня 6-I-J на J, (рис. 9.4) і задача розв’язана!

Рис. 9.4

n-1 кільце

I стержень j стержень 6-I-j стержень

Відмітивши, що при N = 1 задача розв’язується за допомогою процедури Step (I, J), опишемо процедуру HanojTower (N,I,J):

Procedure HanojTower(N, I, J: Integer);

Begin

If N = 1

then Step(I, J)

else begin

HanojTower(N-1, I, 6-I-J);

Step(I, J);

HanojTower(N-1, 6-I-J, J)

end

End;

Процедуру Step(I, J) можна реалізувати, використовуючи представлення даних у масиві Rings[1..N, 1..3] і графічну візуалізацію переміщення кілець.

Визначимо складність алгоритму за часом C(n) (кількість кроків-викликів Step), вписавши рекурентне співвідношення: С(n) = 2C(n-1) + 1

Легко тепер довести, що С(n) = 2n - 1. Доведено, що ця кількість кроків є мінімально можливою, тому наш алгоритм оптимальний.

Приклад 9.5. Лінійні діафантові рівняння

Перелічити всі невід’ємні цілі розв’язки лінійного рівняння a1x1 + a2x2 + ... + anxn = b з цілими додатними коефіцієнтами.

Як і в попередніх прикладах, опишемо алгоритм рекурсивно, здійснивши зведення вихідної задачі до задачі меншого розміру.

Перепишемо вихідне рівняння у виді:

a1 x1 + a2 x 2 + ... + a n-1 x n-1 = b - a n x n

Організуємо перелік всіляких значень xn, при яких права частина b - a n x n > 0. xn = 0, 1, ... y, де y = b div an. Тоді перші n-1 компонент розв’язка (x1, ... , x n-1, x n) вихідного рівняння – розв’язок рівняння

a1 x1 + a2 x2 + ... + a n-1 x n-1 = b - a n x n .

Таким чином ми звели розв’язок вихідного рівняння до розв’язку у+1 рівняння з n-1 невідомим, і, отже, можемо реалізувати алгоритм рекурсивно. Визначимо умови виходу з рекурсії:

при b = 0 існує єдиний розв’язок – (0, 0, ..., 0)

при n = 1 якщо b mod a1 = 0 то x1 = b div a1 інакше розв’язків немає.

Таким чином, виходити з рекурсивних обчислень треба в двох (крайніх) випадках. Ми встановили і параметри процедури: n і b.

Procedure Solution(n, b : integer);

Var

i, j, y, z : Integer;

Begin

If b = 0

then begin

For j := 1 to n do X[j] := 0;

WriteSolution

end

else If (n = 1) and (b mod a[1] = 0)

then begin

X[1]:= b div a[1];

WriteSolution

end

else If n > 1

then begin

z := a[n];

y := b div z;

For i := 0 to y do begin

X[n] := i;

Solution(n - 1, b - z*i)

end

end

End;

Program AllSolutions;

Const

n = 4;

Type

Vector = array[1..n] of Integer;

Var

a, X : Vector;

b : Integer;

i : Integer;

{Procedure WriteSolution друкує розв’язок X[1..n] }

{Procedure Solution}

Begin

{Введення масиву коефіцієнтів a[1..n] і св. члена b}

Solution(n, b)

End;

Приклад 9.6. Піднесення числа до натурального степеня

Піднести дійсне число а у натуральний степінь n.

Раніше ця задача була розв’язана методом послідовного домноження результату на а. Однак її можна розв’язати ефективніше, якщо застосувати метод половинного ділення степеня n. Саме: an = (a n/2) )2. Оскільки число n не обов’язково парне, формулу треба уточнити:

a^n = (a n div 2) 2 * a n mod 2. Доповнивши визначення a n визначенням a1 = a і замінивши домноження на a n mod 2 розбором випадків парний-непарний, отримаємо:

Function CardPower(a : Real; n : Integer) : Real;

var

b : Real;

Begin

If n = 1

then CardPower := a

else begin

b := Sqr(CardPower(a, n div 2));

If n mod 2 = 0

then CardPower := b

else CardPower := a*b

end

End;

Доведіть, що функція CardPower використовує не більш O(log2n) множень і піднесень у квадрат.

Приклад 9.7. Перетин зростаючих послідовностей

Нехай A = [a1, a2,...,an] і B = [b1, b2, ... , bm] – дві зростаючі числові послідовності. Перетином цих послідовностей називається зростаюча послідовність С = [с1, с2,..., сk], що складається з тих і тільки тих чисел, які належать обом послідовностям. Треба знайти C = AB.

Зведемо задачу до кількох підзадач, більш простих, ніж вихідна. Поділимо для цього вихідні послідовності на частини

A = [a1]A2; B = [b1]  B2, де A 2 = [a 2,...,a n], B 2 = [b 2,...,b n]

Тоді

AB = (a1  A2) (b1 + B2) = a1b1  a1B2  A2b1  A2B2

(Дужки в позначеннях одноелементних множин опущені)

Так як послідовності A і B зростають, маємо:

Якщо a1 = b1 то AB = a1b1  A2B2 = a1  A2B2

Якщо a1 < b1 то AB = b1A2  A2B2 = A2B

Якщо a1 > b1 то AB = a1B2  A2B2 = AB2

Ці відношення зводять вихідну задачу до задач менших розмірів, тому їх можна використовувати для рекурсивного описання обчислень.

Процедура Intersect пошуку перетину залежить від параметрів i та j – номерів початкових елементів підпослідовностей A[i..m] і B[j..n], представлених масивами A[1..m] і B[1..n]. Знайдений елемент перетину роздруковується.

Procedure Intersect(i, j : Integer);

Begin

If (i <= m) and (j <= n)

then If a[i] = b[j]

then begin

Write(a[i]);

Intersect(i+1, j+1)

end

else If a[i] < b[j]

then Intersect(i+1, j)

else Intersect(i, j+1)

End;