Одномерные и двумерные массивы. Строки

Массив - это пронумерованная последовательность величин одинакового типа, обозначаемая одним именем. Элементы массива располагаются в последовательных ячейках памяти, обозначаются именем массива и индексом. Каждое из значений, составляющих массив, называется его компонентой (или элементом массива).

Массив данных в программе рассматривается как переменная структурированного типа. Массиву присваивается имя, посредством которого можно ссылаться как на массив данных в целом, так и на любую из его компонент.

Переменные, представляющие компоненты массивов, называются переменными с индексами в отличие от простых переменных, представляющих в программе элементарные данные. Индекс в обозначении компонент массивов может быть константой, переменной или выражением порядкового типа.

Если за каждым элементом массива закреплен только один его порядковый номер, то такой массив называется линейным. Вообще количество индексов элементов массива определяет размерность массива. По этом признаку массивы делятся на одномерные (линейные), двумерные, трёхмерные и т.д.

Пример: числовая последовательность четных натуральных чисел 2, 4, 6, ..., N представляет собой линейный массив, элементы которого можно обозначить А[1]=2, А[2]=4, А[3]=6, ..., А[К]=2*(К+1), где К — номер элемента, а 2, 4, 6, ..., N — значения. Индекс (порядковый номер элемента) записывается в квадратных скобках после имени массива.

Например, A[7] — седьмой элемент массива А; D[6] — шестой элемент массива D.

Для размещения массива в памяти ЭВМ отводится поле памяти, размер которого определяется типом, длиной и количеством компонент массива.

тип идентификатор[количество строк];

Например,

int B[5]; char R[34];

— описывается массив В, состоящий из 5 элементов и символьный массив R, состоящий из 34 элементов. Для массива В будет выделено 5*6=30 байт памяти, для массива R — 1*34=34 байта памяти.

Тип элементов массива может быть любым, за исключением файлового. Обратим внимание, что нумерация элементов массива всегда идет с нуля.

Заполнить массив можно следующим образом:

Первый способ с помощью оператора присваивания. Этот способ заполнения элементов массива особенно удобен, когда между элементами существует какая-либо зависимость, например, арифметическая или геометрическая прогрессии, или элементы связаны между собой реккурентным соотношением.

Задача 1. Заполнить одномерный массив элементами, отвечающими следующему соотношению:

a1=1; a2=1; ai=ai-2+ai-1 (i = 3, 4, ..., n).

cin >> N; /*Ввод количества элементов*/

A[0] = 1;

A[1] = 1;

for(int i=2; i<n; i++)

A[I] = A[I - 1] + A[I - 2];

Другой вариант присваисвания значений элементам массива — заполнение значениями, полученными с помощью датчика случайных чисел.

Задача 2. Заполнить одномерный массив с помощью датчика случайных чисел таким образом, чтобы все его элементы были различны.

function changeProof(proofobj) { if (proofobj.style.display=='none') {proofobj.style.display='inline'} else {proofobj.style.display='none'} } Листинг

// программа заполнение массива #include <iostream.h> #include <time.h> #include <stdlib.h>   void main() { int A[99]; /*Массив из 100 элементов*/ cout << "Введите количество элементов массива"; int N; cin >> N; randomize(); A[0] = -32768 + random(65535); for(int i=1; i<N; i++) { int log=1; do { A[i] = -32768 + random(65535); int j = 1; while((log==1) && (j <= i - 1)) { if(A[i] != A[j]) log = 0; j++; } }while(log==1); } for(i=0;i<N;i++) cout << A[i] << " "; }

Второй способ ввод значений элементов массива с клавиатуры используется обычно тогда, когда между элементами не наблюдается никакой зависимости. Например, последовательность чисел 1, 2, -5, 6, -111, 0 может быть введена в память следующим образом:

int A[99], n;

cout << "Введите количество элементов массива ";

cin >> n;

for(int i=0; i<n; i++)

{

cout << "Введите A[" << i << "]";

cin >> A[i];

}

Над элементами массива чаще всего выполняются такие действия, как

а) поиск значений;

б) сортировка элементов в порядке возрастания или убывания;

в) подсчет элементов в массиве, удовлетворяющих заданному условию.

