книги / Проектирование программ и программирование на C++. Структурное программирование
.pdfбольше текущего осуществляется в правое поддерево, а меньше теку щего - в левое. Ключи не дублируется, поэтому необходимо прове рить, существует ли элемент с заданным ключом в дереве, и если су ществует, то завершить функцию добавления элемента.
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)