Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Visual_Studio_2010

.pdf
Скачиваний:
109
Добавлен:
03.03.2016
Размер:
5.94 Mб
Скачать

//Подключаемый файл fcum.c

//Функция cumsum() #include "hcum.h"

int *(cumsum)(int *arr, int n)

{

int i;

int *SUM;// Указатель для накопительного массива

// Выделение памяти для нового указателя

SUM = (int *)calloc(n, sizeof(int)); setlocale(LC_ALL, ".1251"); // для русских шрифтов

// Проверка выделенной памяти if (SUM == NULL)

{

printf("\n Память не выделена. Нажмите любую клавишу"); _getch();

exit(1);

}

// Основной код формирования накопительной суммы

SUM[0] = arr[0];

for (i = 1; i < n; ++i)

SUM[i] = SUM[i-1] + arr[i];

return (SUM);

}

Следует обратить внимание на расположение заголовочного файла stdlib.h: он находится впереди подключаемого файла fcum.c, поскольку в последнем используется динамическое распределение памяти, для чего требуется библиотека stdlib.h. Формирование накопительной суммы выполнено в разработанной функции fcum.c.

В программе закомментированы библиотечные заголовочные файлы, которые включены в файл hcum.h. Сама накопительная функция cumsum() определена через указатель. Поэтому она возвращает указатель, который принимает адрес нулевого элемента массива накопительной суммы.

Возможный результат выполнения программы показан на рис.17.7.

Рис.17.7. Пример расчета накопительной суммы

311

Задание4

1.Файлы hcum.h и fcum.c расположите за пределами папки проекта. Примените различные комбинации расположения файлов hcum.h и fcum.c в нескольких папках рабочего диска. Обеспечьте работоспособность программы.

2.Внесите изменения в программу, чтобы она была работоспособной без заголовочного файла hcum.h.

3.Напишите программу формирования накопительной суммы вещественных чисел заданного массива. Формирование исходного массива произведите по случайному равномерному закону из интервала [–X; +X], где Х – номер компьютера, на котором выполняется лабораторная работа.

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

5.Напишите функцию для расчета накопительной суммы строк прямоугольной матрицы, размерность которой задается пользователем и заполняется, например, натуральными числами.

6.В программе предусмотрите вывод результатов в текстовый файл с именем compX.txt, где Х – номер компьютера, на котором выполняется лабораторная работа.

Пример 5. Написать программу быстрой сортировки Хоара одномерного массива целых чисел с расположением функций в разных файлах [5]. Предусмотреть формирование одномерного массива случайным образом с динамическим распределением памяти.

Программный код решения примера состоит из трех файлов

// 1-й

файл с главной функцией - файл main.c

#include

<stdio.h>

#include

<conio.h>

#include

<stdlib.h>

#include

<locale.h>

#include

<time.h>

 

 

#include

"hsort.h"// cозданный заголовочный файл

int main (void)

{

 

int

i, n;

int

*M;//

Указатель для исходного массива

int

Limit

= 100;

time_t t;

// переменная текущего времени

//Рандомизация генератора псевдослучайных чисел srand( (unsigned int) time(&t));

//Для поддержки русских шрифтов

setlocale(LC_ALL, ".1251");

printf("\n\t Быстрая сортировка Хоара\n");

312

printf("\n Введите размерность одномерного массива: "); scanf_s("%d", &n);

//Выделение памяти для заданной размерности массива

M = (int *)malloc(n*sizeof(int));

//Формирование случайного исходного массива

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

M[i] = -Limit/2 + rand() % Limit;

printf("\n Исходный одномерный массив:\n"); for (i = 0; i < n; ++i)

if (abs(M[i]) < 10) { if (M[i] < 0)

printf("%4d", M[i]);

else

printf("%3d", M[i]);

}

else {

if (M[i] < 0) printf("%5d", M[i]); else

printf("%4d", M[i]);

}

printf("\n\n Отсортированный одномерный массив:\n");

QuickSort(M, n); for (i = 0; i < n; ++i)

if (abs(M[i]) < 10) { if (M[i] < 0)

printf("%4d", M[i]); else

printf("%3d", M[i]);

}

else {

if (M[i] < 0) printf("%5d", M[i]); else

printf("%4d", M[i]);

}

printf("\n\n Нажмите любую клавишу: ");

_getch(); return 0;

}