Cумму элементов массива можно подсчитать по формуле S=S+A[I] первоначально задав S=0. Количество элементов массива можно подсчитать по формуле К=К+1, первоначально задав К=0. Произведение элементов массива можно подсчитать по формуле P = P * A[I], первоначально задав P = 1.

Задача 3. Дан линейный массив целых чисел. Подсчитать, сколько в нем различных чисел.

function changeProof(proofobj) { if (proofobj.style.display=='none') {proofobj.style.display='inline'} else {proofobj.style.display='none'} } Листинг

{Подсчет количества различных чисел в линейном массиве. ИДЕЯ РЕШЕНИЯ: заводим вспомогательный массив, элементами которого являются псевдологические величины (0 - если элемент уже встречался ранее, 1 - иначе)}   // программа различные числа #include <iostream.h>   void main() { int A[50], Lo[50], n; cout << "Введите количество элементов массива: "; cin >> n; for(int i=0; i<n; i++) { cout << "A[" << i << "]="; cin >> A[i]; Lo[i] = 1; } int kol = 0; /*переменная, в которой будет храниться количество различных чисел*/ for (i=0;i<n;i++) { if (Lo[i]==1) { kol++; for(int k=i; k<n; k++) { Lo[k] = (A[k] != A[i]) && Lo[k]; } } } cout << "Количество различных чисел: " << kol; } Тест: N = 10; элементы массива - 1, 2, 2, 2, -1, 1, 0, 34, 3, 3. Ответ: 6.

Задача 4. Дан линейный массив. Упорядочить его элементы в порядке возрастания.

function changeProof(proofobj) { if (proofobj.style.display=='none') {proofobj.style.display='inline'} else {proofobj.style.display='none'} } Листинг

{Сортировка массива выбором (в порядке возрастания). Идея решения: пусть часть массива (по K-й элемент включительно) отсортирована. Нужно найти в неотсортированной части массива минимальный элемент и поменять местами с (K+1)-м}   // программа сортировка выбором #include <iostream.h>   void main() { int A[30], n; cout << "Введите количество элементов: "; cin >> n; for(int i=0; i<< i A[? ?Введите cout { i++)>> A[i]; } for(i=0; i<n-1; i++) { int k = i; for(int j=i+1; j<n; j++) if(A[j]<=A[k]) k = j; int buf = A[i]; A[i] = A[k]; A[k] = buf; } for(i=0; i<n; i++) { cout << A[i] << " "; } } Тест: N = 10; элементы массива - 1, 2, 2, 2, -1, 1, 0, 34, 3, 3. Ответ: -1, -1, 0, 1, 2, 2, 2, 3, 3, 34.

Демострации различных видов сортировок можно посмотреть здесь

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

Например, данные о планетах Солнечной системы представлены следующей таблицей:

Планета Расст. до Солнца Относ. обьем Относ. масса
Меркурий 57.9 0.06 0.05
Венера 108.2 0.92 0.81
Земля 149.6 1.00 1.00
Марс 227.9 0.15 0.11
Юпитер 978.3 1345.00 318.40
Сатурн 1429.3 767.00 95.20

Их можно занести в память компьютера, используя понятие двумерного массива. Положение элемента в массиве определяется двумя индексами. Они показывают номер строки и номер столбца. Индексы пишутся в отдельных квадратных скобках. Например: A[7][6], D[56][47].

Заполняется двумерный массив аналогично одномерному: с клавиатуры, с помощью оператора присваивания. Например, в результате выполнения программы:

void main()

{

int A[20][20];

for(int i=0; i<3; i++)

for(int j=0; j<2; j++)

A[i][j] = 456 + i;

}

элементы массива примут значения A[1][1] = 457; A[1][2] = 457; A[2][1] = 458; A[2][2] = 458; A[3][1] = 459; A[3][2] = 459.

При описании массива задается требуемый объем памяти под двумерный массив, указываются имя массива и в квадратных скобках максимальное значение индекса.

При выполнении инженерных и математических расчетов часто используются переменные более чем с двумя индексами. При решении задач на ЭВМ такие переменные представляются как компоненты соответственно трех-, четырехмерных массивов и т.д.

