Professional Documents
Culture Documents
АтаП-1 - Лекція 6. Циклічні процеси
АтаП-1 - Лекція 6. Циклічні процеси
Оператори циклів з
передумовою і післяумовою
Поняття циклу. Розробка й програмування циклічного процесу. Оператор циклу з передумовою. Оператор цик-
лу з післяумовою. Деякі корисні алгоритми, в яких використано цикли з передумовою і післяумовою
Поняття циклу
Досить часто на практиці виникають задачі, при розв’язуванні яких одну
дію або деяку послідовність дій треба повторити кілька разів. Кількість повто-
рень може бути визначена наперед, а також може визначатися умовою, яка за-
лежить від розрахованих у процесі обчислення значень або від введеної корис-
тувачем інформації. Щоб виконати певний оператор або групу операторів по-
вторно, використовують цикли.
Фрагмент алгоритму з командами (командою), які можуть виконуватися
більше одного разу залежно від виконання певної умови, називають циклом
(повторенням, структурою повторення, циклічною структурою). Команду або
групу команд, виконання яких повторюється при кожному проходженні циклу
(кожній ітерації), називають тілом циклу. Алгоритм, який містить цикли, нази-
вають алгоритмом з циклами (алгоритмом з повтореннями, циклічним алгори-
тмом, ітераційним алгоритмом). У циклічному алгоритмі одна і та ж послідов-
ність дій виконується кілька разів, доки не виконається певна умова.
Розрізняють цикли з наперед відомою і наперед невідомою кількістю про-
ходів (ітерацій). Цикл називають детермінованим, якщо кількість повторень
тіла циклу наперед відома. Цикл називають ітераційним, якщо кількість повто-
рень тіла циклу наперед невідома і залежить від значень параметрів (деяких
змінних), які беруть участь в обчисленнях.
При логічних (семантичних) помилках в циклічних алгоритмах може ви-
никнути ситуація, коли тіло циклу виконується раз за разом, а умова припинен-
ня циклу не настає. Таку ситуацію називають зациклюванням.
У циклічних алгоритмах, крім циклічних блоків, можуть міститися лінійні
фрагменти і розгалуження, будь-який цикл також може містити лінійні фрагме-
нти і розгалуження, а також вкладений у своє тіло інший цикл або навіть кілька
циклів. Використання циклів дає можливість коротко записати алгоритм, який
здійснює велику кількість дій. Дії, які повторюються в циклі, є однотипними.
Використання циклів суттєво скорочує обсяг алгоритму.
Алгоритми з циклами забезпечують виконання довгої послідовності дій,
записаних порівняно короткою послідовністю команд.
4
Оператор циклу з передумовою
6
Чому тут суму шукаємо в зворотному порядку? — щоб врахувати малі зна-
чення. Якщо в суму водять дуже малі значення і великі, то треба обчислення
починати з малих значень.
У програмуванні існує таке поняття, як “машинний епсилон” — числове
значення, менше від якого не можливо задати відносну точність для будь-якого
алгоритму, який повертає дійсне число. Абсолютне значення “машинного епси-
лона” залежить від розрядності сітки певного комп’ютера, типу (розрядності)
використовуваних при розрахунках чисел і від прийнятого в конкретному
трансляторі формату подання дійсних чисел (кількості бітів, які відводяться на
мантису й експоненту). “Машинний епсилон” також можна визначити як міні-
мальне, відмінне від 0 дійсне число, яке після додавання до 1,0 дає ще резуль-
тат, відмінний від 1,0.
Значення “машинних епсилонів” зберігаються в бібліотеці float.h. Для типу
float — FLT_EPSILON=1.192092896 10 7 , а для типу double — DBL_EPSILON =
2.2204460492503131 10 16 .
Практичне значення “машинного епсилона” пов’язане з тим, що два від-
мінних від нуля числа є однаковими з точки зору машинної арифметики, якщо
їхня різниця за модулем менша від “машинного епсилона”.
Приклад 4. Знайти “машинний епсилон” — мінімальне, відмінне від 0 дійс-
не число, яке після додавання до 1.0 дає ще результат, відмінний від 1.0.
/* Обчислення і виведення на екран значення "машинного
епсилона" – мінімального, відмінного від 0 дійсного числа,
яке після додавання до 1.0 дає ще результат, відмінний від
1.0. Стандартне бібліотечне значення DBL_EPSILON =
2.2204460492503131e–016 */
#include <stdio.h>
#include <stdlib.h>
#include <float.h>
int main() {
double eps=1;
system("chcp 1251&cls ");
while (eps/2+1.0>1.0) eps=eps/2;
7
printf("\nОбчислене машинне епсилон типу double = %.18e",eps);
printf("\nБібліотечне машинне епсилон типу double =\
DBL_EPSILON %.18e", DBL_EPSILON);
printf("\n\n");
system("pause");
return 0;
}
Оскільки нам треба знайти останнє значення eps, яке впливає на результат,
то в умові закінчення циклу використати вираз eps+1.0>1.0 не можна, бо це бу-
де перше eps, яке не впливає на результат. Тому умова має бути eps/2+1.0>1.0.
Хіба що можна подати такий фрагмент програми, скориставшись робочою
змінною eps1:
...
double eps=1,eps1;
system("chcp 1251&cls");
while (eps+1.0>1.0) {eps1=eps; eps=eps/2;}
printf("\nОбчислений машиннмй епсилон типу double = %.18e",eps1);
...
Приклад 5. Значення цілої змінної а скоротити на множники, рівні 2.
/* Скорочення цілого числа на множники, рівні 2 */
#include <stdio.h>
#include <stdlib.h>
int main() {
int a, // введене число – може бути додатне, від’ємне чи нуль
i; // лічильник кількості кроків виконання циклу,
який дорівнює степеневі 2
system("chcp 1251&cls");
printf("Введіть значення a");
scanf("%d",&a);
i=0;
if (a) // (a!=0)
while(a%2==0) // можна також задати умову while(!(a%2))
{ a=a/2;i++; }
8
printf("Скорочене число= %d na 2^%d",a,i);
printf("\n\n");
system("pause");
return 0;
}
Якщо в цій програмі не врахувати, що а може бути рівне 0, то програма за-
циклиться навіть при тому, що виконується оператор зміни значення а: a=a/2.
Умова обчислюється лише на початку кожного такту циклу.
Приклад 6. Алгоритм Евкліда знаходження найбільшого спільного дільника
двох цілих чисел (від більшого числа віднімаємо менше, доки числа не стануть
рівними — це і є НСД).
Словесно алгоритм Евкліда можна подати так:
К1: Ввести числа a, b /* 36 і 24; 14 і 49; 3 і 5 */
К2: Якщо a=b, то перейти на К5
К3: Якщо a>b, то a=a–b, інакше b=b–a
К4: Перейти на К2
К5: Вивести a
Мовою С:
/* Алгоритм Евкліда знаходження найбільшого спільного
дільника двох цілих чисел */
#include <stdio.h>
#include <stdlib.h>
int main() {
int a,b,i; // введені числа
system("chcp 1251&cls");
printf("Введіть числа a i b ");
scanf("%d %d",&a,&b);
if (a==0 || b==0) printf("НСД знайти неможливо"); /* якщо
не перевірити, то програма зациклиться */
else {
a=abs(a); b=abs(b); //функція бібліотеки stdlib.h
while(a!=b)
if (a>b)a=a-b;
9
else b=b-a;
printf("NSD= %d",a);
}
printf("\n\n");
system("pause");
return 0;
}
Завдяки можливостям мови С щодо побудови виразів, ініціалізація циклу і
зміна умови може відбуватися в самій умові. Наприклад, для пропуску симво-
лів-роздільників при введенні символьної інформації можна використати такий
оператор з порожнім тілом циклу (тут нема ні ініціалізації змінних перед цик-
лом, ні перерахунку значення параметра в тілі циклу):
while ((с = getchar()) == ' ' || с == '\n' || с == '\t' );
Тут (а саме у виразі: (с = getchar()) використано правило мови С, що будь-який
вираз з операцією присвоювання, взятий в круглі дужки, має значення, рівне
тому, яке присвоюється.
Приклад 7. Знайти суму цифр цілого числа (число типу long long int). Числа
вводяться доти, поки не буде введено будь-який символ, відмінний від цифри.
При оргацізації циклу введення даних скористаємося тою властивістю, що
функція scanf повертає кількість реально введених і присвоєних змінним еле-
ментів даних (полів).
/* У циклі вводяться цілі числа, доки не буде введено будь-
яку букву. Обчислюється сума цифр кожного числа */
#include <stdio.h>
#include <stdlib.h>
int main() {
long long int n; // введене число
int i, // лічильник кількості цифр у числі
s; // сума цифр
system("chcp 1251&cls");
printf("Вводьте цілі числа. Для закінчення - букву\n");
while ( printf("Введіть число "), scanf("%lld",&n)){
n=llabs(n); //функція бібліотеки stdlib.h
10
s=0; i=0;
while(n>0) {
s=s+n%10;
n=n/10;
i++;
}
printf("Кількість цифр = %d,\nсума цифр = %d\n\n",i,s);
}
printf("\n\n");
system("pause");
return 0;
}
Приклад 8. Визначити, чи є число простим. Числа вводяться доти, поки не
буде введено будь-який символ, відмінний від цифри.
(Визначення: число n є простим, якщо в нього немає дільників в інтервалі
[2, n div 2]).
Прості числа діляться лише самі на себе і на 1. Перше просте число — 2.
Якщо є число n, то ділити його на всі (n–1) попередні числа не треба. Досить
поділити на числа до n/2, бо далі результат буде меншим від 2. Проте і це заба-
гато — досить поділити на числа до заокругленого до більшого n (наприкдад,
число 50=1255 можна подати парами множників: 1 і 50, 2 і 25, 5 і 10, 10 і 5, 25
і 2, 50 і 1, які після половини послідовності повторюються (тільки в зворотному
порядку) тому досить перевірити множники 2 і 5, що приблизно вкладається в
діапазон чисел від 2 до 50 8 ; число 36=12233 можна подати добутками
пар чисел: 1 і 36, 2 і 18, 3 і 12, 4 і 9, 6 і 6, 9 і 4, 12 і 3, 18 і 2, 36 і 1 — після пари 6
і 6 пари повторюються у зворотному порядку — тому досить перевіряти поді-
льність на числа від 2 до 36 6 ).
(Отже, число n є простим, якщо в нього немає дільників в інтервалі [2, n ]).
При оргацізації циклу введення даних, як і в попередньому випадку, скори-
стаємося тою властивістю, що функція scanf повертає кількість реально введе-
них і присвоєних змінним елементів даних (полів).
11
/* У циклі вводяться цілі числа, доки не буде введено будь-
яку букву.
Визначається, чи є число простим */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main() {
long long int n; // введене число
int ind, // індикатор простоти: 0 – непросте, 1 - просте
i, s; // лічильник і корінь з введеного числа
system("chcp 1251&cls");
printf("Вводьте цілі числа. Для закінчення – букву\n");
while (scanf("%lld",&n)){ // дає нуль, якщо вводиться не
число
n=llabs(n); // llabs - функція бібліотеки stdlib.h
if (n<2)
ind=0; // введене число не просте
else
ind=1; // вважаємо, що введене число просте
if (n>3) {
s=ceil(sqrt((double)n)); // sqrt вимагає дійсного ар-
гумента; ceil - заокруглення до більшого
i=2;
while(i<=s && ind) {
if (n%i==0)
ind=0;
i++;
}
}
if (ind)
printf("\tпросте\n");
else
printf("\tне просте\n");
}
12
printf("\n\n");
system("pause");
return 0;
}
Приклад 9. Визначити за введеною датою номер дня в році (шляхом суму-
вання попередніх днів). Дати вводяться доти, поки не буде введено будь-який
символ, відмінний від цифри.
/* Визначення номера дня в році.
У циклі вводяться дати в форматі дд.мм.рррр, доки не
буде введено будь-який символ, відмінний від цифри */
#include <stdio.h>
#include <stdlib.h>
int main() {
int dd, mm=1, rr=2022; // введена інформація
int k, // номер дня в році
mr; // робоча змінна - номер попереднього місяця
system("chcp 1251 & cls");
printf("Вводьте дати за форматом дд.мм.рррр\nДля закінчення
– букву\n");
while (scanf("%d.%d.%d", &dd,&mm,&rr)){ // дає нуль, якщо
вводиться не число
/* Кількість днів за попередні місяці */
k=0;
mr=1;
while (mr<mm) { // обчислення кількості днів за попередні місяці
switch (mr) {
case 4: case 6: case 9: case 11:
k=k+30; break;
case 2: if (rr%4==0 && (rr%100!=0 || rr%400==0))
k=k+29;
else
k=k+28;
break;
default: k=k+31;
13
} // switch
mr++;
} // while
/* Одержання результату */
k=k+dd;
printf("\tНомер дня в році - %d\n",k);
} // while (scanf...)
printf("\n\n");
system("pause");
return 0;
}
Прикладів можна навести ще безліч.
де bn — наближене значення a .
Для виходу із циклу ітерацій (послідовних наближень) можна застосувати
абсолютну похибку
a bn2 10 6 ,
Але цей критерій не спрацює, якщо число буде меншим від 10-6. Тому тут треба
скористатися відносною похибкою:
a
2
1 10 6 .
bn
17
Програма вводить числа і виводить значення кореня з заданою точністю,
доки не буде введено замість числа будь-який символ, відмінний від цифри.
/* Обчислення значення квадратного кореня за методом
дотичних Ньютона (формула Герона) */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main() {
const double eps=1.e-6; // точність обчислення
double a, // введене число
bn; // наближене значення кореня
system("chcp 1251 & cls");
printf("Вводьте дійсні числа. Для закінчення вводу - букву");
while ((printf("\n a= "), scanf("%lg",&a))) { /* результатом
операції коми є значення останньої,
записаної в дужках, операції */
if (a<0) printf("\tкорінь не існує");
else
if (a==0)
printf("\t0");
else { // число >0
bn=1;
do
bn=(a/bn+bn)/2;
while (fabs(a/(bn*bn)-1)>=eps);
printf("\t%lg",bn);
} // else
} // while
printf("\n\n");
system("pause");
return 0;
}
18
Приклад 14. Вгадування числа — програма генерує випадкове число, а ко-
ристувач вгадує його.
/*Генерація випадкових чисел від 1 до 100 і вгадування */
#include <stdio.h>
#include <stdlib.h> // для одержання випадкових чисел
#include <time.h> // для srand(time(0));)
int main () {
int r, x; // згенероване і введене числа
int n; // кількість спроб при вгадуванні
system("chcp 1251 & cls");
printf("Вводьте дійсні числа. Для закінчення вводу - букву");
srand(time(0));
n=0; r=rand()%100+1; /* з проміжка від 1 до 100 */
do {
printf("\nВведіть число від 1 до 100 ");
scanf("%d",&x);
if(r<x) printf("\tвведене число більше");
if(r>x) printf("\tвведене число менше");
n++;
} while (r-x);
printf("Ви вгадали число за %d спроб",n);
printf("\n\n");
system("pause");
return 0;
}
1 1 1
(1) n 1
(1) n 1
3 4 ... 3 4 3
23 4 45 6 6 78 n 1 2 n( 2 n 1)( 2n 2) n 1 n( 2 n 1)( n 1)
23
Алгоритм 1. Для запобігання механічним помилкам під час вводу інфор-
мації доцільно замість фрагменту програми:
printf("Введіть a, b, c ");
scanf("%lf %lf %lf",&a,&b,&c);
використовувати такий циклічний алгоритм:
fseek(stdin,0,SEEK_END); // очистка буфера(бібліотека stdio.h),
// працює не у всіх середовищах
printf("Введіть a, b, c ");
while (scanf("%lf %lf %lf",&a,&b,&c)<3) { // передбачення
// механічних помилок вводу
printf("Помилка в даних. Повторіть ввід a, b, c ");
fseek(stdin,0,SEEK_END); // очистка буфера
}
Алгоритм 2. Для перевірки правильності введення даних можна скориста-
тися таким алгоритмом — цикл працює доти, доки не буде введено правильне
значення:
ind=1;
do {
printf("Введіть значення x в межах від 0 до 9:");
scanf("%lf", &x);
fseek(stdin,0,SEEK_END); // очистка буфера
if (x<0 || x>9) printf("\tнеправильне значення x\n");
else ind=0;
} while (ind);
Алгоритм 3. Для зациклювання роботи програми, точніше, виконання ал-
горитму програми невизначену кількість разів, можна скористатися такими під-
ходами.
а) Якщо числові дані вводити за допомогою функції scanf і перше число задати
з помилкою, то функція поверне значення 0. Таким чином, при використанні фун-
кції scanf у циклі для закінчення вводу чисел першим можна ввести будь-який си-
мвол, відмінний від цифри (а також відмінний від знаків плюс і мінус, крапки, з
якої може починатися дійсне число), тобто ввести явно помилкове щодо числового
формату значення. Це можна реалізувати так:
24
printf("Вводьте цілі числа. Для закінчення – букву\n");
ind=scanf("%lld",&n);
while (ind) {
... // оператори обробки даних
ind=scanf("%lld",&n);
}
або, використовуючи можливості оператора циклу і можливості мови С щодо
побудови виразів, можна подати значно ефективніший фрагмент алгоритму:
printf("Вводьте цілі числа. Для закінчення – букву\n");
while (scanf("%lld",&n)){ // дає 0, якщо вводиться не число
... // оператори обробки даних
}
б) Продовжувати чи припиняти повторне виконання алгоритму також мож-
на, використовуючи діалог з користувачем. Його можна реалізувати, напри-
клад, так:
...
char vidp; // відповідь користувача
do { // зациклювання роботи програми
... // виконуваний блок програми
fseek(stdin,0,SEEK_END); // очистка буфера - у буфері
// залишається Enter або інша зайва
// інформація від попереднього вводу
printf("Продовжувати роботу (Y - так)? ");
while ((vidp=getchar())==' '||vidp=='\n'||vidp=='\t'); // пропуск
// пробільних символів і одержання відповіді
fseek(stdin,0,SEEK_END); // очистка буфера
} while (vidp=='Y'||vidp=='y'||vidp=='Т'||
vidp=='т'); // з українськими буквами Т чи т працює,
// якщо є system("chcp 1251") і char vidp описано без unsigned
...
Алгоритм 4. Для очищення буфера вводу, крім функції fseek (яка працює не
у всіх середовищах програмування), можна циклічно виконати функцію getchar,
скориставшись циклом з передумовою з порожнім тілом:
25
while (getchar()!='\n');
Виконання цього циклу полягає в зчитуванні всіх символів із буфера вво-
ду, доки не буде зчитано символ '\n' (Enter). Таким чином буфер буде очище-
но від непотрібної інформації. Але, якщо при зчитуванні інформації виникне
помилка чи якщо користувач при роботі в Windows натисне комбінацію кла-
віш Ctrl+Z або при роботі в Linux натисне комбінацію клавіш Ctrl+D, то фун-
кція getchar поверне значення EOF (кінець файла) і після цього зчитування
зупиниться, і тільки наступне натискання на клавішу Enter завершить вико-
нання циклу. Тому, щоб значення EOF не зупиняло виконання програми,
краще виконати також перевірку, чи не зчитано значення EOF:
char c;
while ((c=getchar())!='\n' && c!=EOF); .
Використання розглянутих циклічних алгоритмів значно спрощує роботу
користувача при введенні інформації.
26