[ЗМІСТ]      [Далі]       [Назад]      [Початок розділу]

 

3.6. Цикли з лічильником

 

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

      Візьмемо арифметичну прогресію з цілим початковим членом b і позитивною різницею d > 0

 

       а  = b,                                             (AP)

        0

       а  = а   + d, k=1,2,...

        k    k-1

      Нехай с - ціле число, Р - інструкція, k - ціла змінна.

      Канонічним зростаючим циклом з лічильником або коротше канонічно зростаючим циклом назвемо інструкцію

 

                  --------------------------------

                  ¦  для k<-b до с через d повт  ¦

                  ¦     Р                        ¦            (Lc+)

                  ¦  кц,                         ¦

                  L-------------------------------

 

яка служить скороченням для розгалуження

 

       якщо b<=с то

          k <- b;

          цикл

             Р;

             якщо k>c-d то вихід;

             k <- k+d

          кц

       кр.

 

              Правило зростаючого циклу з лічильником:

    ¦     Якщо початкове значення b лічильника циклу k більше кінцевого

    ¦ значення с, то цикл (Lc+) рівносильний інструкції Нічого.

    ¦     У іншому випадку виконується ланцюг:

    ¦ 1. Лічильнику циклу k присвоюється початкове значення b.

    ¦ 2. Виконується інструкція Р.

    ¦ 3. Обчислюється значення F умови k>c-d.

    ¦ 4. Якщо F=Іст, то цикл (Lc+) завершує свою роботу.

    ¦    Якщо F=Хиб, то значення лічильника k збільшується на величину

    ¦ приросту d і пристрій керування знову переходить до виконання

    ¦ пункту 2 даного правила.

    L---------------------------------------------------------------

 

      Таким чином, лічильник циклу пробігає послідовно всі члени аi арифметичної прогресії, якщо вони існують, які задовольняють нерівності аi<=c. Причому останнє значення k рівне найбільшому серед них. Якщо ж таких членів послідовності не існує, то маємо

      Властивість 3.14. Якщо с<b, то цикл (Lc+) вироджується в інструкцію Нічого.

      Доведення безпосередньо витікає з властивості 2.8.¦

      Канонічний зростаючий цикл називається суцільним, якщо d = 1. Позначення (L1+). У цьому випадку визначення кроку циклу "через 1" можна опустити:

 

       для k<-b до с повт

          Р

       кц.

      Зауваження 3.7. Умову виходу з суцільного циклу можна замінити рівносильною k=с.¦

      Властивість 3.15. Якщо b<=с, то цикл (L1+) рівносильний арифметичному циклу

 

       k <- b-1;

       повт c-b+1 раз

          k <- k+1;

          Р

       кц.

      Доведення. Присвоєння k <- b замінимо ланцюгом k <- b-1; k <- k+1. Перепишемо визначення (Lc+), враховуючи зауваження 3.7 і очевидну тотожність b<=с = Іст:

 

       k <- b-1; k <- k+1;

       цикл

          Р;

          якщо k=с то вихід;

          k <- k+1

       кц,

звідки отримуємо

 

       k <- b-1;

       поки k<>с повт

          k <- k+1;

          Р

       кц.

Неважко підрахувати, що кількість повторень останнього циклу складає c-b+1.¦

 

      Приклад 3.20. Обчислити факторіал у=n! (див. приклад 3.3).

       у <- 1;

       для k<-1 до n повт

          у <- у*k

       кц.

 

      Приклад 3.21. Обчислити добуток всіх непарних чисел від  1 до n.

       у <- 1;

       для k<-1 до n через 2 повт

          у <- у*k

       кц.

 

      Повернемося до арифметичної прогресії вигляду (AP) з цілим додатнім членом b і на цей раз від’ємною різницею d<0.

      Нехай знову с - ціле число, Р - інструкція, k - ціла змінна.

      Канонічним спадним циклом з лічильником або коротше канонічно спадним циклом назвемо інструкцію

 

                  --------------------------------

                  ¦  для k<-b до с через d повт  ¦

                  ¦     Р                        ¦            (Lc-))

                  ¦  кц,                         ¦

                  L-------------------------------

 

яка тепер служить скороченням для розгалуження

 

       якщо b>=с то

          k <- b;

          цикл

             Р;

             якщо k<c-d то вихід;

             k <- k+d

          кц

       кр.

 

      Правило виконання циклу (Lc-) будується аналогічно правилу виконання зростаючого циклу (Lc+).

      Отже, лічильник циклу пробігає послідовно всі члени аi арифметичної прогресії, якщо вони існують, які задовольняють нерівності аi >=c. Причому останнє значення k рівне найменшому серед них. Якщо ж таких членів послідовності не існує, то знову маємо

      Властивість 3.14'. Якщо с>b, то цикл (Lc-) вироджується в інструкцію Нічого.

      Доведення безпосередньо витікає з властивості 2.8.¦

      Канонічний спадний цикл називається суцільним, якщо d = -1. Позначення (L1-). Але в цьому випадку визначення кроку циклу "через -1") не опускається.

      Властивість 3.15'. Якщо b>=с, то цикл (L1-) рівносильний арифметичному циклу

 

       k <- b+1;

       повт b-c+1 раз

          k <- k-1;

          Р

       кц.

      Доведення повторює доведення властивості 3.15.¦

 

      Приклад 3.22. Обчислити факторіал у=n! (див. приклади 3.3 і 3.20).

Інший спосіб обчислення факторіала отримаємо, якщо записати співвідношення

       у  = 1;

        0

       у  = у   * (n-i+1), i=1,2,..., n.

        i    i-1