Однако описание массива в виде многомерной структуры делается лишь из соображений удобства программирования как результат стремления наиболее точно воспроизвести в программе объективно существующие связи между элементами данных решаемой задачи. Что же касается образа массива в памяти ЭВМ, то как одномерные, так и многомерные массивы хранятся в виде линейной последовательности своих компонент, и принципиальной разницы между одномерными и многомерными массивами в памяти ЭВМ нет. Однако порядок, в котором запоминаются элементы многомерных массивов, важно себе представлять. В большинстве алгоритмических языков реализуется общее правило, устанавливающее порядок хранения в памяти элементов массивов: элементы многомерных массивов хранятся в памяти в последовательности, соответствующей более частому изменению младших индексов.

Задача 5. Заполнить матрицу порядка n по следующему образцу:

... n-2 n-1 n
... n-3 n-2 n-1
... n-4 n-3 n-2
... ... ... ... ... ... ...
n-1 n-2 n-3 ...
n n-1 n-2 ...

function changeProof(proofobj) { if (proofobj.style.display=='none') {proofobj.style.display='inline'} else {proofobj.style.display='none'} } Листинг

// программа заполнение массива #include <iostream.h>   void main() { int A[10][10], n; cout << "Введите порядок матрицы: "; cin >> n; for(int i=0; i<n; i++) for(int j=i; j<n; j++) { A[i][j] = j + i + 1; A[j][i] = A[i][j]; } for(i=0; i<n; i++) { cout << "\n"; for(int j=0; j<n; j++) cout << A[i][j] << " "; } }

Задача 6. Дана целочисленная квадратная матрица. Найти в каждой строке наибольший элемент и поменять его местами с элементом главной диагонали.

function changeProof(proofobj) { if (proofobj.style.display=='none') {proofobj.style.display='inline'} else {proofobj.style.display='none'} } Листинг

// программа обмен в массиве #include <iostream.h>   void main() { int A[15][15], n; cout << "Введите количество элементов в массиве: "; cin >> n; for(int i=0; i<n; i++) for(int j=0; j<n; j++) { cout << "A[" << I << "][" << J << "] "; cin >> A[i][j]; } for(i=0; i<n; i++) { int max = A[i][1]; int ind = 1; for(int j=2; j<n; j++) if(A[i][j]>max) { max = A[i][j]; ind = j; } int vsp = A[i][i]; A[i][i] = A[i][ind]; A[i][ind] = vsp; } for(i=0; i<n; i++) { cout << "\n"; for(int j=0; j<n; j++) { cout << A[i][j] << " "; } } }

Строки в С++

В языке С++ нет определенного типа отведенного для работы со строками, как в языке Pascal. Но вместо этого используется массив символов, последним элементом которого является маркер конца строки "\0". Только при этом одномерный массив получает свойства строки, которую можно использовать в библиотечных функциях для работы со строками или при выводе строки на экран с помощью оператора cout << stroka. Например

char stroka1[] = {"Это строка\0"};

char stroka2[3] = {'A','c','\0'};

char stroka3[100];

Как видим - при явном задании строки символ конца строки обязателен. Также заметим что количество элементов массива задается с учетом нулевого символа (см. stroka2).

Функции для работы со строками подключаются с помощью библиотеки string.h. Полный список функций можно посмотреть в документации к языку.

strcpy(str1,str2); - копировать str1 в str2, str1 > str2! при несоблюдении возможна потеря информации.

strcat(str1,str2); - соединение str1 + str2 и заностися в str1, str2 не изменяется.

strlen(str1,str2); - длина строки, нулевой символ не учитывается.

strcmp(str1,str2); - сравнение строк (если равно нулю, то равны).

strncpy(str1,str2,n); - копировать n символов в str1 в str2

Контрольные вопросы и задания

  1. Что такое массив?
  2. Почему массив является структурированным типом данных?
  3. Что такое размерность массива? Существуют ли ограничения на размерность массива?
  4. Какого типа могут быть элементы массива?
  5. Какого типа могут быть индексы элементов массива?
  6. Какими способами может быть заполнен массив? Приведите примеры.
  7. Как определить минимальный объём памяти, отводимой под массив?
  8. Какие действия выполняют обычно над элементами массива?
  9. Может ли массив быть элементом массива?
  10. Пусть элементами массива A (a[1], a[2], a[3], a[4]) являются соответственно x, -x, x2, -x2. Чему будет равно значение выражения

