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

Иванова Г.С. - Основы программирования

.pdf
Скачиваний:
2771
Добавлен:
02.04.2015
Размер:
13.53 Mб
Скачать

7. Программирование с использованием динамической памяти

щего варианта последовательности поиск вершины с ключом 7 будет выполнен за три шага (рис. 7.19).

Если мы добрались до листа, а иско­ мая вершина не обнаружена, то следует вьщать соответствующее сообщение. В противном случае вершину помечают как найденную (запоминают ее адрес) и обрабагываюг в соогвегствии с алгоритмом программы:

^^.^ „

'^"'-li'l "P""!L!!f'^ " бинарном дереве

pass:^root;

{начинаем с корня бинарного дерева}

flag:=false;

{признак «вершина не найдена»}

while (passonil)

and not flag do {пока не найден элемент или не до­

 

шли до листа}

ifn=pass^.value thenflag:^'true {значение найдено}

else

 

 

ifn<pass\value

then pass:-pass\left

{влево}

 

else pass:-pass\right; {вправо}

(/[;7ag rAew <вершина найдена>

 

else <вершина не найдена> ...

 

Поиск вершины также можно осуществлять, используя рекурсию. Для удобства использования построим рекурсивную функцию, которая будет возвращать true, если элемент найден, и false ~ в противном случае. Адрес найденного элемента будем возвращать через параметр pass:

Function Find(r:topj>tr; Varpass:topj)tr; n:integer):boolean; Begin

ifr^nil then Find:=fiilse {значение не найдено} else

ifn=r^.value then begin

Find:-true; {значение найдено} pass:=r; {запомнили адрес}

end

else

ifn<r^.value then

Find:^Find(r^Jefl,n) {влево} else Find: =Find(r^.right,n); {вправо}

End;

Вызывать такую функцию можно непосредственно из условия операто­ ра условной передачи управления или циклов, например:

241

Часть 1. Основы алгоритмизации и процедурное программирование

а б

Рис. 7.20. Удаление листа:

а - поиск удаляемой вершины; б- удаление вершины

if Find(r,pass,n) then <вершина найдена, адрес в pass>

else <вершина не найдена> ...

Удаление вершины с указанным ключом. Удалению вершины с ука­ занным ключом предшествует ее поиск (см. выше). Непосредственное удале­ ние вершины реализуется в зависимости от того, какая вершина удаляется:

удаляемая вершина не содержит поддеревьев (лист) - удаляем ссьшку на вершину из корня соответствующего поддерева (рис. 7.20);

удаляемая вершина содержит одну ветвь (рис. 7.21, а): для удаления необходимо скорректировать соответствующую ссылку в корне, заменив ад­ рес удаляемой вершины адресом вершины, из нее выходящей (рис. 7.21, б);

удаляемая вершина содержит две ветви (рис. 7.22, а): в этом случае нужно найти подходящую вершину, которую можно вставить на место уда­ ляемой, причем эта подходящая вершина должна легко перемещаться. Такая вершина всегда существует: это либо самый правый элемент левого подде­ рева, либо самый левый элемент правого поддерева удаляемой вершины (рис. 7.22, б).

Ниже представлена рекурсивная процедура удаления вершины с указан­ ным значением (параметры: г - адрес корня дерева, к - значение).

Рис. 7.21. Удаление корня с одним поддеревом:

а -> поиск удаляемой вершины; б- удаление вершины

242

7. Программирование с использованием динамической памяти

ш^

б

в

Рис. 7.22. Удаление корня с двумя поддеревьями:

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

в- замена вершины самой левой вершиной правого поддерева

Procedure Delete(var г:topj)tr;к:integer);

{Внутренняя рекурсивная процедура поиска заменяющей вершины в левом поддереве. Параметры: г - адрес корня левого поддерева, q - адрес заменяемой вершины}

Procedure delfvar r:topj)tr; q:topj)tr); Var ql:topj)tr;

begin

ifr\right=^nil then {заменяющая вершина найдена} begin

q\value:-r\value; {копируем значение}

 

r:-r\left;

{удаляем заменяющую вершину}

 

Dispose(ql);

{освобождаем память}

 

end

{идем по поддереву направо}

else del(r\right,ф

End;

 

Var q:topj)tr;

 

