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

книги / Проектирование программ и программирование на C++. Структурное программирование

.pdf
Скачиваний:
4
Добавлен:
12.11.2023
Размер:
3.86 Mб
Скачать

больше текущего осуществляется в правое поддерево, а меньше теку­ щего - в левое. Ключи не дублируется, поэтому необходимо прове­ рить, существует ли элемент с заданным ключом в дереве, и если су­ ществует, то завершить функцию добавления элемента.

p o i n t *

f i r s t ( i n t d ) //ф о р м и р о в а н и е

п ер в о го

//э л е м е н т а

д е р е в а

 

 

 

 

{

 

 

 

 

 

 

 

p o in t*

p=new

p o i n t ;

 

 

 

p -> k e y = d ;

 

 

 

 

 

p - > l e f t = 0 ;

 

 

 

 

 

p - > r ig h t= 0 ;

 

 

 

 

r e t u r n

p ;

 

 

 

 

 

}

 

 

 

 

 

 

 

//д о б а в л е н и е

эл ем ен та

d в д ер ев о

поиска

P o in t* A d d ( p o in t* r o o t, i n t d)

 

 

{

 

 

 

 

 

 

 

P o in t* p = r o o t, * r;

 

 

 

//ф л а г

для п роверки сущ ествован ия

элем ен та d

b o o l o k = f a ls e ;

 

 

 

w h ile(p & & !ok)

 

 

 

 

{

 

r= p ;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

if(d = = p - > k e y ) o k = tru e ;

 

 

 

 

e l s e

 

 

 

 

 

//п о д д е р е в о

i f ( d < p - > k e y ) p = p - > l e f t ; //п о й т и в л ев о е

e l s e

p = p - > r i g h t ; //п о й т и

в

п р аво е

//п о д д е р е в о

 

 

 

 

 

 

}

 

 

 

 

 

 

 

i f ( o k )

r e t u r n

р ;//н а й д е н о , не

добавл яем

//с о з д а е м

у з е л

 

 

 

p o in t*

q=new

p o i n t ( ) ; //в ы д ел и л и

пам ять

q -> k ey = d ;

 

 

 

 

 

q - > l e f t = 0 ;

 

 

 

 

 

q - > r ig h t = 0 ;

 

 

 

 

//д о б а в л я е м

в л ев о е

п одд ер ево

 

 

i f ( d < r - > k e y ) r - > l e f t = q ;

 

 

//д о б а в л я е м

в п р аво е

п одд ер ево

 

 

e l s e

r - > r i g h t

=q;

 

 

 

r e t u r n

q;

 

 

 

 

 

19.11. Удаление элемента из дерева

Рассмотрим удаление элемента из дерева поиска (см. рис. 21). Следует учитывать следующие случаи:

1.Узла с требуемым ключом в дереве нет.

2.Узел с требуемым ключом является листом, т.е. не имеет потомков.

3.Узел с требуемым ключом имеет одного потомка.

4.Узел с требуемым ключом имеет двух потомков.

Впервом случае необходимо выполнить обход дерева и срав­ нить ключи элементов с заданным значением.

Во втором случае нужно заменить адрес удаляемого элемента ну­ левым. Для этого нужно выполнить обход дерева и заменить адрес

удаляемого элемента нулевым. Например, при удалении элемента с ключом 7 мы меняем левое адресное поле элемента с ключом 8 на 0.

Третий случай похож на второй, так как мы должны заменить адресное поле удаляемого элемента адресом его потомка. Например, при удалении элемента с ключом 8 мы меняем левое адресное поле элемента с ключом 9 на адрес элемента с ключом 7.

Самым сложным является четвертый случай, так как возникает вопрос, каким элементом мы должны заменить удаляемый элемент. Этот элемент должен иметь два свойства. Во-первых, он должен иметь не более одного потомка, а во-вторых, мы должны сохранить упорядоченность ключей, т.е. он должен иметь ключ либо не мень­ ший, чем любой ключ левого поддерева удаляемого узла, либо не больший, чем любой ключ правого поддерева удаляемого узла. Та­ ким свойством обладают два узла: самый правый узел левого подде­ рева удаляемого узла и самый левый узел правого поддерева удаляе­ мого узла. Любым из них и можно заменить удаляемый элемент. Например, при удалении узла 9 его можно заменить узлом 12 (самый левый узел правого поддерева удаляемого узла).

Для удаления будем использовать рекурсивную функцию.

/^вспомогательная переменная для удаления уз­

ла, имеющего двух потомков*/ point *q;

Point* Del(Point* r)

{

/*удаляет узел, имеющий двух потомков, заменяя

его правым узлом левого поддерева*/

i f ( r - > r i g h t 1=0) r = D e l ( r - > r i g h t ) ; / / и д е м

/ / в п равое п о д д е р е в о

 

e l s e / /д ош ли д о

с а м о г о п р а в о г о у з л а

{

/ / з а м е н я е м э т и м у з л о м у д а л я е м ы й q - > k e y = r - > k e y ;

q = r ;

r = r - > l e f t ;

}

r e t u r n г;

P o i n t * D e l e t e ( P o i n t * p , i n t KEY)

{

/ / P o i n t * q ;

i f ( p ) / / и щ е м у з е л

i f ( K E Y < p - > k e y ) / / и с к а т ь в л е в о м п о д д е р е в е p - > l e f t = D e l e t e ( p - > l e f t , K E Y ) ;

e l s e i f ( K E Y > p - > k e y ) / / и с к а т ь в п р а в о м

//поддереве

p->right=Delete(p->right, KEY); else//y3en найден

{

/ / у д а л е н и е

q = p ; / / з а п о м н и л и а д р е с у д а л я е м о г о у з л а / / у з е л и м ее т н е б о л е е о д н о г о п о т о м к а с л е в а

i f ( q - > r i g h t = = 0 )

p = q - > l e f t ; / / м е н я е м на п о т о м к а e l s e

/ / у з е л и м е е т н е б о л е е о д н о г о п о т о м к а с п р а в а i f ( q - > l e f t = = 0 )

p = q - > r i g h t ; / / м е н я е м на п о т о м к а e l s e / / у з е л и м е е т д в у х п о т о м к о в

p - > l e f t = D e l ( q - > l e f t ) ; d e l e t e q;

}

r e t u r n p;

19.12. Обработка деревьев с помощью рекурсивного обхода

Задача 1. Найти количество четных элементов в дереве.

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

//количество четных элементов в дереве void kol(Point *р, int &rez)

{

if (p)

{

kol(p->left,rez);

if(p->key%2==0)rez++;

kol(p->right, rez);

Задача № 2. Найти количество отрицательных элементов в де­

реве. Решается аналогично предыдущей.

//количество отрицательных элементов в дереве void quantity_otr(int a,int &k)

{

if (a<0)k++;

}

void kol(Point *p,void (*ptr)(int,int&), int

&rez)//итератор

{

if (p)

{

kol(p->left,quantity_otr,rez); ptr(p->key,rez);

kol(p->right, quantity_otr,rez);

}

20.ПРЕПРОЦЕССОРНЫЕ СРЕДСТВА

20.1.Стадии и команды препроцессорной обработки

Назначение препроцессора - обработка исходного текста про­

граммы до ее компиляции (можно назвать первой фазой компиля­ ции). Инструкции препроцессора называют директивами. Они начи­ наются с символа #.

На стадии обработки директив препроцессора возможно выпол­ нение следующих действий:

1. Замена идентификаторов заранее подготовленными последо­ вательностями символов ( # d e f i n e ) .

2. Включение в программу текста из указанных файлов (# i n c l u d e ).

3.Исключение из программы отдельных частей (условная компиляция).

4.Макроподстановка, т.е. замена обозначения параметризируемым текстом.

20.2. Директива #define

Директива # d e f in e имеет несколько модификаций. Они преду­ сматривают определение макросов или препроцессорных идентифи­ каторов, каждому из которых ставится в соответствие некоторая последовательность символов. В последующем тексте программы препроцессорные идентификаторы заменяются на заранее оговорен­ ные последовательности символов.

# d e f in e и дентиф икатор строка_ зам ещ ени я Пример работы директивы define:

До обработки

 

# d e fin e

b e g in

{

# d e fin e

end

}

v o id m ain() b e g in операторы end

После обработки

v o id m a in ()

{

операторы

}

С помощью # d e f in e удобно определять размеры массивов

Пример работы директивы define:

До обработки

# d e fin e

N

10

id e f in e

М

100

v o id m a in ()

{

in t m a tr [N ][N ]; d ou b le mas [M]

}

После обработки

v o id m a in ()

{

i n t m a tr [ 1 0 ] [ 1 0 ] ; d o u b le m a s[100]

}

Те же возможности в C++ обеспечивают константы, определен­ ные в тексте программы, поэтому в C++ по сравнению с классиче­ ским С #d e f in e используется реже.

v o i d m a i n ( ) / / и с п о л ь з о в а н и е к о н с т а н т в C++

{

c o n s t

i n t

N=10;

c o n s t

i n t

M=100;

i n t m a t r [ N ] [ N ] ;

d o u b l e

mas[M]

}

 

 

20.3. Включение текстов из файлов

Для включения текста из файла используется команда

#i n c l u d e . Она имеет две формы записи:

#i n c l u d e <имя_ файла>

#i n c l u d e "имя_ файла"

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

По принятому соглашению к тем файлам, которые надо поме­ щать в заголовке программы, приписывается расширение h (заголо­ вочные файлы).

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

в одном файле, который с помощью директивы i n c l u d e включается во все модули, где необходимы эти объекты.

/ / ф а й л t r e e , h

/ / д е р е в о и функции для е г о формирования / / и п е ч а т и

# i n c l u d e < i o s t r e a m . h > s t r u c t P o i n t

{

i n t k e y ;

P o i n t * l e f t , * r i g h t ;

};

P o i n t * f i r s t ( i n t d ) / / ф о р м и р о в а н и е п е р в о г о

/ / э л е м е н т а д е р е в а

{

P o i n t * p=new P o i n t ;

p - > k e y = d ; p - > l e f t = 0 ;

p - > r i g h t = 0 ; r e t u r n p;

}

P o i n t * A d d ( P o i n t * r o o t , i n t d ) / / д о б а в л е н и е / / э л е м е н т а d в д е р е в о п о и с к а

{

P o i n t * p = r o o t , * r ; b o o l o k = f a l s e ;

w h i l e ( p & & ! ok)

{

r = p ;

i f ( d = = p - > k e y ) o k = t r u e ; e l s e

i f ( d < p - > k e y ) p = p - > l e f t ; / / п о й т и / / в л е в о е п о д д е р е в о

e l s e p = p - > r i g h t ; / / п о й т и в п р а в о е

/ / п о д д е р е в о

}

i f ( o k ) r e t u r n р ; / / н а й д е н о , не д о б а в л я е м / / с о з д а е м у з е л

P o i n t * N ew _ p oin t= n e w P o i n t ( ) ; //в ы д е л и л и память

New_point->key=d;

N e w _ p o i n t - > l e f t = 0 ;

N e w _ p o i n t - > r i g h t = 0 ;

i f ( d < r - > k e y ) r - > l e f t = N e w _ p o i n t ; e l s e r - > r i g h t = N e w _ p o i n t ;

r e t u r n N e w _ p o i n t ;

}

v o i d S h o w ( P o i n t * p , i n t l e v e l )

{

i f (P)

{

S h o w ( p - > l e f t , l e v e l + 5 ) ;

f o r ( i n t i = 0 ; i < l e v e l ; i + + ) c o u t < < " c o u t < < p - > k e y < < " \ n " ;

S h o w ( p - > r i g h t , l e v e l + 5 ) ;

}

}

/ / Ф а й л с о с н о в н о й п р о г р а м м о й

# i n c l u d e < i o s t r e a m . h >

#include "tree.h"

v o i d m a i n ()

{

i n t n , k ; c o u t « " n ? " ;

c i n > > n ;

P o i n t * r o o t = f i r s t ( 1 0 ) ; / / п е р в ы й э л е м е н т f o r ( i n t i = 0 ; i < n ; i + + )

{

c o u t « ,,? M; c i n > > k ;

A d d ( r o o t , k ) ;

}

S h o w ( r o o t , 0 ) ;

}

Препроцессор добавляет текст файла t r e e . h в файл, в котором расположена основная программа, и, как единое целое, передает на компиляцию.

20.4. Условная компиляция

Для условной компиляции используются следующие команды:

# i f константное выражение

позволяют выполнить проверку

# i f d e f

препроцессорный

и ден ­ условий

тификатор

 

i i f n d e f

препроцессорный

иден ­

тификатор

 

# e is e

 

позволяют определить диапазон дей­

# e n d if

 

ствия проверяемого условия.

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

# i f у сл о в и е т е к с т 1

#e ls e т ек ст 2

#e n d if

Конструкция #else текст2 необязательна. Текст1 включа­ ется в компилируемый текст только при истинности проверяемого условия. Если условие ложно, то при наличии директивы else на компиляцию передается текст2, если эта директива отсутствует, то

при ложном условии текст 1 просто опускается.

Различие между форматами команд # i f следующее:

1. Директива # i f константное_ вы раж ение проверяет зна­

чение константного выражения. Если оно отлично от нуля, то счита­ ется, что проверяемое условие истинно.

2. В директиве # i f d e f препроцессорный

идентификатор про­

веряется, определен ли с помощью директивы

ttd e fin e и д ен ти ­

фикатор, помещенный после # i f d e f . Если идентификатор опреде­ лен, то т е к с т 1 используется компилятором.

3. В директиве # if n d e f препроцессорный идентификатор про­ веряется обратное условие: истинным считается неопределенность идентификатора. Если идентификатор не определен, то т е к с т 1 ис­ пользуется компилятором.

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

программы. Такими средствами защиты снабжены все заголовочны

файлы стандартной библиотеки (например, iostream. h).

Схема защиты от повторного включения:

//Файл с именем filename включается в другой //файл

#ifndef _FILE_NAME

....//включаемый текст файла filename #define _FILE_NAME 1

#include <...>//заголовочные файлы <текст модуля>

#endif

FILE NAME - зарезервированный для файла filename препро-

цессорный идентификатор, который не должен встречаться в других

текстах программы.

20.5. Макроподстановки средствами препроцессора

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

С помощью директивы # d e f in e идентификатор с т р о - ка_зам ещ ения можно вводить макроопределения, в которых строка замещения фиксирована. Большими возможностями обладает макро­ определение с параметрами:

#define имя (список_параметров) строка_замещения

Здесь имя - это имя макроса (идентификатор), список_параметров - список разделенных запятыми идентификаторов.

//пример 1

#define max(a,b) (a<b?b:а)//макроопределение

max(x,y)// заменяется выражением (х<у?у:х) max(z,4)// заменяется выражением (z<4?4:z) //пример 2

#define ABS(x) (х<0?-(х):х)//макроопределение

ABS(E-Z)//заменяется выражением (E-Z<0?-(E-Z):E-Z)

Соседние файлы в папке книги