Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
81
Добавлен:
20.03.2016
Размер:
17.83 Mб
Скачать

Глава 7. Работа с указателями и структурами данных

133

Рекурсия в структурах

В структурах возможна рекурсия, т. е. структуры могут ссылаться сами на себя. Допустим, у нас имеется списковая структура типа:

"Слово (cтрока символов)", "Счетчик количества каждого искомого слова в тексте", "Указатель на предыдущую структуру, в которой встречается данное слово", "Указатель на последующую структуру, в которой встречается данная строка".

Такая структура может быть представлена в виде рекурсивного шаблона c указателем на такую же структуру:

struct tnod

//(***)

{

 

char *word;

 

int count;

 

struct tnod *left;

 

struct tnod *right;

 

}t,*p;

 

Если, например, p=&t, то доступ к элементам структуры будет таким:

p->word, p->left, p->right

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

Двоичное дерево — это такая конструкция, которая отображает связь между данными исходя из того, что данные находятся в так называемых узлах. Под узлом понимается переменная типа "структура" (ее еще называют записью). Первая запись двоичного дерева (его корень) содержит один узел. В узле содержится некая полезная информация, для обработки которой мы и строим само дерево, а также два указателя на нижестоящие узлы. Из корневого узла "вырастают" всего две "веточки": левый нижний узел (первый указатель) и правый нижний узел (второй указатель). Из каждого из этих узлов тоже "вырастает" по две "веточки" и т. д. Можно сказать, что в такой конструкции каждый узел — это тоже двоичное дерево (корень, из которого выходят две "веточки").

В нашем случае двоичное дерево строится так:

никакой узел этого дерева не содержит более двух ближайших потомков (детей);

в корневом узле слова нет;

первое поступившее слово записывается в корневой узел, а остальные поступающие будут распределяться так: если поступившее слово меньше первого, то оно записывается в левое поддерево, если больше корневого — в правое;

каждое слово будет находиться в структуре типа (***);

слово записывается в переменную word, а счетчик count в данной структуре устанавливается в единицу: слово пока что встретилось (в нашем случае, введено) один раз.

134

Часть I. Изучение языка С/С++

 

 

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

Потом вводится третье слово. Оно опять сравнивается со словами, находящимися в структурах дерева (***), начиная с корневой структуры и далее по тем же принципам, что описаны ранее. Если поступившее слово меньше корневого, то дальнейшее сравнение идет в левой части дерева. Это означает, что из корневой структуры выбирается указатель, хранящийся в переменной left, по нему отыскивается следующее (меньшее, чем в данном узле) слово, с которым и станет сравниваться поступившее. Если поступившее слово меньше, то из нового узла извлекается указатель, хранящийся в переменной left, и по нему выбирается следующий "левый" узел и т. д.

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

В итоге получим двоичное дерево: от каждого узла будут выходить не более двух подузлов и таких, что в левом подузле всегда будет слово, меньшее, чем в самом узле, а в правом подузле всегда будет находиться слово большее, чем в в самом узле. Так как физически каждое слово будет находиться в отдельной структуре типа (***), то в этой же структуре в переменных left и right будут располагаться указатели на структуры, где хранятся подузлы данной структуры. И все это дерево располагается в памяти.

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

Листинг 7.10

// 7.10_2011.cpp

#include "stdafx.h" #include <stdio.h> #include <conio.h>

#include <string.h> //strcpy() #include <malloc.h>

using namespace System;

Глава 7. Работа с указателями и структурами данных

135

#define maxline 1000

 

#define eof -1

 

 

int fix=0; //глобальная для печати дерева

 

//-------Ввод строки с клавиатуры

 

int getline(char s[],int lim)

 

{

 

 

int c,i;

 

 

for(i=0; i<lim-1 && (c=getchar()) != eof && c != '\n'; i++)

 

s[i]=c;

 

 

s[i]='\0';

 

i++;

//для учета количества

 

return(i);

 

 

}

 

 

//---------------------------------

 

 

struct tnod

//базовый узел

 

{

 

 

char *word;

//значение переменной — символьная строка в узле

 

 

//(слово)

 

int count;

//здесь накапливается число встреч данного слова

 

struct tnod *left; /*левый потомок: здесь хранится указатель на левый подузел, т. е. на структуру, в которой хранится слово, меньшее данного. Если слов меньших данного нет, то здесь хранится нуль */

struct tnod *right; /*правый потомок: здесь хранится указатель на правый подузел, т. е. на структуру, в которой хранится слово, большее данного. Если слов больших данного нет, то здесь хранится нуль */

};