11. a[-a[a[3]-2]]+a[-a[a[3]]]

при x=2?

  1. Можно ли выполнять обход двумерного массива, организовав внешний цикл по столбцам, а внутренний — по строкам?
  2. Используются ли вложенные циклы, если совершается обход только главной диагонали квадратной матрицы?

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

Вся программа условно может быть разделена на две части: основную и вспомогательную. В основной части производится простейшая обработка информации, организуется обращение к разным вспомогательным модулям (подпрограммам).

Вспомогательный алгоритм тоже может вызывать другие вспомогательные, длина такой цепочки вызовов теоретически не ограничена. Здесь и далее следующие пары слов используются как синонимы: алгоритм и программа, вспомогательный алгоритм и подпрограмма, команда и оператор, программа и модуль. Вспомогательными и основными алгоритмы являются не сами по себе, а по отношению друг к другу.

При использовании вспомогательных алгоритмов необходимо учитывать способ передачи значений исходных данных для них и получения результата от них. Аргументы вспомогательного алгоритма — это переменные, в которых должны быть помещены исходные данные для решения соответствующей подзадачи. Результаты вспомогательного алгоритма — это также переменные, где содержаться результаты решения этих подзадач, а также результатом может быть конкретное действие, которое совершает компьютер под действием подпрограммы.

Подпрограммы могут быть двух видов: подпрограмма без параметров и подпрограмма с параметрами. Обращение к подпрограмме может быть организовано из любого места основной программы или другой подпрограммы сколько угодно раз.

При работе с подпрограммами важными являются понятия формальных и фактических параметров. Формальные параметры — это идентификаторы входных данных для подпрограммы. Если формальные параметры получают конкретные значения, то они называются фактическими. Формальные параметры могут получить конкретные значения только в той программе, где производится обращение к данному модулю-подпрограмме. Тип и порядок записи фактических параметров должны быть такими же, как и формальных параметров. В противном случае результат работы программы будет непредсказуемым. Из этого следует, что фактические параметры используются при обращении к подпрограмме из основной, а формальные параметры — только в самом модуле.

Подпрограмма с параметрами используется для записи многократно повторяющихся действий при разных исходных данных.

При составлении подпрограмм с параметрами надо соблюдать следующие правила:

1) каждая подпрограмма имеет свое имя и список формальных параметров;

2) процедура из основной программы вызывается командой вызова, которая по форме ничем не отличается от вызова команды исполнителя. Результат присваивается одной или нескольким переменным, которые находятся в списке формальных параметров. Но результатом могут быть, конечно, не только значения переменных, но какое либо действие, выполненное ЭВМ.

Пример 1. Используем алгоритм нахождения наибольшего общего делителя двух натуральных чисел в качестве вспомогательного при решении задачи: составить программу вычитания дробей (a, b, c, d — натуральные числа). Результат представить в виде обыкновенной несократимой дроби.

Подпрограмма.

  1. Ввести натуральные числа M, N.
  2. Если M=N, перейти к п. 5, иначе к следующему пункту.
  3. Если M>N, то M:=M-N, иначе N:=N-M.
  4. Перейти к п. 2.
  5. Передать значение M в основную программу.
  6. Конец подпрограммы.

Основная программа.

  1. Ввести значения A, B, C, D.
  2. E:=A*D - B*C.
  3. F:= B*D.
  4. Если E=0, вывести значение E и перейти к п. 9, иначе перейти к следующему пункту.
  5. M:=|E|, N:=F, перейти к подпрограмме вычисления НОД.
  6. G := M.
  7. E и F нацело разделить на G.
  8. Вывести значения E и F на печать.
  9. Конец программы.

