Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Lab8.doc
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
189.44 Кб
Скачать

Лабораторна робота №8 Тема: рекурсивні функції, вказівники на функції в алгоритмічній мові с

1. Мета роботи

Мета роботи – поглиблене вивчення можливостей функцій з використанням рекурсії та вказівників.

2. Теоретичні відомості

2.1. Рекурсивні функції

Рекурсивним називається такий спосіб реалізації функції., коли функція може звертатися сама до себе. У рекурсивній функції повинні виконуватися наступні правила:

- при кожному виклику такої функції в неї повинні передаватися модифіковані дані;

- на якомусь етапі повинен бути припинений подальший виклик даної функції. Рекурсивний процес повинен крок за кроком так спрощувати завдання, щоб зрештою для нього з'явилося не рекурсивне рішення. Тут легко припуститися помилки, що полягає в тім, що функція буде послідовно викликати саму себе нескінченно довго;

після завершення кожного виклику рекурсивної функції в точку повернення повинен передаватися деякий результат для подальшого використання.

/* Приклад: використання рекурсивної функції. Припустимо, що задано два числа a і b. Необхідно більше із цих чисел розділити на менше. */

#include <iostream.h>

void main (void)

{

float a, b;

float recurs (float, float) ;

cout <<”Введіть значення a і b " << endl;

cin >> a >> b // Ввід значень

cout << " a= " << a << " b = " << b;

cout << " Результат ділення: " << recurs (a, b);

// Прототип функції

float recurs (float a, float b) / * Рекурсивна функція */

if(a<b) return recurs(b,a); /* Якщо а < b, те параметри міняються

/*місцями (рекурсивний виклик) */

else return a/b; /* Вихід з рекурсії */

}

У функцію recurs передаються значення двох дійсних чисел. Припустимо спочатку, що умова а < b не виконується. У цьому випадку при першому ж звертанні до recurs має місце нерекурсивний вихід. Нехай тепер умова а < b виконується. Тоді функція recurs звертається до самої себе, однак параметри а і b у виклику міняються місцями, тобто параметру а у функції передається більше значення (b), а параметру b - менше значення (a). Після чого відбувається вихід з рекурсій і в main буде переданий результат.

Рекурсія нерідко входить у визначення математичних алгоритмів.

// Приклад: обчислення факторіала за допомогою рекурсії:

//

#jinclude <iostream.h>

void main (void)

long fact (int.); /* Прототип функції */

int n;

cout << "Введіть n" << endl;

cin >> n; /* Ввід значення */

cout << " Факторіал " << n << “ = “ << fact (n);

}

long fact (int n) /* Рекурсивна функція */

{

return (n) ? (long) n * fact(n-1):1;

}

Розглянемо процес виконання цієї програми. Функція main викликає функцію fact і передає параметр n Якщо n == 0, то повернеться значення 1. Інакше функція fact(n) повинна викликати fact( n-l). Так буде тривати до виклику fact(0), що поверне значення 1 у точку виклику fact(l).

Покажемо порядок виконання кожного виклику функції fact і кожного оператора return.

Порядок викликів:

Порядок повернень:

fact (n)

return{1)

fact ( n-l)

return(1*0!)

. . .

fact (l)

return (( n-l)*( n-2) !)

fact (0)

return(n*( n-l)!)

Для розуміння механізму виконання рекурсивної функції запишемо її в такий спосіб:

long fact ( int n )

{

if (n) return (long) n * fact ( n-1); // IP*

else return 1;

}

Слово else можна й опустити.

Розглянемо стан стека у випадку обчислення 3!

Головний модуль (main) запише в стек число 3 і адресу повернення (позначимо його IP main) для продовження виконання програми, тобто повернення в точку виклику - у функцію cout << "Факторіал" << n << "=" << fact(n); - для виводу результату.

При кожному наступному виклику функції (long) n * fact ( n-1) обчислення даного виразу залишається незавершеним, у стек послідовно записуються число n (2, 1, 0) і адреса повернення на завершення обчислення зазначеного вираpe. Це буде та ж сама адреса, позначимо її умовно IP* і далі (дивися зображення стану стека) зазначено, яке n було на вході у функцію при збереженні даного IP*.

Нижче зображений стан стека при обчисленні 3! В таблиці 1: bp (base pointer) – позначення одного з регістрів процесора; IP – регістра лічильника команд (instruction pointer).

