Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Лабораторная работа 11. Программирование рекурсивных алгоритмов
Рекурсивные функции Написать программу упорядочивания массива методом быстрой сортировки, используя рекурсию. Говоря кратко, рекурсивной называется функция, в которой имеется обращение к ней самой. Алгоритм быстрой сортировки, заключается в использовании «процедурыа разделения», присмснясмая к фрагменту массива (изначально - ко всему массиву). На каждом шаге образовывались две половинки текущего фрагмента, и к ним снова нужно было применять процедуру разделения. То есть но своей сути алгоритм является рекурсивным, и если не здесь применять рекурсивные функции, то где?.. К счастью, любая функция в программе на C++ может вызываться рекурсивно. При этом в стеке выделяется новый участок памяти для размещения копий параметров, а также автоматических и регистровых переменных, поэтому предыдущее состояние выполняемой функции сохраняется, и к нему впоследствии можно вернуться (так и произойдет, если только вата программа где-нибудь не зависнет). Одна из возможных версий программы сортировки приведена ниже. «include < lostream.h> #include < iostream.h> void qsort(f1oat* array, int left, int right); int main(){ const int n = 10; float arr[n]; int i, l, r; cout < < " Введите элементы массива: "; for (i= 0; i < n; i++) cin > > arr[i1]; l=0; r= n - 1; // левая и правая границы начального фрагмента qsort(arr, l, r); // 1 for (i= 0; i< n; i++) cout< < аrr[i] < < ' '; return 0; } void qsort(float* array, int left, int right){ int 1= left, j = right; float middle = array[(left * right) / 2]; float temp; while (i < j) { while (array[i] < middle) i++; while (middle < array[j]) j--; if (i < =j) { temp = array[i]; array[i]= array[j]: array[j] = temp; i++; j--; } } if (left < j) qsort(array, left, j); if (i < right) qsort(array, i, right); } }} Мы не будем подробно анализировать эту программу, поскольку алгоритм уже использовался. Единственное изменение это то, что процедура разделения реализована здесь в виде рекурсивно вызываемой функции qsortt). в теле которой есть два обращения к самой себе: в операторе 2 — для сортировки левой (юловннки текущего фрагмента, и в операторе 3 — для сортировки его правой половинки. Однако, у рекурсии есть и недостатки: во-первых, такую программу труднее отлаживать, поскольку требуется контролировать глубину рекурсивного обращения, во-вторых, при большой глубине стек может переполниться, а в-третьих, использование рекурсии повышает накладные расходы (например, в данном случае в стеке сохраняются отнюдь не два числа, представляющие собой границы фрагмента, а гораздо больше, не говоря уже о затратах, связанных с вызовом функции). Поэтому рекурсию при всей ее красоте следует применять с осторожностью. Многофайловые проекты До сих пор мы писали программы, исходный текст которых размешался в одном файле. Однако, реальные задачи требуют создания многофайловых проектов с распределением решаемых подзадач по разным модулям (4> айлам). Напомним, что пользуясь технологией нисходящего проектирования программ. мы разбиваем исходную задачу на подзадачи, затем при необходимости каждая из них также разбивается на подзадачи, и так далее, пока решение очередной подзадачи не окажется достаточно простым, то есть реализуемым в виде функции обозримого размера (как уже указывалось, наиболее предпочтительным считается размер не более одного-двух экранов текстового редактора). Исходные тексты совокупности функций для решения какой-либо подзадачи, как правило, размешаются в отдельном модуле (файле). Такой файл называют исход-i: f.4 (source 1Не). Обычно он имеет расширение.с или.срр. Прототипы всех функций исходного файла выносят в отдельный так называемый заголовочный файл (headerfile), для него принято использовать расширение.h или.hop. Таким образом, заголовочный файл ххх.h содержит интерфейс для некоторого набора функций, а исходный файл ххх.ерр содержит реализацию этой ■ набора. Если некоторая функция из указанного набора вызывается из какого-то другого исходного модуля ууу. срр. то вы обязаны включить в этот модуль заголовочный файл ххх. h с помощью директивы #t ncl ибе'. Негласное правило стиля программирования на C++ требует включения этого же заголовочного файла (с помощью#include> и в исходный файл ххх.ерр. Теперь о глобальных переменных. В многофайловом проекте возможны два «вида глобальности». Если некоторая глобальная переменная glvarl объявлена в файле ххх.ерр с модификатором static, то она видима от точки определения до конца этого файла, то есть область ее видимости ограничена файлом. Если же другая глобальная переменная gl var2 объявлена в файле ххх.ерр без модификатора static, то она может быть видимой в пределах всего проекта. Правда, для того, чтобы она оказалась видимой в другом файле, необходимо иметь в этом файле ее объявление с модификатором extern (рекомендуется это объявление поместить в файл ххх. п). Что и как следует размещать в заголовочном файле В заголовочном файле принято размещать: □ определения типов, задаваемых пользователем, констант, шаблонов; □ объявления (прототипы) функций; Q объявления внешних глобальных переменных (с модификатором extern); -J пространства имен1. Теперь обратим ваше внимание на проблему повторного включения заголовочных файлов. Проблема может возникнуть при иерархическом проектировании структур данных, когда в некоторый заголовочный файл ууу. h включается при помощи директивы #inclixte другой заголовочный файл ххх. h (например, для использования типов, определенных в этом файле). Впрочем, лучше рассмотреть эту проблему на конкретном примере. Ниже приведены тексты очень простой многофайловой программы, в которой определены типы данных «Точка» (структура Point в файле Point.h) и «Прямоугольник» (структура Reel в файле Rect.п). Поскольку второй тип данных определяется через первый, в файле Rect.г имеется директива (Hnclude " Point.h". В основном модуле maln.cpp просто создаемся объект типа «Прямоугольник»1 и выводятся координаты его левого верхнего и правого нижнего углов. В основном модуле используются как функции из модуля Point.срр. так и функции из модуля Rect.срр. поэтому в него включены оба заголовочных файла Point.h и Rect.h. Но после обработки этих директив препроцессором окажется, что структура Point определена дважды. В результате компилятор выдаст сообщение об ошибке наподобие следующего: «error...: 'Point': 'struct' type redefinition». iiilUilll Файл Polnt.h HIIIIUII а Объявления тигов struct Point 1 int x: tnt y: ): // Прототипы функций void SetXYlPointi point, int x. 1nt y): mt GetX(Polnt4 point): int GetY(Pomt4 point): aaaaa Файл Rect.h Ulllll/ll . #tnclude " Point.h" // Объявление типов struct Rect { Point leftTop: Point rlghtBottom; ): // Прототипы функций void SetlTR8(Rect& rect. Point It. Point rb): void GetlTlRectS rect. Pointi It): void GetRBfRecti rect. Point* rb); aaaaa Файл Point.cpp шиши linclude -Point.h" void SetXKPoint* point. 1nt x. int y) { point.x - x: polnt.y - y: I Int Getx(Po1nt& point) { return point.x: int GetVtPoinU point) { return point.y: ) Ullllllll Файл Rect.cpp Ullllllll #include -Rect.h" void SetLTR6(Rect4 rect. Point It. Point rb) { rect.leftTop - It: rect.rlojitBottom - rt>; J void GeUT(Rect& rect. Points It) { It - rect.leftTop; I void GetRB(fiect& rect. Point* rb) { rb - rect.rightBotto»: I ♦ include < stdio.h> ♦ include -point.h" «Include 'Rect.h" llllllllll Файл Main.cpp Ullllllll int ma1n(){ Point ptl, pt2. It. rb: Rect recti: SetXY(ptl. 2. 5); SetXY(pt2. 10. 14); SetLTRBtrecti, ptl. pt2); GetLKrectl. It): GetRB(rectl. rb): printfCrect.lt.x - Xd. rect.lt.y - Xd\n". It.x. lt.y): printfCrect.rb.x - Xd. rect.rb.y - Xd\n\ rb.x. rt.y); return 0; } Каков выход из этой ситуации? Бьерн СтрауструЛ рекомендует использовать так называемые стражи включения, и этот способ нашел широкое применение. Он состоит в следующем; чтобы предотвратить повторное включение заголовочных файлов, содержимое каждого. h-файла-должно находиться между директивами условной компиляции #1fndef и #endif. как описано ни#е: #ifndef FILEW*_H Heffne FILENMC.H /* содержимое эасогювочного файла */ lendlf /* FILENAMEH */ Применительно к нашему примеру файл Point.h должен содержать следующий текст; IIIIIIIIII «айп Point.h UIIUUU #ifndef P01NT_H Wefine POINT.H // Объявления типов struct Point { int x; int y: )i // Прототипы функций void SetXY(Polnt& point, int x. int y): int GetX(Polnt4 point): int GetYfPoint* point): #endif /* P0INT_H •/ Реюлиндуем вам проверить рассмотренный пример на вашем компиляторе. Задача 7.7. Многофайловый проект — форматирование текста Написать программу форматирования текста, читаемою из файла unformt.txt и состоящего из строк ограниченной длины. Слова в строке разделены произвольным количеством пробелов. Программа должна читать входной файл по строкам, qbop-матировать каждую строку и выводить результат в выходной файл formtd. txt. Форматирование.заключаете* в выравнивании границ текста слева и справа путем равномерного распределения пробелов между соседними словами, а также в отступе с левой стороны страницы на margin позии/лй, то есть результирующий текстдолжен находиться в позицияхmargin ♦ 1..margin • maxljine. Кроме этого, программа должна подсчитать общее количество слов в тексте. На примере этой задачи мы показываем технологию разработки многофайловых проектов. Алгоритм решении задачи не представляет особой сложности: 1. Открыть входной фай л. 2. Читать файл построчно в текстовый буфер line, попутно удаляя возможные пробелы в начале строки (да первого слова). 3. Для каждойстроки line выполнить следующие действия: ■ Вычислить величину интервала (количество пробелов), которую необходимо обеспечить между соседними словами для равномерного распределения слов в пределах строки. ■ Вывести каждое слово из строки 1i пе в выходной файл, вставляя между словами необходимое количество пробелов и одновременно увеличивая счетчик слов па единицу. 4. После обработки последней строки входного файла вывести на экран значение счетчика слов и закрыть выходной файл. Разбиение на подзадачи. В результате детализации описанного алгоритма определяем спецификации нужных нам функций: Q void Definter (const char* pline. int 8 base_int. int A adoMnt. lot S inter) определяет для строки, на которую указывает pi ine. количество межсловных промежутков inter, требуемую величину основного интервала base^int для каждого промежутка (количество пробелов) и величину дополнительного интервала add_int. определяемую как остаток от деления общего количества пробелов в строке на количество межсловных промежутков; последняя величина должна быть равномерно распределена путем добавления одного пробела в каждый из первых add_mt промежутков; □ void Getltne (FILE* finp. char* pi ine) читает очередную строку из входного файла в массив символов с адресом р1 ire. ликвидируя при этом пробелы в начале строки; Q void Ptrtlfiterval (FILE* 'out. const int k) выводит очередной интервал, состоящий из К пробелов; □ Int PutWord (FILE* fout. const char* pline. const int startpos) выводит очередное слово в выходной файл, начиная с позиции startpos текущей строки pi ine; возвращает номер позиции в строке pltne, следующей за последним переданным символом, или 0 — если достигнут конец строки; Q int SearchNextWord (const char* pline. const int curpos) возвращает номер позиции, с которой начинается следующее слово в строке pi ine. или 0. если достигнут конец строки (поиск начинается с позиции curpos). Разбиение на модули. Наша программа будет располагаться Р двух исходных файлах: task7_7.cpp — с функцией main, edi t.срр - с реализацией перечисленных выше функций, а также заголовочный (райл edit.h с ннтер< ]> ейсом этих функций. Ниже приводится содержимое этих файлов. Illllllllllllltlllllttllllllllllllltlllllinitllllltlltlltl II Файл Task7_7.cpp ♦ include < stdio.h> ♦ include < strlng.h> «include < stdlib.h> ♦ include " edit.h" // Глобальные перевен^ые const irt maxlHne - 63: const int margin - 5;
Популярное:
|
Последнее изменение этой страницы: 2016-08-31; Просмотров: 633; Нарушение авторского права страницы