function changeProof(proofobj) { if (proofobj.style.display=='none') {proofobj.style.display='inline'} else {proofobj.style.display='none'} } Пример решения задачи на языке С++

  // программа НОД   #include <iostream.h> #include <math.h>   void Nod(int m, int n, int &k);   void main() { int a, b, c, d, g, e, f; cout << "Введите числители и знаменатели дробей:"; cin >> a >> b >> c >> d; e = a * d - b * c; f = b * d; if (e==0) cout << "Ответ: " << e; else { Nod(fabs(e),f,g); e = e / g; f = f / g; cout << "Ответ: " << e << "/" << f; } }   void Nod(int m, int n, int &k) { while (m != n) if (m > n) m -= n; else n -= m; k = m; }  

Как видно из примера, объявление подпрограммы-функции находится в разделе описаний прототипов функций, а реализация после основной функции main. В заголовке подпрограммы содержится список формальных параметров с указанием их типа, которые условно можно разделить на входные и выходные (перед ними стоит &). Вообще при обращении к функции со списком параметров без &, внутри функции используются копии параметров, которые после выполнения удаляются. Знак & указывает компилятору что необходимо использовать саму переменную, а не ее копию. При обращении к функции указывается ее имя и список фактических параметров. Формальные и фактические параметры должны соответствовать по количеству и по типу.

Описание функции в С++ осуществляется следующим образом:

тип_возвращаемого_значения <Имя функции>(<список фактических параметров>);

Например,

void Nod(int e, int f, int &k);

int f1(float a);

long f2();

Функция всегда возвращает единственное значение. Как видно из примера 1, мы использовали тип void в качестве возращаемого типа. Т.е. указали компилятору, что наша функция не возвращает никакого значения.

Покажем, как изменится подпрограмма из примера, если ее записать в виде функции, возвращающей само значение НОД (без использования возвращаемой переменной).

int Nod(int m, int n)

{

while (m!=n)

if (m > n) m -=n; else n -= m;

return (n);

}

Итак, в теле функции хотя бы один раз встречается команда return, которая указывает, какое значение вернуть в качестве значения функции.

Вызов функции в основной будет следующим:

g = Nod(fabs(e), f);

Вообще, вызов функции может присутствовать в выражении, стоящем: в правой части операции присваивания, в операторе вывода, в качестве фактического параметра в вызове другой подпрограммы и т.д.

При решении задач целесообразно проанализировать условие, записать решение в крупных блоках (не являющихся операторами C++), детализировать каждый из блоков (записав в виде блоков, возможно, по-прежнему не операторов C++), и т.д., продолжать до тех пор, пока каждый из блоков не будет реализован с помощью операторов языка.

Пример 2. Дано натуральное число n. Переставить местами первую и последнюю цифры этого числа.

function changeProof(proofobj) { if (proofobj.style.display=='none') {proofobj.style.display='inline'} else {proofobj.style.display='none'} } Листинг основной функции

  void main() { int n; cout << "Введите натуральное число: "; cin >> n; if (Impossible(n)) cout << "Невозможно переставить цифры, возникнет переполнение!"; else { Change(n); cout << "Ответ: " << n; } }  

Можно заметить, что необходимо детализировать логическую функцию Impossible, которая диагностирует, возможна ли перестановка, и функцию Change, которая эту перестановку (в случае, если она возможна) выполняет.

function changeProof(proofobj) { if (proofobj.style.display=='none') {proofobj.style.display='inline'} else {proofobj.style.display='none'} } Листинг функции Impossible

int Impossible(int n) { if(Number(n)<5) return (0); else return ((n % 10 >= 3)&&((((n % 10000)/10)*10 + n / 10000)> 32768 % 10000)); }

Здесь необходимо детализировать функцию Number, возвращающую количество цифр в записи натурального числа (т.к. функция Impossible содержит ее вызов, то в разделе описаний прототипов функция Number должна ей предшествовать).

function changeProof(proofobj) { if (proofobj.style.display=='none') {proofobj.style.display='inline'} else {proofobj.style.display='none'} } Листинг функции Number

int Number(int n) { int vsp = 0; while (n > 0) { vsp++; n = n / 10; } return (vsp); }

Наконец, последняя функция.

function changeProof(proofobj) { if (proofobj.style.display=='none') {proofobj.style.display='inline'} else {proofobj.style.display='none'} } Листинг функции Change

