Возможны три варианта обхода дерева:
- сверху вниз — корень, левое поддерево, правое поддерево;
- слева направо — левое поддерево, корень, правое поддерево;
- снизу вверх — левое поддерево, правое поддерево, корень.
Понятно, что процедура обхода рекурсивна. Под термином «обход дерева» понимается то, что в соответствии с одним из определенных выше вариантов посещается каждый узел дерева и с его содержательной частью выполняются некоторые действия. Для нашей задачи подходит только второй вариант, так как только в этом случае порядок посещения узлов будет соответствовать упорядоченному массиву. В качестве полезной работы будем извлекать значение из узла двоичного дерева и выводить его в массив в памяти. Отметим особенности функции LRBeat, производящей обход дерева. Во-первых, она рекурсивная, во-вторых, в ней для хранения адресов узлов используется свой собственный стек. Стек, поддерживаемый на уровне архитектуры микропроцессора, используется для работы процедурного механизма, и мы со своими проблемами только «путаемся у него под ногами».
:prg02_13.asm - программа обхода двоичного
дерева поиска (слева направо). ;Вход: произвольный масиив чисел в
памяти. :Выход: двоичное дерево в памяти.
объявления структур: nodejxee struc :узел дерева
ends
: для программного стека
desc_stk struc :дескриптор стека
ends
•.описание макрокоманд работы со стеком:
create_stk macro HandHead:REQ.descr:REQ.Si zeStk:=<256>
¦.создание стека
endm
push_stk macro descr:REQ.rg_item:REQ
добавление элемента в стек
endm
pop_stkmacro descr:REQ. rg_item:REQ
извлечение элемента из стека
endm
.data
исходный массив:
masdb 37h.l2h,8h.65h.4h.54h.llh.02h.32h,5h,4h,87h.7h.21h.65h.45h.22h,llh.77h.
51h.26h.73h.35h.12h.49h.37h.52h l_mas=$-mas
упорядоченный массив (результат см. в отладчике): ordered array db 1 mas dup (0)
doubleWord_stkdesc_stk <> -.дескриптор стека
count call dd 0 :счетчик количества рекурсивного вызова процедуры
.code
BuildBinTree proc ;см. prg02_12.asm
:двоичное дерево поиска построено
ret
BuildBinTree endp LRBeat proc
:рекурсивная процедура обхода дерева - слева направо :(левое поддерево, корень, правое поддерево)
inc count_call ;подсчет количества вызовов процедуры
:(для согласования количества записей и извлечений из стека)
cmp ebx.O
je @@exit_p
mov ebx.[ebx].l_son
cmp ebx, 0
je @@ml
push_stk doubleWord_stk.ebx mini: call LRBeat
pop stkdoubleWord stk.ebx
r r_ —
jnc @@m2
:подчистим за собой стек и на выход raovecx.count_call
dec ecx
pop NewNode:pop "в никуда"
loop $-6
jmp @@exi t_p @@m2: moval,[ebx].simbol
stosb
mov ebx, [ebx]. r_son cmp ebx.O je @@ml push stk doubleWord stk.ebx
c'all LRBeat " @@exit_p: deccount_call
ret
LRBeat endp start proc near ;точка входа в программу:
call BuildBinTree :формируем двоичное дерево поиска ;обходим
узлы двоичного дерева слева направо и извлекаем значения ;из
содержательной части, нам понадобится свой стек (размер (256 байт) нас
устроит. :но макроопределение мы подкорректировали)
create_stk Hand_Head.doubieWord_stk
push ds
pop es
lea edi.ordered_array
mov ebx.HeadTree push_stk doubleWord_stk.ebx указатель на корень в наш стек
call LRBeat
Еще одно замечание о рекурсивной процедуре. Реализация
рекурсивное™ в программах ассемблера лишена той комфортности, которая
характерна для языков высокого уровня. При реализации рекурсивной
процедуры LRBeat возникает несбалансированность стека, в результате
после последнего ее выполнения на вершине стека лежит не тот адрес и
команда RET отрабатывает неверно. Для устранения подобного эффекта нужно
вводить корректировочный код, суть которого заключается в следующем.
Процедура LRBeat подсчитывает количество обращений к ней и легальных, то
есть через команду RET, выходов из нее. При последнем выполнении
анализируется счетчик обращений countcal 1 и производится корректировка
стека.
Для полноты изложения осталось только показать, как изменится процедур;!. LRBeat для других вариантов обхода дерева.
Построенное выше бинарное дерево теперь можно использовать для
дальнейших операций, в частности поиска. Для достижения максимальной
эффективности поиска необходимо, чтобы дерево было сбалансированным.
Так, дерево считается идеально сбалансированным, если число вершин в его
левых и правых поддеревьях отличается не более чем на 1. Более
подробные сведения о сбалансированных деревьях вы можете получить,
изучая соответствующую литературу. Здесь же будем считать, что
сбалансированное дерево уже построено. Разберемся с тем, как производить
включение и исключение узлов в подобном, заранее построенном
упорядоченном дереве.
Включение узла в упорядоченное бинарное дерево
Задача включения узла в дерево уже была решена нами при
построении дерева. Осталось оформить соответствующий фрагмент программы
в виде отдельной процедуры addNodeTree. Чтобы не дублировать код,
разработаем рекурсивный вариант процедуры включения — addNodeTree. Он
хоть и не так нагляден, как нерекурсивный вариант кода в программе
prg_03.asm, но выглядит достаточно профессионально. Текст процедуры
addNodeTree вынесен на дискету.
Работу процедуры addNodeTree можно проверить с помощью программы prgO2_ 13.asm (в ПРИМЕРе ей соответствует программа prgO2_14.asm).
В результате проведенных работ мы получили дублирование кода,
строящего и дополняющего дерево. В принципе для строительства и
включения в дерево достаточно одной процедуры addNodeTree. Но в учебных
целях мы ничего изменять не будем, чтобы иметь два варианта
строительства дерева — рекурсивный и не рекурсивный. При необходимости
читатель сам определит, какой из вариантов более всего отвечает его
задаче и в соответствии с этим доработает этот код.
Исключение узла из упорядоченного бинарного дерева
Эта процедура более сложная. Причиной тому то обстоятельство,
что существует несколько вариантов расположения исключаемого узла в
дереве: узел является листом, узел имеет одного потомка и узел имеет
двух потомков. Самый непростой случай — последний. Процедура удаления
элемента del NodeTree является рекурсивной и, более того, содержит
вложенную процедуру del, которая также является рекурсивной. Текст
процедуры del NodeTree вынесен на дискету.
Работу этой процедуры можно проверить с помощью программы prg02_13.asm (в ПРИМЕРе ей соответствует программа prg02_15.asm).
Графически процесс исключения из дерева выглядит так, как показано на рис. 2.20. Исключаемый узел помечен стрелкой.
Рис. 2.20. Исключение из дерева
Для многих прикладных задач, занимающихся обработкой символьной информации, важное значение могут иметь так называемые лексикографические деревья. Для нашего изложения они важны тем, что эффективно могут быть применены при разработке трансляторов, чему мы также уделим внимание в этой книге.