Ліворуч від стека зазначена група операторів, що генерує черговий виклик функції. При виклику в стек містяться дані, що приводить до його нарощуванню (від старших адрес до молодших, тобто зменшується вміст регістра sp). Праворуч від зображення стека показана послідовність формування результату (виконання оператора return (long) n * fact ( n-1);). При цьому виконуються команди вилучення інформації зі стека, тобто дані в цьому випадку вибираються і стек буде зменшуватися. Вказівник вершини стека (вмістиме регістра sp) тепер буде збільшуватися, число 3 зі стека видалить функція main (наростить sp на довжину типу int).

Не рекурсивна вітка return 1; видобуде зі стека верхню адреса повернення (IP* ----0), і далі буде реалізовуватися процес згортання рекурсії, формування кінцевого результату.

Таблиця 1

Послідовні

c ть виклику

Молодші

(вершина

адреси області стека

стека)

П ослідовність

п овернення

bp

sp- вказівник

в ершини стека

IP* 0

1*fact(0)

0

0!=1=fact(0)

bp

2*fact(1

IP* 1

1*0!=1=fact(1)

1

bp

3*fact(2)

IP* 2

2*1!=2=fact(2)

2

bp

IP-main 3

printf(" ", n, fact(3));

3

старші адреси

області стека

3*2!=6=fact(3), повернення у функцію main (),

вивід результату

/* Приклад: рекурсивна процедура обрахування чисел, що належать послідовності Фібоначчі. */

#include <iostream.h>

void main (void)

{

long n; int i;

// Прототип функції

long fib(long);

do{

do{

cout << " n-? “ << endl;

} while (cin >> n, n<0);

cout << " Fib(" << n << " ) = " << fib(n);

cout << " Вихід- 0 ";

} while (cin >> i, i);

}

long fib (long n) // Реалізація функції

{

. if ( n==0 | | n== 1) return 1;

return fib( n-l)+fib( n-2);

}

Функція main також може бути рекурсивною в Turbo C, тобто розширення файлу з вихідною програмою повинне бути '.с', а не '.срр'. Наприклад: L10_041.С.

/* Приклад: не оголошуючи масиву, ввести групу даних, вивести їхню загальну кількість, порядкові номери i значення чисел у зворотному порядку. Ознака кінця вводу чисел - 0. */

//

# include <iostream.h>

void main (void)

{

static int n; int m, l=0; // Можна static n=0;

static int k;

/* Дані із класом зберігання static зберігаються в пам'яті на весь час виконання програми */

cout << "Число- ? " << endl;

cin >> m;

if ( m ) { n++; l++;

cout << " Виклик : n=” << n << " l= " << l << endl;

main ( );

/* Запис main без дужок не забезпечує рекурсивного виклику, але й не дає помилки компіляції*/

k++;

cout<<"Вихід: номер=" << k << " число=" << m << " 1= " << l << " n= " << n;

}

Результат роботи програми:

число-? 4 виклик: n=0,l=1

число-? 3 виклик: n=1, l=1

число-? 2 виклик: n=2,1=1

число-? 1 виклик: n=3,1=1

число-? 0 виклик: n=4,1=1

вихід: номер=1 число=0 l=0 n=4

вихід: номер=2 число=1 l=1 n=4

вихід: номер=3 число=2 1=1 n=4

вихід: номер=4 число=3 1=1 n=4

вихід: номер=5 число=4 1=1 n=4.

Наступний характерний приклад рекурсивної функції - це швидке сортування (сортування Хоара). Для заданого масиву вибирається один елемент, що розбиває інші елементи на дві підмножини - те, що менше, і те, що не менше його. Та ж процедура рекурсивно застосовується й до двох отриманих підмножин. Якщо в підмножині залишається менш двох елементів, то рекурсія завершується.

/*qsort сортує v[left] . , ,v[right] по зростанню*/

void qsort(int v[], int left, int right)

{

int i, last;

void swap (int v[],int i, int j);

if (left>=right) /* Нічого не робиться, якщо */

return; /*в масиві менше двох елементів */

swap (v, left, (left+right) /2) ; /* елемент, що ділить, */

last=left; /* Переноситься в v[0] */

for (i = left+1; i <= right; i++) /* Ділення на частини */

if (v[i] < v[left]>

swap(v, ++last, i) ;

swap (v, left, last) ; /* Заміняємо елемент, що ділить, */

qsort(v,left, last-l) ;

qsort (v, last+1, right) ;

}

void swap (int v[] , int i, int j )

// swap міняє місцями v[i] і v[j]

{

int temp;

temp=v [ i ] ;

v[j]=temp;

}

Рекурсивна програма не забезпечує ні ощадливої витрати пам'яті, ні швидкодії, але в порівнянні з деякими нерекурсивними часто коротше, а також легше для розуміння. Такі програми зручні для обробки структур даних типу дерев.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]