char *strsave(char *s) // C++ позволяет объявлять без слова struct

/*Функция выделяет по malloc() память по длине строки s, и в эту память помещает саму строку s, а затем выдает указатель на начало помещенной в буфер строки. */

{

char *p;

p=(char *)malloc(sizeof(strlen(s)+1));

/*т. к. malloc() выдает указатель типа void, то принудительно приводим его к типу char, чтобы согласовать с р*/

if((p != NULL)) strcpy(p,s); return(p);

}

//---------------------------------

tnod *NodAlloc(void)

/*Функция выделяет память под структуру tnod и возвращает указатель на эту память*/

{

tnod *p=p=(tnod *)malloc(sizeof(tnod));

136

Часть I. Изучение языка С/С++

 

 

return(p);

 

}

 

//----------------------------------

 

tnod *MakeTree(tnod *p,char *w)

 

/*Эта функция формирует двоичное дерево.

 

p — указатель на структуру, по которой будет создаваться новый узел или будут проверяться старые узлы.

w — слово, поступающее для его поиска в дереве.

Если такое слово уже есть, то в счетчик структуры, где оно обнаружено, добавляется единица: подсчитывается, сколько одинаковых слов.

Если такого слова в дереве нет, то для него образуется новый узел (новая структура)

*/

{

int cond;

if(p==NULL) /*появилось новое слово. Для этого слова еще нет узла и его надо создать*/

{

p=NodAlloc(); //выделяется память под новый узел p->word=strsave(w);

/*введенное слово по strsave() записывается в память и на него выдается указатель */

p->count = 1; // слово пока единственное p->left=p->right= NULL;

/*т. к. это слово пока последнее в дереве, то этот узел не указывает на следующие (меньшие — слева или большие — справа) слова дерева*/

}

else if((cond=strcmp(w,p->word))==0)

/* слово не новое, тогда оно сравнивается со словом в узле, на который указывает указатель р*/

p->count++;

/*в этом узле — такое же слово, поэтому добавляем к счетчику единицу*/ else if(cond < 0)

/*слово меньше того, которое в узле, поэтому его помещаем в левую часть дерева опять же с помощью этой функции. Ее первая часть создаст узел в динамической памяти, поместит в него слово, в левые и правые подузлы поместит нули, т. к. пока дальше этого слова ни справа, ни слева ничего нет, а в левую часть узлапредка поместит указатель на эту структуру */

p->left=MakeTree(p->left,w); else if(cond > 0)

p->right=MakeTree(p->right,w);

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

return(p);

Глава 7. Работа с указателями и структурами данных

137

/*Возвращает указатель на область, где создана новая структура, или на структуру, в которую добавлена единица, т. к. поступившее слово совпало со словом в этой структуре*/

}

//-----------------------------

int TreePrint(tnod *p)

//left_right=1 при выводе левого узла //left_right=2 при выводе правого узла

/*Эта функция печатает или выводит на экран все дерево, рекурсивно себя вызывая*/

{

if(p != NULL) //p всегда указывает на начало дерева //р!=NULL — дерево существует

{

/*Left = ссылка на левый подузел, right — на правый. Эти ссылки могут быть нулевыми: дерево дальше не идет*/

printf("Count word repetition=%d word's value=%s\n",p->count,p->word); TreePrint(p->left); //выводит левый узел:

TreePrint(p->right); //выводит правый узел

}

return(0);

}

//-------------------------------------------------------------

#pragma argsused int main()

{

tnod *pp;

char s[maxline]; pp=NULL;

int i=0;

printf("Enter your words: >\n"); while(getline(s,maxline)-1)

/*getline() возвращает количество введенных символов

Когда нажимаем только <Enter>, вводится всего один символ ('\n').

Если от единицы отнять единицу, получим нуль, а это — для оператора while сигнал о прекращении цикла*/

{

pp=MakeTree(pp,s); // формирует очередной узел

// рр всегда указывает на начало дерева

} //while

TreePrint(pp); //вывод дерева

_getch();

}

Физическая структура дерева приведена на рис. 7.11.