begin

then WriteLnCЭлeмeнm не найден или дерево пустое *)

ifr=nil

else

{поиск элемента с заданным ключом}

243

Часть L Основы алгоритмизации и процедурное программирование

if k<r\value then {если меньше, то налево}

Delete(r\lefUk)

else

ifk>r^.value then {если больше, то направо}

Delete(r\right,k)

else

begin {элемент найден, его необходимо удалить} {удаление листа или корня с одним поддеревом} ifr\right=nil then {нет правого поддерева}

begin

r:=r\left;

О18ро8е(ф; end

else

ifr\left=nil then {нет левого поддерева} begin

r:=r\ right; О18ро8е(ф;

end

else {удаление корня с двуми поддеревьями} del(r^Jeft,r);

end End;...

Сортировка с использованием дерева. Так как дерево формируется по определенным выше правилам, то сортировка по возрастанию осуществля­ ется обходом дерева «слева направо». Обход начинается с самого нижнего левого листа или, если такого листа нет, корня. Вывод значений осуществля­ ется в следующем порядке: сначала выводится значение самого нижнего ле­ вого поддерева, затем корня, затем самого нижнего левого поддерева право­ го поддерева и т.д. (рис. 7.23).

Пример 7.6. Разработать про­ грамму сортировки заданной по­ следовательности целых чисел с использованием сортированного бинарного дерева.

 

Программа должна строить

 

бинарное дерево из вводимых с

 

клавиатуры целых чисел, а затем

Рис. 7.23. Обход дерева «слева

осуществлять обход дерева для

направо»

вывода отсортированных данных.

244

7. Программирование с использованием динамической памяти

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

Обход дерева «слева направо» также будем осуществлять рекурсивно. Нерекурсивный вариант достаточно сложен, и его использование нецелесо­ образно.

Полностью текст программы приведен ниже.

Program Sort4;

 

 

Type topjptr^^top;

{тип "указатель на вершину дерева"}

top=record

 

{тип вершины дерева)

value:integer;

{целое число}

left, right:top_ptr;

{указатели на левое и правое поддеревья}

end;

 

 

 

Var next number:integer;

n pass:topj)tr;

{корень бинарного дерева}

{процедура добавления вершины к дереву}

Procedure AddfVar r:top_ptr; pass: top_ptr);

begin

 

 

 

ifr=nil

then r:=pass {если место свободно, то добавляем}

else

{иначе идем налево или направо}

if (pass\value<r\value)

then Add(r^.left,pass)

 

else Add(r\right,pass);

end;

{процедура сортировки - обход дерева} procedure Tree(r:top_ptr);

begin ifronilthen

begin {если есть поддерево}

Tree(r\left); {обход левого поддерева} Write(revalue: 4); {вывод значения из корня} Tree(r\right); {обход правого поддерева}

end;

end;

{основная программа} begin

{формирование исходного дерева}

WriteLnCВводите числа'); г:=пП;

Read(nextjiumber); while not EOF do

245

Часть L Основы алгоритмизации и процедурное программирование

begin

 

 

new(pass);

{выделяем память для нового элемента}

withpass^do

{заносим значения}

begin

 

 

value:

-nextjiumber;

left: =nil;

right: ==nil;

end;

 

 

Add(r, pass);

{добавляем элемент к дереву}

Readfnextjiumber)

 

end;

 

 

ReadLn;

 

 

WnteLnCCopmupoeaHuan

последовательность:);

Tree(r);

 

 

End

 

 

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

Операция поиска вершины. Худшим случаем при поиске вершины явля­ ется случай, когда дерево имеет вид последовательности вершин (рис. 7.24, а, б), В этом случае для поиска вершины в среднем потребуется (п+1)/2 проверок, как при последовательном поиске, например, в массиве, или Оу{х\),

Оценку в среднем выполним для сбалансированного дерева (рис. 7.24, в). Поиск вершины в этом случае потребует (Iog2 п4-1)/2 проверок, т. е. получаем вычислительную сложность в среднем Ocp(Iog2 п).