//2-й файл - подключаемый заголовочный файл hsort.h

//с прототипом функции быстрой сортировки Хоара void QuickSort(int *A, int n);

313

//3-й файл - подключаемый файл my_sort.c

//с кодом быстрой сортировки Хоара

void QuickSort (int *A, int n)

{

int i, j, s; int L, R; int k, x;

#define D 1000 struct stack {

int L; int R;

} st[D];// имитация стека

s = 1; st[1].L = 0; st[1].R = n - 1;

do {

L = st[s].L; R = st[s].R; s--; do {

i = L; j = R;

x = A[(L+R)/2]; // разделяющий элемент

do {

while (A[i] < x) i++;

while (x < A[j]) j--;

if (i <= j){

k = A[i]; A[i] = A[j]; A[j] = k; i++; j--;

}// end if

} while (i < j); // end 3d do

if (i < R)

{s++; st[s].L = i; st[s].R = R;}

R = j;

}while (L < R);

}while (s != 0);

}

Цикл do – while применен для того, чтобы тело цикла выполнялось хотя бы один раз.

Возможный результат выполнения программы показан на рис.17.8.

314

Рис. 17.8. Пример сортировки одномерного массива

Задание5

1.Произведите сборку проекта из трех предложенных файлов.

2.Напишите программу с сохранением исходного массива и полученного отсортированного массива.

3.Выведите результаты в текстовый файл с именем compX.txt, где Х – номер компьютера, на котором выполняется лабораторная работа. В текстовом файле массивы выведите в виде двух столбцов: исходный массив и отсортированный массив. Размер массивов не менее 10.

4.Измените программу, исключив заголовочный файл hsort.h.

5.Введите изменения в программу для сортировки вещественных чисел.

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

Пусть задан полином в виде

P(x) a0 xn a1xn 1 an 1x an .

Алгоритм схемы Горнера осуществляется при помощи формулы [6]

P(x) a0 x(a1 x(a2 x(an 1 x an ) )).

С учетом того, что un an , расчет полинома в заданной точке x0 производится по следующей итерационной формуле [6]

uk x0uk 1 ak , k n 1, ,1,0.

Для программной реализации примера примем полином

P(x) x4 2x3 3x2 4x 5.

Рассчитаем значение полинома в точке x0 2 . Программный код решения примера

#include <stdio.h>

#include <conio.h>

#include <locale.h>

// Размерность массива коэффициентов полинома

#define N 5

315

// Главная функция int main (void) {

int i;

// Массив коэффициентов полинома

double A[ ] = {1.0, 2.0, 3.0, 4.0, 5.0}; double x0 = 2.0, y;

setlocale(LC_ALL, ".1251"); // для русских шрифтов setlocale(LC_NUMERIC, "English");// для вывода вещественных чисел

// Консольный заголовок

printf("\n\t Применение схемы Горнера\n");

printf(" Вычисление полинома P(x) %d-го порядка в точке х =

%1.4f:\n", N-1, x0);

// Основной цикла расчета по схеме Горнера y = A[0];

for ( i = N-2; i >= 0; --i ) y = y*x0 + A[(N-1)-i];

printf("\n\t P(x) = %0.4f \n", y ); // вывод результата

printf("\n Нажмите любую клавишу (Press any key): \a"); _getch();

return 0;

}

Следует обратить внимание на индексацию массива, который находится в теле цикла. В последней функции printf() использован спецификатор \a для подачи звукового сигнала.

Результат выполнения программы представлен на рис. 17.9.

