Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь КАТЕГОРИИ: АрхеологияБиология Генетика География Информатика История Логика Маркетинг Математика Менеджмент Механика Педагогика Религия Социология Технологии Физика Философия Финансы Химия Экология ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Структуры, ссылающиеся на себя
Предположим, что нам надо справиться с более общей зада-чей, состоящей в подсчете числа появлений всех слов в неко-тором файле ввода. Так как список слов заранее не известен,мы не можем их упорядочить удобным образом и использоватьбинарный поиск. Мы даже не можем осуществлять последователь-ный просмотр при поступлении каждого слова, с тем чтобы ус-тановить, не встречалось ли оно ранее; такая программа будетработать вечно. (Более точно, ожидаемое время работы растеткак квадрат числа вводимых слов). Как же нам организоватьпрограмму, чтобы справиться со списком произвольных слов? Одно из решений состоит в том, чтобы все время хранитьмассив поступающих до сих пор слов в упорядоченном виде, по-мещая каждое слово в нужное место по мере их поступления.OДнако это не следует делать, перемещая слова в линейноммассиве, - это также потребует слишком много времени. Вместоэтого мы используем структуру данных, называемую доичным де-ревом. Каждому новому слову соответствует один "узел" дерева;каждый узел содержит:указатель текста слова счетчик числа появлений указатель узла левого потомка указатель узла правого потомка Никакой узел не может иметь более двух детей; возможно от-сутсвие детей или наличие только одного потомка. Узлы создаются таким образом, что левое поддерево каждо-го узла содержит только те слова, которые меньше слова вэтом узле, а правое поддерево только те слова, которые боль-ше. Чтобы определить, находится ли новое слово уже в дереве,начинают с корня и сравнивают новое слово со словом, храня-щимся в этом узле. Если слова совпадают, то вопрос решаетсяутвердительно. Если новое слово меньше слова в дереве, топереходят к рассмотрению левого потомка; в противном случаеисследуется правый потомок. Если в нужном направлении пото-мок отсутствует, то значит новое слово не находится в деревеи место этого недостающего потомка как раз и является мес-том, куда следует поместить новое слово. Поскольку поиск излюбого узла приводит к поиску одного из его потомков, то сампроцесс поиска по существу является рекурсивным. В соответс-твии с этим наиболее естественно использовать рекурсивныепроцедуры ввода и вывода. Возвращаясь назад к описанию узла, ясно, что это будетструктура с четырьмя компонентами: STRUCT TNODE \(/* THE BASIC NODE */ CHAR *WORD; /* POINTS TO THE TEXT */ INT COUNT; /* NUMBER OF OCCURRENCES */ STRUCT TNODE *LEFT; /* LEFT CHILD */ STRUCT TNODE *RIGHT; /* RIGHT CHILD */\); Это "рекурсивное" описание узла может показаться рискован-ным, но на самом деле оно вполне корректно. Структура неимеет права содержать ссылку на саму себя, но STRUCT TNODE *LEFT; описывает LEFT как указатель на узел, а не как сам узел. Текст самой программы оказывается удивительно маленьким,если, конечно, иметь в распоряжении набор написанных намиранее процедур, обеспечивающих нужные действия. Мы имеем ввиду функцию GETWORD для извлечения каждого слова из файлаввода и функцию ALLOC для выделения места для хранения слов. Ведущая программа просто считывает слова с помощью функ-ции GETWORD и помещает их в дерево, используя функцию TREE. #DEFINE MAXWORD 20MAIN() /* WORD FREGUENCY COUNT */\(STRUCT TNODE *ROOT, *TREE(); CHAR WORD[MAXWORD]; INT T; ROOT = NULL; WHILE ((T = GETWORD(WORD, MAXWORD)) \! = EOF) IF (T == LETTER) ROOT = TREE(ROOT, WORD); TREEPRINT(ROOT);\) Функция TREE сама по себе проста. Слово передается функ-цией MAIN к верхнему уровню (корню) дерева. На каждом этапеэто слово сравнивается со словом, уже хранящимся в этом уз-ле, и с помощью рекурсивного обращения к TREE просачиваетсявниз либо к левому, либо к правому поддереву. В конце концовэто слово либо совпадает с каким-то словом, уже находящимсяв дереве (в этом случае счетчик увеличивается на единицу),либо программа натолкнется на нулевой указатель, свидетель-ствующий о необходимости создания и добавления к дереву но-вого узла. В случае создания нового узла функция TREE возв-ращает указатель этого узла, который помещается в родитель-ский узел. STRUCT TNODE *TREE(P, W) /* INSTALL W AT OR BELOW P */ STRUCT TNODE *P; CHAR *W; \(STRUCT TNODE *TALLOC(); CHAR *STRSAVE(); INT COND; IF (P == NULL) \(/* A NEW WORD HAS ARRIVED */ P == TALLOC(); /* MAKE A NEW NODE */ P->WORD = STRSAVE(W); P->COUNT = 1; P->LEFT = P->RIGHT = NULL; \) ELSE IF ((COND = STRCMP(W, P->WORD)) == 0) P->COUNT++; /* REPEATED WORD */ ELSE IF (COND < 0)/* LOWER GOES INTO LEFT SUBTREE */ P->LEFT = TREE(P->LEFT, W); ELSE /* GREATER INTO RIGHT SUBTREE */ P->RIGHT = TREE(P->RIGHT, W); RETURN(P); \) Память для нового узла выделяется функцией TALLOC, явля-ющейся адаптацией для данного случая функции ALLOC, написан-ной нами ранее. Она возвращает указатель свободного прост-ранства, пригодного для хранения нового узла дерева. (Мывскоре обсудим это подробнее). Новое слово копируется функ-цией STRSAVE в скрытое место, счетчик инициализируется еди-ницей, и указатели обоих потомков полагаются равными нулю.Эта часть программы выполняется только при добавлении новогоузла к ребру дерева. Мы здесь опустили проверку на ошибкивозвращаемых функций STRSAVE и TALLOC значений (что неразум-но для практически работающей программы). Функция TREEPRINT печатает дерево, начиная с левого под-дерева; в каждом узле сначала печатается левое поддерево(все слова, которые младше этого слова), затем само слово, азатем правое поддерево (все слова, которые старше). Если вынеуверенно оперируете с рекурсией, нарисуйте дерево сами инапечатайте его с помощью функции TREEPRINT; это одна изнаиболее ясных рекурсивных процедур, которую можно найти. TREEPRINT (P) /* PRINT TREE P RECURSIVELY */ STRUCT TNODE *P; \(IF (P!= NULL) \(TREEPRINT (P->LEFT); PRINTF("%4D %S\N", P->COUNT, P->WORD); TREEPRINT (P->RIGHT); \)\) Практическое замечание: если дерево становится "несба-лансированным" из-за того, что слова поступают не в случай-ном порядке, то время работы программы может расти слишкомбыстро. В худшем случае, когда поступающие слова уже упоря-дочены, настоящая программа осуществляет дорогостоящую ими-тацию линейного поиска. Существуют различные обобщения дво-ичного дерева, особенно 2-3 деревья и AVL деревья, которыене ведут себя так "в худших случаях", но мы не будем здесьна них останавливаться. Прежде чем расстаться с этим примером, уместно сделатьнебольшое отступление в связи с вопросом о распределении па-мяти. Ясно, что в программе желательно иметь только одинраспределитель памяти, даже если ему приходится размещатьразличные виды объектов. Но если мы хотим использовать одинраспределитель памяти для обработки запросов на выделениепамяти для указателей на переменные типа CHAR и для указате-лей на STRUCT TNODE, то при этом возникают два вопроса. Пер-вый: как выполнить то существующее на большинстве реальныхмашин ограничение, что объекты определенных типов должныудовлетворять требованиям выравнивания (например, часто це-лые должны размещаться в четных адресах)? Второй: как орга-низовать описания, чтобы справиться с тем, что функция ALLOCдолжна возвращать различные виды указателей? Вообще говоря, требования выравнивания легко выполнитьза счет выделения некоторого лишнего пространства, простообеспечив то, чтобы распределитель памяти всегда возвращалуказатель, удовлетворяющий всем ограничениям выравнивания.Например, на PDP-11 достаточно, чтобы функция ALLOC всегдавозвращала четный указатель, поскольку в четный адрес можнопоместить любой тип объекта. единственный расход при этом -лишний символ при запросе на нечетную длину. Аналогичныедействия предпринимаются на других машинах. Таким образом,реализация ALLOC может не оказаться переносимой, но ее ис-пользование будет переносимым. Функция ALLOC из главы 5 непредусматривает никакого определенного выравнивания; в главе8 мы продемонстрируем, как правильно выполнить эту задачу. Вопрос описания типа функции ALLOC является мучительнымдля любого языка, который серьезно относится к проверке ти-пов. Лучший способ в языке "C" - объявить, что ALLOC возвра-щает указатель на переменную типа CHAR, а затем явно преоб-разовать этот указатель к желаемому типу с помощью операцииперевода типов. Таким образом, если описать P в виде CHAR *P;то (STRUCT TNODE *) P преобразует его в выражениях в указатель на структуру типаTNODE. Следовательно, функцию TALLOC можно записать в виде: STRUCT TNODE *TALLOC()\(CHAR *ALLOC(); RETURN ((STRUCT TNODE *) ALLOC(SIZEOF(STRUCT TNODE)));\) это более чем достаточно для работающих в настоящее времякомпиляторов, но это и самый безопасный путь с учетом будую-щего. Упражнение 6-4 ---------------- Напишите программу, которая читает "C"-программу и печа-тает в алфавитном порядке каждую группу имен переменных, ко-торые совпадают в первых семи символах, но отличаются где-тодальше. (Сделайте так, чтобы 7 было параметром). Упражнение 6-5 ---------------- Напишите программу выдачи перекрестных ссылок, т.е.Программу, которая печатает список всех слов документа и длякаждого из этих слов печатает список номеров строк, в кото-рые это слово входит. Упражнение 6-6 ---------------- Напишите программу, которая печатает слова из своегофайла ввода, расположенные в порядке убывания частоты их по-явления. Перед каждым словом напечатайте число его появле-ний.
Поиск в таблице
Поля
Объединения
Определение типа В языке "C" предусмотрена возможность, называемая TYPEDEFдля введения новых имен для типов данных. Например, описание TYPEDEF INT LENGTH; делает имя LENGTH синонимом для INT. "Тип" LENGTH может бытьиспользован в описаниях, переводов типов и т.д. Точно такимже образом, как и тип INT: LENGTH LEN, MAXLEN; LENGTH *LENGTHS[]; Аналогично описанию TYPEDEF CHAR *STRING; делает STRING синонимом для CHAR*, то есть для указателя насимволы, что затем можно использовать в описаниях вида STRING P, LINEPTR[LINES], ALLOC(); Обратите внимание, что объявляемый в конструкции TYPEDEFтип появляется в позиции имени переменной, а не сразу засловом TYPEDEF. Синтаксически конструкция TYPEDEF подобнаописаниям класса памяти EXTERN, STATIC и т. Д. мы также ис-пользовали прописные буквы, чтобы яснее выделить имена. В качестве более сложного примера мы используем конст-рукцию TYPEDEF для описания узлов дерева, рассмотренных ра-нее в этой главе: TYPEDEF STRUCT TNODE \(/* THE BASIC NODE */ CHAR *WORD; /* POINTS TO THE TEXT */ INT COUNT; /* NUMBER OF OCCURRENCES */ STRUCT TNODE *LEFT; /* LEFT CHILD */ STRUCT TNODE *RIGHT; /* RIGHT CHILD */ \) TREENODE, *TREEPTR; В результате получаем два новых ключевых слова: TREENODE(структура) и TREEPTR (указатель на структуру). Тогда функ-цию TALLOC можно записать в виде TREEPTR TALLOC() \(CHAR *ALLOC(); RETURN((TREEPTR) ALLOC(SIZEOF(TREENODE))); \) Необходимо подчеркнуть, что описание TYPEDEF не приводитк созданию нового в каком-либо смысле типа; оно только до-бавляет новое имя для некоторого существующего типа. приэтом не возникает и никакой новой семантики: описанные такимспособом переменные обладают точно теми же свойствами, что ипеременные, описанные явным образом. По существу конструкцияTYPEDEF сходна с #DEFINE за исключением того, что она интер-претируется компилятором и потому может осуществлять подста-новки текста, которые выходят за пределы возможностей мак-ропроцессора языка "C". Например, TYPEDEF INT (*PFI) (); создает тип PFI для "указателя функции, возвращающей значе-ние типа INT", который затем можно было бы использовать впрограмме сортировки из главы 5 в контексте вида PFI STRCMP, NUMCMP, SWAP; Имеются две основные причины применения описанийTYPEDEF. Первая причина связана с параметризацией программы,чтобы облегчить решение проблемы переносимости. Если для ти-пов данных, которые могут быть машинно-зависимыми, использо-вать описание TYPEDEF, то при переносе программы на другуюмашину придется изменить только эти описания. Одна из типич-ных ситуаций состоит в использовании определяемых с помощьюTYPEDEF имен для различных целых величин и в последующемподходящем выборе типов SHORT, INT и LONG для каждой имею-щейся машины.Второе назначение TYPEDEF состоит в обеспечении лучшей доку-ментации для программы - тип с именем TREEPTR может оказать-ся более удобным для восприятия, чем тип, который описантолько как указатель сложной структуры.И наконец, всегда существует вероятность, что в будущем ком-пилятор или некоторая другая программа, такая как LINT, смо-жет использовать содержащуюся в описаниях TYPEDEF информациюдля проведения некоторой дополнительной проверки программы.* 7. Ввод и вывод *
|
|||||||||
Последнее изменение этой страницы: 2016-08-26; просмотров: 254; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.133.119.66 (0.009 с.) |