Операция построения дерева. Худшим случаем при построении дерева является дерево, представляющее собой последовательность вершин, так как при этом поиск вершины, к которой необходимо добавить новую, потребует максимального числа проверок. Количество проверок в этом случае: п-1 - для последнего элемента, 1 - для второго (для первого элемента проверки не

б

Рис. 7.24. Частные случаи бинарных деревьев:

а, б- деревья в виде последовательности вершин; в - сбалансированное дерево

246

7. Программирование с использованием динамической памяти

требуется). В среднем необходимо [(п-1)+1]/2 проверок; умножив на п-1 элемент, получаем п(п-1)/2 проверок, или OyivP-),

Оценку в среднем выполним также на сбалансированном дереве. Коли­ чество проверок для поиска родительской вершины составит (log2 (п-1 )+1)/2; соответственно, умножив на (п-1), получим 0^p(nlog2 п).

Операция обхода дерева в процессе получения сортированной последо­ вательности. Для деревьев обоих видов сложность одинакова, так как для каждой вершины при обходе проверяется наличие левого и правого поддере­ вьев, т.е. суммарное количество проверок равно 2п. Следовательно, вычис­ лительная сложность операции обхода равна 0(п). С учетом построения де­ рева вычислительная сложность в среднем составит 0^,р(п Iog2 п).

Задания для самопроверки

Задание 1. Разработайте профамму, которая обеспечивает быстрый поиск ин­ формации о сотрудниках фирмы с использованием бинарных деревьев: имя, фами­ лия, отчество, отдел, должность, служебный телефон, домашний адрес и телефон. Определите, во сколько раз быстрее в среднем будет выполняться поиск информации по сравнению с последовательным поиском (см. параграф 4.6), если количество со­ трудников фирмы 10, 100, 1000 человек.

Задание 2. Для программы предыдущего задания оцените объем оперативной памяти, необходимой для размещения информации о 300 сотрудниках фирмы. Опре­ делите долю памяти, отводимой для хранения адресов элементов.

7.6.Практикум. Разбор арифметических выражений

сиспользованием бинарных деревьев

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

Одним из способов разбора выражения является представление его в ви­ де дерева, каждое поддерево которого отображает одну операцию выражения в порядке убывания приоритета операции. В корнях такого дерева хранится знак операции, а каждое поддерево представляет собой операнд. Так, напри­ мер, выражение

(х+1,5)(х-10)(х+5)/(х.ЗЛ)

может быть представлено бинарным деревом, изображенным на рис. 7.25 (возведение в степень обозначено символом «^»).

247

Рис. 7.26. Представление в виде бинарного дерева выражения, содержащего элементарные функции

Часть I. Основы алгоритмизации и процедурное программирование

и \^ и v^ и v^ ^\^

©@0®0©0®

Рис. 7.25. Представление выражения в виде бинарного дерева

Построение дерева выражения выполняется следующим образом:

а) в символьной строке, содержащей запись выражения, определяется положение операции с минимальным приоритетом, записанной вне круглых скобок (если строка содержит несколько операций одинакового приоритета, то выбираем любую из них);

б) операция записывается в корень дерева, а строка делится на две: под­ строка первого операнда и подстрока второго операнда, при этом по необхо­ димости выполняется операция снятия скобок;

в) в подстроках операндов вновь определяется положение операции с минимальным приоритетом и т.д.

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

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

выполняется рекурсивно:

если вершина - лист, то в качестве ре­ зультата подставляется значение константы или переменной,

если вершина - операция, то вычисляет­ ся левый операнд, потом - правый операнд, а затем над ними выполняется операция.

Правила работы с деревом выражения лег­ ко можно дополнить так, чтобы обеспечить вы­

числение элементарных функций, таких, как sin X или In X. Например, имя функции будем за­ писывать в соответствующей вершине дерева, а аргумент представлять в виде левого поддерева. Выражение (x+l,5)*cos(2*x+5), таким образом, будет представлено деревом, изображенным на рис. 7.26.

248

7. Программирование с использованием динамической памяти

Пример 7.7. Разработать программу построения таблицы значений функции одного аргумента, определенной пользователем. (Чтобы не услож­ нять и без того сложную программу, не будем предусматривать операцию «унарный минус».)

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