Рис. 17.9. Результат вычисления полинома по схеме Горнера

Задание 6

1. Примените оператор цикла do – while вместо оператора цикла for.

2.Создайте подключаемый файл, в котором реализуется схема Горнера. Предусмотрите вызов этого файла из главной функции.

3.Предусмотрите ввод коэффициентов полинома с клавиатуры. Далее предусмотрите обращение к созданному файлу с реализацией схемы Горнера.

4.Напишите программу символической записи на консоли заданного полинома по известным коэффициентам. Оформите этот фрагмент программы в виде подключаемого файла.

316

Контрольные вопросы

1.Как рекомендуется организовать внутреннюю работу пользовательских функций по отношению к другим функциям в программах на языке С? Перечислите основные правила организации внутренней работы функций и их достоинства.

2.В чем заключается основное назначение заголовочных файлов (h-файлов) в проектах языка С?

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

4.Как следует объявить функцию, чтобы к ней можно было обращаться из других функций проекта?

5.Какие классификаторы классов памяти поддерживает стандарт языка С?

6.Какой классификатор памяти используется по умолчанию в программах на языке С?

7.Какие расширения можно применить к файлам, содержащим пользовательские функции?

8.Как осуществляется компиляция файлов с пользовательскими функциями в программной среде Visual Studio?

9.Как осуществляется подключение файлов с пользовательскими функциями, которые расположены на различных логических дисках компьютера?

БИБЛИОГРАФИЧЕСКИЙ СПИСОК

1.Прата С. Язык программирования С. Лекции и упражнения : пер. с англ. / С. Прата. – 5-е изд. – М. : Вильямс, 2006. – 960 с.

2.Шилдт Г. Полный справочник по С : пер. с англ./Г. Шилдт. – 4-е изд. –

М. : Вильямс, 2007. – 704 с.

3.Дейтл Х. М. Как программировать на С : пер. с англ./Х. М. Дейтл, П. Дж. Дейтл. – 4-е изд. – М. : Бином-Пресс, 2006. – 912 с.

4.Керниган Б. У. Язык программирования С : пер. с англ./Б. У. Керниган, Д. М. Ритчи. – 2-е изд. – М. : Вильямс, 2007. – 304 с.

5.Хусаинов Б. С. Структуры и алгоритмы обработки данных. Примеры на языке Си (+CD) : учеб. пособие / Б. С. Хусаинов. – М. : Финансы и стати-

стика, 2004. – 464 с.

6.Математический энциклопедический словарь / гл. ред. Ю. В. Прохоров ; ред. кол. : С. И. Адян, Н. С. Бахвалов, В. И. Битюцков [и др.]. – М. : Сов. энцикл., 1988. – 847 с.

317

Тема 18

РЕКУРСИВНЫЕ АЛГОРИТМЫ И ФУНКЦИИ

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

ТЕОРЕТИЧЕСКАЯ ЧАСТЬ

Рекурсивные функции (лат. recursio – возвращение) – в вычислительной математике функции, определенные на множестве натуральных чисел и принимающие значения того же множества [1].

Рекурсивный алгоритм – это алгоритм, решающий задачу путем решения одного или нескольких более простых вариантов той же задачи [2]. Функция называется рекурсивной, если в ее определении содержится вызов этой же функции [3]. Рекурсивная функция может вызывать саму се6я или непосредственно, или косвенно через другую функцию [4]. Рекурсии целесообразно применять в задачах, которые можно разбить на множество меньших подобных задач [5]. Рекурсия в программировании может быть определена как сведение задачи к такой же задаче, но манипулирующей более простыми данными.

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

Рекурсивный подход обычно предпочитается итеративному в тех случаях, когда рекурсия более полно и последовательно отражает математическую сторону задачи и приводит к программе, которая проще для понимания и отладки. Другой причиной для выбора рекурсивного решения является то, что итеративное может не быть очевидным [4].