void Change(int &n) { int kol = Number(n); int p = n % 10; if(kol>1) s = n / pow(10, kol - 1); else s = 0; int r = (n % pow(10, kol - 1)) /10; n = p * pow(10, kol - 1) + r*10 + s; }

Возможны также подпрограммы, которые вызывают сами себя. Они называются рекурсивными. Создание таких подпрограмм является красивым приемом программирования, но не всегда целесообразно из-за чрезмерного расхода памяти ЭВМ.

Пример 3. Найти максимальную цифру в записи данного натурального числа.

function changeProof(proofobj) { if (proofobj.style.display=='none') {proofobj.style.display='inline'} else {proofobj.style.display='none'} } Листинг

// программа максимальная цифра #include <iostream.h>   int Maximum(long n);   void main() { long A; cout << "Введите натуральное число: "; cin >> A; cout << "Максимальная цифра равна " << Maximum(A); }   int Maximum(long n); { if (n < 10) return(n); else if ((n % 10) > Maximum (n / 10)) return (n % 10); else return (Maximum(n / 10)) }

При создании функции Maximum было использовано следующее соображение: если число состоит из одной цифры, то она является максимальной, иначе если последняя цифра не является максимальной, то ее следует искать среди других цифр числа. При написании рекурсивного алгоритма следует позаботиться о граничном условии, когда цепочка рекурсивных вызовов обрывается и начинается ее обратное «раскручивание». В нашем примере это условие N < 10.

Более подробно о рекурсии говорится в следующей статье.

Контрольные вопросы и задания

  1. Какие алгоритмы называют вспомогательными?
  2. Какое количество вспомогательных алгоритмов может присутствовать в основном алгоритме?
  3. Можно ли вспомогательные алгоритмы, написанные для решения данной задачи, использовать при решении других задач, где их применение было бы целесообразно?
  4. Какие параметры называют формальными? фактическими?
  5. Какое соответствие должно соблюдаться между формальными и фактическими параметрами?
  6. Может ли фактических параметров процедуры (функции) быть больше, чем формальных? А меньше?
  7. Существуют ли подпрограммы без параметров?
  8. Существуют ли ограничения на число параметров подпрограмм? Если нет, то чем же всё-таки ограничивается это количество в С++?
  9. В каком разделе объявляются и в каком реализуются подпрограммы в С++?
  10. Какого типа может быть значение функции?
  11. Расскажите о методе последовательной детализации при разработке программ.
  12. Какие подпрограммы называют рекурсивными?
  13. Что такое граничное условие при организации рекурсивной подпрограммы?

Рекурсия

Рекурсия — это такой способ организации вспомогательного алгоритма (подпрограммы), при котором эта подпрограмма (процедура или функция) в ходе выполнения ее операторов обращается сама к себе. Вообще, рекурсивным называется любой объект, который частично определяется через себя.

Например, приведенное ниже определение двоичного кода является рекурсивным:

<двоичный код> ::= <двоичная цифра> | <двоичный код><двоичная цифра>

<двоичная цифра> ::= 0 | 1

Здесь для описания понятия были использованы, так называемые, металингвистический формулы Бэкуса-Наура (язык БНФ); знак "::=" обозначает "по определению есть", знак "|" — "или".

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

Приведём другие примеры рекурсивных определений.

Пример 1. Классический пример, без которого не обходятся ни в одном рассказе о рекурсии, — определение факториала. С одной стороны, факториал определяется так: n!=1*2*3*...*n. С другой стороны,

Одномерные и двумерные массивы. Строки - student2.ru

Граничным условием в данном случае является n<=1.

Пример 2. Определим функцию K(n), которая возвращает количество цифр в заданном натуральном числе n:

Одномерные и двумерные массивы. Строки - student2.ru

Задание. По аналогии определите функцию S(n), вычисляющую сумму цифр заданного натурального числа.

Пример 3. Функция C(m, n), где 0 <= m <= n, для вычисления биномиального коэффициента Одномерные и двумерные массивы. Строки - student2.ru по следующей формуле Одномерные и двумерные массивы. Строки - student2.ru является рекурсивной.

Ниже будут приведены программные реализации всех этих (и не только) примеров.

