Сборник по задачам и примерам Assembler

       

Обход узлов дерева



Обход узлов дерева

Возможны три варианта обхода дерева:

  • сверху вниз — корень, левое поддерево, правое поддерево;
  • слева направо — левое поддерево, корень, правое поддерево;
  • снизу вверх — левое поддерево, правое поддерево, корень.
  • Понятно, что процедура обхода рекурсивна. Под термином «обход дерева» понимается то, что в соответствии с одним из определенных выше вариантов посещается каждый узел дерева и с его содержательной частью выполняются некоторые действия. Для нашей задачи подходит только второй вариант, так как только в этом случае порядок посещения узлов будет соответствовать упорядоченному массиву. В качестве полезной работы будем извлекать значение из узла двоичного дерева и выводить его в массив в памяти. Отметим особенности функции 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. Исключение из дерева

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


    Содержание раздела