Процедура Constr_Tree конструирования дерева из выражения по сути рекурсивна: она последовательно должна разбивать строку выражения на от­ дельные операции и строить соответствующие поддеревья. Параметры про­ цедуры: адрес корня дерева г и строка выражения, из которой необходимо по­ строить дерево. Процедура должна многократно осуществлять поиск разде­ ляющего знака операции: сначала нижнего уровня приоритета, затем все бо­ лее высокого. Этот поиск целесообразно реализовать как внутреннюю функ­ цию, которая будет получать множество знаков операции Set_Of и строку st.

Вычисление выражения по дереву таклсе будет выполняться рекурсив­ ной функцией Count. Эта функция в качестве параметров будет получать зна­ чение адрес корня дерева и значение х. При вычислении учтем, что выполне­ ние некоторых операций, например деления на ноль, получение логарифма отрицательного числа и т.п., невозможно; в этом случае функция Count будет возвращать значение параметра Key=false.

Program ex;

 

 

 

Type setChar=set of char;

{тип «множество символов»}

str80=strmg[80];

{тип «строка длиной 80 символов»}

рТор='^Тор;

 

{тип «указатель на вершину»}

Top=record

 

 

{тип «вершина»}

operator:string[5];

{знак операции}

value:single;

 

{значение константы}

lefUrighUpTop;

 

{указатели на левое и правое поддерево}

end;

 

 

 

Var st:str80;

 

 

{строка - запись выражения}

Root:pTop;

 

{корень дерева выражения}

кеу:Ьоо1еап; {признак существования значения в заданной точке}

x,xn,xe,dx,y:single;

{начальное, конечное значения и шаг

n,i:word;

 

 

аргумента, значение аргумента и функции}

{количество точек и номер текущей точки}

{рекурсивная функция конструирования поддерева выражения с корнем г из строки st}

Procedure Constr_Tree(r:pTop;st:str80);

Var next.'pTop; SetOp:setChar; po,code:integer; stlstri:str80; с:single;

249

Часть 1. Основы алгоритмизации и процедурное программирование

{внутренняя функция поиска разделительного знака в строке st: SetOp - множество знаков; функция возвращает позицию разде­ лительного знака или 0}

Function PosOp(st:str80;SetOp:setChar):byte; Var ij,k,p:byte;

begin j:=0;k:=0;p:=0;i:=l;

while (7<= length(st)) and (p'=0) do begin

ifst[ij= Y* ^hen inc(j) {считаем количество открывающихся скобок}

else ifst[i]'=')' then inc(k) {считаем количество закрывающихся скобок}

else ifQ'^k) and (st[i] in SetOp) thenp:=i;

inc(i):

end;

PosOp:=p;

end;

{раздел операторов функции конструирования дерева выражения}

Begin

po:='PosOp(st,[*+ \'- 7Л'

{ищем разделительный знак операции + или -}

ifpo=0

thenpo;=PosOp(st,f'*\

/']);

{ищем разделительный знак

 

 

 

 

 

 

операции * или /}

ifpo=0

thenpo:=PosOp(st,f'^*J);{ищем разделительный знак операции '^}

ifpooO

then

 

{разделяющий знак найден}

begin

 

 

 

 

 

 

г\operator: =st[ро];

{записываем знак операции в вершину}

stl:=copy(st,l,po-l);

 

{копируем подстроку первого операнда}

if(stlf]J=

Т) and (PosOpfstlJ'"' \ V\ Ч \'.;

'^ 7>=Ф then

 

 

stl:=copy(stl,2,length(stl)-2);

{убираем скобки}

stri:=copy(st,po-^lJength(st)'poJ;

{копируем подстроку второго опе­

 

 

 

ранда}

 

 

if(stri[l]=

'О and (PosOpfstriJ'*

\ 7\ Ч \'-', '^'])^0) then

 

 

stri:=copy(stri,2Jength(stri)'2);

{убираем скобки}

new(r^.left);

 

{создаем левое поддерево}

Constrjrree(r\left,stl);

 

{конструируем левый операнд}

new(r^.right);

{создаем правое поддерево}

Constr_Tree(r^,right,stri);

{конструируем правый операнд}

end

 

 

 

 

 

 

else

 

 

 

 

 

 

ifst[lj=

'х' then {аргумент}

 

 

begin

r^.operator: = 'x\'

250