Обращение к рекурсивной подпрограмме ничем не отличается от вызова любой другой подпрограммы. При этом при каждом новом рекурсивном обращении в памяти создаётся новая копия подпрограммы со всеми локальными переменными. Такие копии будут порождаться до выхода на граничное условие. Очевидно, в случае отсутствия граничного условия, неограниченный рост числа таких копий приведёт к аварийному завершению программы за счёт переполнения стека.

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

Выполнение действий в рекурсивной подпрограмме может быть организовано одним из вариантов:

{ { {

P; операторы; операторы;

операторы; P; P;

} } операторы;

}

рекурсивный подъём рекурсивный спуск и рекурсивный спуск,

и рекурсивный подъём

Здесь P — рекурсивная подпрограмма. Как видно из рисунка, действия могут выполняться либо на одном из этапов рекурсивного обращения, либо на обоих сразу. Способ организации действий диктуется логикой разрабатываемого алгоритма.

Реализуем приведённые выше рекурсивные определения в виде функций.

Пример 1.

double Factorial(int N)

{

double F;

if (N<=1) F=1.; else F=Factorial(N-1)*N;

return F;

}

Пример 2.

int K(int N)

{

int Kol;

if (N<10) Kol=1; else Kol=K(N/10)+1;

return Kol;

}

Пример 3.

int C(int m, int n)

{

int f;

if (m==0||m==n) f=1; else f=C(m, n-1)+C(m-1, n-1);

return f;

}

Пример 4. Вычислить сумму элементов линейного массива.

При решении задачи используем следующее соображение: сумма равна нулю, если количество элементов равно нулю, и сумме всех предыдущих элементов плюс последний, если количество элементов не равно нулю.

function changeProof(proofobj) { if (proofobj.style.display=='none') {proofobj.style.display='inline'} else {proofobj.style.display='none'} } Просмотреть листинг решения

#include <stdio.h> #include <conio.h> #include <stdlib.h> #include <time.h>   int summa(int N, int a[100]); int i,n, a[100];   void main() { clrscr(); printf("\nКоличество элементов массива? "); scanf("%d", &n); printf("\nВ сформированном массиве %d чисел:\n", n); randomize(); for (i=0; i<n; i++) {a[i]= -10+random(21); printf("%d ", a[i]);} printf("Сумма: %d", summa(n-1, a)); } int summa(int N, int a[100]) { if (N==0) return a[0]; else return a[N]+summa(N-1, a); }

Пример 5. Определить, является ли заданная строка палиндромом, т.е. читается одинаково слева направо и справа налево.

Идея решения заключается в просмотре строки одновременно слева направо и справа налево и сравнении соответствующих символов. Если в какой-то момент символы не совпадают, делается вывод о том, что строка не является палиндромом, если же удается достичь середины строки и при этом все соответствующие символы совпали, то строка является палиндромом. Граничное условие — строка является палиндромом, если она пустая или состоит из одного символа.

function changeProof(proofobj) { if (proofobj.style.display=='none') {proofobj.style.display='inline'} else {proofobj.style.display='none'} } Просмотреть листинг решения

#include <stdio.h> #include <conio.h> #include <string.h>   char s[100]; int pal(char s[100]);   void main() { clrscr(); printf("\nВведите строку: "); gets(s); if (pal(s)) printf("Строка является палиндромом"); else printf("Строка не является палиндромом"); } int pal(char s[100]) { int l; char s1[100]; if (strlen(s)<=1) return 1; else {l=s[0]==s[strlen(s)-1]; strncpy(s1, s+1, strlen(s)-2); s1[strlen(s)-2]='\0'; return l&&pal(s1);} }

Задание. Используя аналогичный подход, определите, является ли заданное натуральное число палиндромом.

Подводя итог, заметим, что использование рекурсии является красивым приёмом программирования. В то же время в большинстве практических задач этот приём неэффективен с точки зрения расходования таких ресурсов ЭВМ, как память и время исполнения программы. Использование рекурсии увеличивает время исполнения программы и зачастую требует значительного объёма памяти для хранения копий подпрограммы на рекурсивном спуске. Поэтому на практике разумно заменять рекурсивные алгоритмы на итеративные.

Контрольные вопросы и задания

Наши рекомендации