
Информатика в техническом университете / Информатика в техническом университете. Основы программирования
.pdf7. Программирование с использованием динамической памяти
щего варианта последовательности поиск вершины с ключом 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
Часть 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