Наличие в задаче рекуррентного соотношения позволяет использовать рекурсию. Например, арифметическая прогрессия – это последовательность чисел, в которой разность между последующими и предыдущими членами остается неизменной и называется разностью прогрессии [1]. То есть каждый следующий член прогрессии равен предыдущему, увеличенному на разность прогрессии.

Различают прямую и косвенную рекурсию. Прямой (непосредственной) рекурсией является вызов функции внутри ее тела, косвенной – рекурсия, осуществляющая рекурсивный вызов функции посредством цепочки вызова других функций [6]. Все функции, входящие в цепочку, тоже считаются рекурсивными.

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

318

и является простейшей формой рекурсии, поскольку действует подобно циклу [7]. Если в программе имеется хвостовая рекурсия, то ее лучше преобразовать к итерации.

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

Когда функция вызывает сама себя (когда имеем дело с рекурсивной функцией), новый набор локальных переменных и параметров размещается в памяти в стеке, а код функции выполняется с самого начала, причем используются именно эти новые переменные. При рекурсивном вызове функции новая копия ее кода не создается. Новыми являются только значения, которые использует данная функция. При каждом возвращении из рекурсивного вызова старые локальные переменные и параметры извлекаются из стека, и сразу за рекурсивным вызовом возобновляется работа функции с «внутренней» точки ее вызова [8].

Общая схема определения рекурсивной функции

Существует условие, при котором функция выполняет свою задачу с использованием рекурсивных вызовов, соответствующих одной или нескольким «уменьшенным» версиям задачи.

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

базовым или условием останова [2; 4–6].

Рассмотрим некоторые определения, относящиеся к рекурсии. Максимальное число рекурсивных вызовов без возвратов, которые происходят во время выполнения программы, называется глубиной рекурсии. Число рекурсивных вызовов в каждый конкретный момент времени – это текущий уровень рекурсии. В практических приложениях важно убедиться, что максимальная глубина рекурсий не только конечна, но и достаточно мала [11].

Существуют три разные формы рекурсивных программ:

1)форма с выполнением действий до рекурсивного вызова (на рекурсивном спуске);

2)фома с выполнением действий после рекурсивного вызова (на рекурсивном возврате);

3)форма с выполнением действий как до, так и после рекурсивного вызова (как на рекурсивном спуске, так и на рекурсивном возврате).

Понятие рекурсии сходно с понятием математической индукции. У нее,

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

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

319

Главное при оформлении рекурсивной функции – это правильное задание условия выхода из рекурсивных вызовов. Если при зацикливании программы на операторе цикла компьютер может выполнять его, не реагируя на любые действия пользователя, то при зацикливании из-за неправильного оформления рекурсивной функции неизбежно происходит аварийный останов, когда будет исчерпан объем оперативной памяти, которая выделяется при каждом рекурсивном вызове. Следует всегда помнить, что даже при правильном оформлении рекурсивной функции необходимо учитывать объем вычислительной работы. Так, расчет факториала целого положительного числа – трудоемкая операция. В математической постановке задачи нет ничего сложного, но при программировании этого процесса для различных систем и компьютеров имеются свои ограничения, например трудности рекурсивного вычисления факториала.

Для некоторых задач рекурсивные функции вполне оправданы. В частности, динамические информационные структуры включают рекурсивность в само определение обрабатываемых данных. Именно для таких данных применение рекурсивных алгоритмов не имеет конкуренции со стороны итерационных методов [6].

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

блемой – это называется рекурсивным вызовом, или шагом рекурсии. Шаг ре-

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

Рассмотрим пример вычисления факториала целого положительного числа, когда хвостовая рекурсия есть и когда ее нет. При этом покажем возможность исключения рекурсии. Пусть определена следующая рекурсивная функция:

int fact (int n) { if (n == 0)

return 1; else

return n * fact (n - 1);

}

Исходная функция fact() не имеет хвостовой рекурсии, так как перемножение выполняется после рекурсивного вызова (умножение на n). Для ис-

320

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