Суцільний спадний цикл дозволяє записати

 

       у <- 1;

       для k<-n до 1 через -1 повт

          у <- у*k

       кц.

 

      Приклад 3.23. Обчислити подвійний факторіал у=n!!

За означенням

 

              ¦ 1*3*5* ... *n, якщо n - непарне,

       n!! = <

              ¦ 2*4*6* ... *n, якщо n - парне.

 

У обох випадках ми маємо всі члени спадної арифметичної прогресії, що містяться між 1 і n. Отже

 

       у <- 1;

       для k<-n до 1 через -2 повт

          у <- у*k

       кц.

      Помітимо, що при непарному n останнім значенням k буде 1, а при парному - число 2.

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

      Спрощеним циклом з лічильником назвемо цикл вигляду

       k <- b;

       поки k<=с повт

          Р;                                               (Lcw)

          k <- k+d

       кц.

      Він відрізняється від канонічного циклу двома обставинами:

      а) при  b>c залишає значення  k=b,  тоді  як  канонічний не надає k ніякого значення;

      б) результуюче значення k більше останнього використаного в інструкції Р на величину d.

 

      Незахищеним циклом з лічильником назвемо цикл вигляду

       k <- b;

       повт

          Р;                                               (Lcu)

          k <- k+d

       до k>с.

      Цьому циклу властиві обидві попередніх відмінності від канонічного циклу. Крім цього, при b>с незахищений цикл виконується один раз, що може призвести до несподіваних наслідків.

      Проблема утруднюється також тим, що різні мови програмування, а іноді навіть різні реалізації однієї і тієї ж мови, розуміють під циклом з лічильником то канонічний, то спрощений, то незахищений варіант. Тому значення лічильника циклу  після  виходу  з нього звичайно вважають невизначеним. Крім того, незахищений варіант може вимагати додаткового захисту, наприклад,

 

       якщо b<=с то

          для k<-b до с через d повт Р кц

       кр.

 

3.7. Програмування циклів з лічильником

 

      Що стосується циклу з лічильником, то у мові Паскаль передбачені суцільні канонічні зростаючі і спадні цикли з натуральним або цілим лічильником. Вони записуються відповідно

 

       for k:=b to с do begin

          Pas(Р)

       end

 

і

 

       for k:=b downto с do begin

          Pas(Р)

       end

 

при тих же, що і раніше зауваженнях відносно  "begin-end".

 

      Приклад 9P. Два факторіали (задачі 3.20 і 3.22).

    program Factorials;

       var N, K: word;

           Y1, Y2: real;

    begin

       write('n=? '); readln(N);

       Y1:= 1; Y2:= 1;

       for K:=1 to N do Y1:= Y1*K;

       for K:=N downto 1 do Y2:= Y2*K;

       writeln(N, ' != ', Y1, Y2)

    end.

 

      Інші варіанти циклів з лічильником записуються через цикли за умовою.

      Приклад 10P. Подвійний факторіал (задача 3.23).

    program Double_Factorial;

       var N, K: integer;

           Y: real;

    begin

       write('n=? '); readln(N);

       Y:= 1; K:= N;

       while K>0 do begin

          Y:= Y*K; K:= K-2

       end;

       writeln(N, ' !!= ', Y)

    end.

 

      у мові Сі цикли з лічильником реалізуються за допомогою цикла for Сі. Цикл for має наступний вигляд:

      for (e1; F; e2)

            {

             P

            },

де e1- вираз, який обчислюється один раз перед початком циклу, F – умова продовження циклу, e2- вираз, який обчислюється після кожного кроку циклу, P – ланцюг інструкцій. Цикл for поєднує риси циклів за умовою та циклів з лічильником, оскільки умова продовження циклу F може бути будь-якою. Вирази e1, e2 можуть вміщувати декілька присвоєнь, які в цьому випадку записуються через кому. Наприклад, для обчислення n! можна написати цикл:

      for (k = 1, y = 1; k<=n; k++)

            y *= k;

або навіть

      for (k = 1, y = 1; k<=n; y*=k, k++);

 

      Цикл for є реалізацією спрощеного циклу з лічильником.

      За допомогою циклу for у мові Сі можна програмувати як зростаючі, так і спадні цикли з лічильником. Вони записуються відповідно

 

      for (k = b; k<=с; k +=d)

            {

             С(Р)

            }

 

і

 

      for (k = b; k>=с; k +=d)

            {

             С(Р)

            }

при тих же, що і раніше зауваженнях відносно  "{-}".

Суцільно зростаючий та суцільно спадний цикли реалізуються відповідно

 

      for (k = b; k<=с; k++)

            {

             С(Р)

            }

 

і

 

      for (k = b; k>=с; k--)

            {

             С(Р)

            }

 

      Приклад 9C. Два факторіали (задачі 3.20 і 3.22).

#include <stdio.h>

 

/* Factorials */

main()

{

  unsigned n, k;

  double y1, y2;

 

  printf(n=?); scanf(“%u”, &n);

  y1 = 1; y2 = 1;

  for (k = 1; k<=n; k++) y1 *= k;

  for (k = n; k>=1; k--) y2 *= k;

  printf(“%u != %lf %lf”, n, y1, y2);

}

 

      Приклад 10C. Подвійний факторіал (задача 3.23).

#include <stdio.h>

 

/* Double_Factorial */

main()

{

  int n, k;

  double y;

 

  printf(n=?); scanf(“%d”, &n);

  y = 1; k = n;

  for (k = n; k>=1; k -= 2)

    y *= k;

  printf(“%d !!= %lf”, n, y);

}

[ЗМІСТ]      [Далі]       [Назад]      [Початок розділу]