Главная страница
Навигация по странице:

  • 4.4.2. Основные операции

  • 4.4.3. Поиск и вставка в деревьях

  • Алгоритмы и структуры данныхНовая версия для Оберона cdмосква, 2010Никлаус ВиртПеревод с английского под редакцией


    Скачать 2.67 Mb.
    НазваниеАлгоритмы и структуры данныхНовая версия для Оберона cdмосква, 2010Никлаус ВиртПеревод с английского под редакцией
    Анкор11221
    Дата18.05.2023
    Размер2.67 Mb.
    Формат файлаpdf
    Имя файлаAlgoritmy_i_struktury_dannyh.pdf
    ТипКнига
    #1142198
    страница16 из 22
    1   ...   12   13   14   15   16   17   18   19   ...   22

    4.4. Деревья
    4.4.1. Основные понятия и определения
    Мы видели, что последовательности и списки удобно определять следующим об%
    разом. Последовательность (список) с базовым типом
    T
    – это:
    1) пустая последовательность (список);
    2) конкатенация (сцепление) некоторого элемента типа
    T и последовательно%
    сти с базовым типом
    T
    Тем самым рекурсия используется как средство определения метода структу%
    рирования, а именно последовательности или итерации. Последовательности и итерации встречаются настолько часто, что их обычно рассматривают в качестве фундаментальных схем структурирования и поведения. Но нужно помнить, что их определить с помощью рекурсии можно, однако обратное неверно, тогда как рекурсию можно элегантно и эффективно использовать для определения гораздо более изощренных структур. Хорошо известный пример – деревья. Определим понятие дерева следующим образом. Дерево с базовым типом
    T
    – это либо:
    1) пустое дерево, либо
    2) узел типа
    T
    с конечным числом поддеревьев, то есть не соединяющихся меж%
    ду собой деревьев с базовым типом
    T
    Сходство рекурсивных определений последовательностей и деревьев показы%
    вает, что последовательность (список) – это дерево, в котором у каждого узла не больше одного поддерева. Поэтому список можно считать вырожденным деревом.
    Есть несколько способов представить дерево. Например, рис. 4.17 показывает несколько таких представлений для дерева с базовым типом
    T = CHAR
    . Все эти представления изображают одну и ту же структуру и потому эквивалентны. Но именно представление в виде графа объясняет, почему здесь используют термин
    «дерево». Довольно странно, что деревья чаще изображают растущими вниз или –
    если использовать другую метафору – показывают корни деревьев. Однако последнее описание вносит путаницу, так как корнем (
    root
    ) обычно называют верхний узел (A).
    Упорядоченное дерево – это такое дерево, в котором для ветвей каждого узла фиксирован определенный порядок. Поэтому два упорядоченных дерева на рис. 4.18 различны. Узел y
    , который находится непосредственно под узлом x
    , на%
    зывается (непосредственным) потомком узла x
    ; если x
    находится на уровне i
    , то говорят, что y
    находится на уровне i+1
    . Обратно, узел x
    называется (непосредст%
    венным) предком узла y. Уровень корня по определению равен нулю. Максималь%
    ный уровень элементов в дереве называется его глубиной, или высотой.
    Деревья

    Динамические структуры данных
    192
    Рис. 4.17. Представление дерева посредством (a) вложенных подмножеств,
    (b) скобочного выражения, (c) текста с отступами, (d) графа
    Рис. 4.18. Два разных упорядоченных дерева

    193
    Если узел не имеет потомков, то он называется концевым (терминальным), или
    листом; узел, не являющийся концевым, называется внутренним. Количество
    (непосредственных) потомков внутреннего узла – это его степень. Максимальная степень всех узлов называется степенью дерева. Число ветвей или ребер, по кото%
    рым нужно пройти, чтобы попасть из корня в узел x
    , называется его длиной пути.
    Длина пути корня равна нулю, его непосредственных потомков – 1 и т. д. Вообще,
    длина пути узла на уровне i
    равна i
    . Длина путей дерева определяется как сумма длин путей всех его компонент. Ее еще называют длиной внутренних путей. На%
    пример, у дерева на рис. 4.17 длина внутренних путей равна 36. Очевидно, сред%
    няя длина пути равна
    P
    int
    = (S
    S
    S
    S
    Si: 1
    ≤ i ≤ n: n i
    × i) / n где n
    i
    – число узлов на уровне i
    . Чтобы определить длину внешних путей, расши%
    рим дерево особыми дополнительными узлами всюду, где в исходном дереве от%
    сутствовало поддерево. Здесь имеется в виду, что все узлы должны иметь одина%
    ковую степень, а именно степень дерева. Такое расширение дерева равнозначно заполнению пустых ветвей, причем у добавляемых дополнительных узлов, конеч%
    но, потомков нет. Дерево с рис. 4.17, расширенное дополнительными узлами, по%
    казано на рис. 4.19, где дополнительные узлы показаны квадратиками. Длина вне%
    шних путей теперь определяется как сумма длин путей всех дополнительных узлов. Если число дополнительных узлов на уровне i
    равно m
    i
    , то средняя длина внешнего пути равна
    P
    ext
    = (S
    S
    S
    S
    Si: 1
    ≤ i ≤ m i
    × i) / m
    Длина внешних путей дерева на рис. 4.19 равна 120.
    Число m
    дополнительных узлов, которые нужно добавить к дереву степени d
    ,
    определяется числом n
    исходных узлов. Заметим, что к каждому узлу ведет в точ%
    ности одно ребро. Таким образом, в расширенном дереве m + n ребер. С другой
    Рис. 4.19. Троичное дерево, расширенное дополнительными узлами
    Деревья

    Динамические структуры данных
    194
    стороны, из каждого исходного узла выходит d
    ребер, из дополнительных узлов –
    ни одного. Поэтому всего имеется d*n + 1
    ребер, где
    1
    соответствует ребру, веду%
    щему к корню. Две формулы дают следующее уравнение, связывающее число m
    дополнительных узлов и число n
    исходных узлов: d
    ×
    n + 1 = m + n
    , откуда m = (d–1)
    × n + 1
    Дерево заданной высоты h
    будет иметь максимальное число узлов, если у всех узлов есть d
    поддеревьев, кроме узлов на уровне h
    , у которых поддеревьев нет.
    Тогда у дерева степени d
    на уровне 0 есть 1 узел (а именно корень), на уровне 1 –
    d его потомков, на уровне 2 – d
    2
    потомков d
    узлов уровня 1 и т. д. Отсюда получа%
    ем выражение
    N
    d
    (h) = S
    S
    S
    S
    Si: 0
    ≤ i < h: d i
    для максимального числа узлов дерева высоты h
    и степени d
    . В частности, для d = 2
    получаем
    N
    2
    (h) = 2
    h
    – 1
    Особенно важны упорядоченные деревья степени 2. Их называют двоичными
    (бинарными) деревьями. Определим упорядоченное двоичное дерево как конеч%
    ное множество элементов (узлов), которое либо пусто, либо состоит из корня
    (корневого узла) с двумя отдельными двоичными деревьями, которые называют
    левым и правым поддеревом корня. В дальнейшем мы будем в основном зани%
    маться двоичными деревьями, поэтому под деревом всегда будем подразумевать
    упорядоченное двоичное дерево. Деревья степени больше 2 называются сильно вет
    вящимися деревьями, им посвящен раздел 4.7.1.
    Знакомые примеры двоичных деревьев – семейная родословная, где отец и мать индивида представлены узлами%потомками (!); таблица результатов теннисного турнира, где каждому поединку соответствует узел, в котором запи%
    сан победитель, а две предыдущие игры соперников являются потомками;
    арифметическое выражение с двухместными операциями, где каждому оператору соответствует узел, а операндам – поддеревья (см. рис. 4.20).
    Рис. 4.20. Представление в виде дерева для выражения
    (a + b/c) * (d – e*f)

    195
    Обратимся теперь к проблеме представления деревьев. Изображение таких рекурсивных конструкций в виде ветвящихся структур подсказывает, что здесь можно использовать наш аппарат указателей. Очевидно, нет смысла объявлять переменные с фиксированной древесной структурой; вместо этого фиксирован%
    ную структуру, то есть фиксированный тип, будут иметь узлы, для которых сте%
    пень дерева определяет число указательных компонент, ссылающихся на подде%
    ревья узла. Очевидно, ссылка на пустое дерево обозначается с помощью
    NIL
    Следовательно, дерево на рис. 4.20 состоит из компонент определенного ниже типа и может тогда быть построено, как показано на рис. 4.21.
    TYPE Node =
    POINTER TO NodeDesc;
    TYPE NodeDesc = RECORD op: CHAR; left, right: Node END
    Рис. 4.21. Дерево рис. 4.20, представленное как связная структура данных
    Прежде чем исследовать, какую пользу можно извлечь, применяя деревья,
    и как выполнять операции над ними, дадим пример программы, которая строит дерево. Предположим, что нужно построить дерево, значениями в узлах которого являются n
    чисел, считываемых из входного файла. Чтобы сделать задачу инте%
    ресней, будем строить дерево с n
    узлами, имеющее минимальную высоту. Чтобы получить минимальную высоту при заданном числе узлов, нужно размещать мак%
    симальное возможное число узлов на всех уровнях, кроме самого нижнего. Оче%
    видно, этого можно достичь, распределяя новые узлы поровну слева и справа от каждого узла. Это означает, что мы строим дерево для заданного n
    так, как показа%
    но на рис. 4.22 для n = 1, ... , 7
    Правило равномерного распределения при известном числе узлов n лучше всего сформулировать рекурсивно:
    1. Использовать один узел в качестве корня.
    2. Построить таким образом левое поддерево с числом узлов nl = n DIV 2 3. Построить таким образом правое поддерево с числом узлов nr = n – nl – 1
    Деревья

    Динамические структуры данных
    196
    Это правило реализуется рекурсивной процедурой, которая читает входной файл и строит идеально сбалансированное дерево. Вот определение: дерево явля%
    ется идеально сбалансированным, если для каждого узла число узлов в левом и правом поддеревьях отличается не больше чем на 1.
    TYPE Node = POINTER TO RECORD
    (* ADruS441_BalancedTree *)
    key: INTEGER; left, right: Node
    END;
    VAR R: Texts.Reader; W: Texts.Writer; root: Node;
    PROCEDURE tree (n: INTEGER): Node;
    (*            n   *)
    VAR new: Node;
    x, nl, nr: INTEGER;
    BEGIN
    IF n = 0 THEN new := NIL
    ELSE nl := n DIV 2; nr := n–nl–1;
    NEW(new); Texts.ReadInt(R, new.key);
    new.key := x; new.left := tree(nl); new.right := tree(nr)
    END;
    RETURN new
    END tree;
    PROCEDURE PrintTree (t: Node; h: INTEGER);
    (*       t  h  *)
    VAR i: INTEGER;
    BEGIN
    IF t # NIL THEN
    Рис. 4.22. Идеально сбалансированные деревья

    197
    PrintTree(t.left, h+1);
    FOR i := 1 TO h DO Texts.Write(W, TAB) END;
    Texts.WriteInt(W, t.key, 6); Texts.WriteLn(W);
    PrintTree(t.right, h+1)
    END
    END PrintTree;
    Например, предположим, что имеются входные данные для дерева с 21 узлами:
    8 9 11 15 19 20 21 7 3 2 1 5 6 4 13 14 10 12 17 16 18
    Вызов root := tree(21)
    читает входные данные и строит идеально сбалансиро%
    ванное дерево, показанное на рис. 4.23. Отметим простоту и прозрачность этой программы, построенной с использованием рекурсивных процедур. Очевидно,
    что рекурсивные алгоритмы особенно удобны там, где нужно обрабатывать ин%
    формацию, структура которой сама определена рекурсивно. Этот факт снова про%
    является в процедуре, которая печатает получившееся дерево: если дерево пусто,
    то ничего не печатается, для поддерева на уровне
    L
    сначала печатается его левое поддерево, затем узел с отступом в
    L
    символов табуляции и, наконец, его правое поддерево.
    Рис. 4.23. Дерево, порожденное программой tree
    4.4.2. Основные операции
    с двоичными деревьями
    Есть много операций, которые может понадобиться выполнить с древесной струк%
    турой; например, часто нужно выполнить заданную процедуру
    P
    в каждом узле дерева. Тогда
    P
    следует считать параметром более общей задачи обхода всех уз%
    лов, или, как обычно говорят, обхода дерева. Если мы рассмотрим такой обход как
    Деревья

    Динамические структуры данных
    198
    единый последовательный процесс, то получится, что от%
    дельные узлы посещаются в некотором конкретном поряд%
    ке и как бы расставляются в линию. Описание многих алгоритмов сильно упрощается, если обсуждать последо%
    вательность обработки элементов в терминах какого%либо отношения порядка. Из структуры деревьев естественно определяются три основных отношения порядка. Их, как и саму древесную структуру, удобно выражать на языке ре%
    курсии. Имея в виду двоичное дерево на рис. 4.24, где
    R
    обозначает корень, а
    A
    и
    B
    – левое и правое поддеревья, три упомянутых отношения порядка таковы:
    1. Прямой порядок (preorder):
    R, A, B
    (корень до поддеревьев)
    2. Центрированный порядок (inorder):
    A, R, B
    3. Обратный порядок (postorder):
    A, B, R
    (корень после поддеревьев)
    Обходя дерево на рис. 4.20 и записывая соответствующие литеры по мере посещения узлов, получаем следующие упорядоченные последовательности:
    1. Прямой порядок:
    * + a / b c – d * e f
    2. Центрированный порядок:
    a + b / c * d – e * f
    3. Обратный порядок:
    a b c / + d e f * – *
    Здесь можно узнать три формы записи выражений: прямой обход дерева выра%
    жения дает префиксную нотацию, обратныйпостфиксную, а центрированный
    обычную инфиксную, правда, без скобок, которые нужны для уточнения приори%
    тетов операций.
    Сформулируем теперь эти три метода обхода в виде трех конкретных про%
    грамм с явным параметром t
    , обозначающим дерево, которое нужно обработать,
    и с неявным параметром
    P
    , обозначающим операцию, применяемую к каждому узлу. Подразумевается следующее определение:
    TYPE Node = POINTER TO RECORD ... left, right: Node END
    Теперь три метода легко формулируются в виде рекурсивных процедур; этим снова подчеркивается тот факт, что операции с рекурсивно определенными структурами данных удобней всего определять в виде рекурсивных алгоритмов.
    PROCEDURE preorder (t: Node);
    BEGIN
    IF t # NIL THEN
    P(t); preorder(t.left); preorder(t.right)
    END
    END preorder
    PROCEDURE inorder (t: Node);
    BEGIN
    IF t # NIL THEN
    inorder(t.left); P(t); inorder(t.right)
    END
    END inorder
    Рис. 4.24. Двоичное дерево

    199
    PROCEDURE postorder (t: Node);
    BEGIN
    IF t # NIL THEN
    postorder(t.left); postorder(t.right); P(t)
    END
    END postorder
    Заметим, что указатель t
    передается по значению. Этим выражается тот факт,
    что передается ссылка на рассматриваемое поддерево, а не переменная, чьим зна%
    чением является эта ссылка и чье значение могло бы быть изменено, если бы t
    передавался как параметр%переменная.
    Пример обхода дерева – печать с правильным числом отступов, соответству%
    ющим уровню каждого узла.
    Двоичные деревья часто используют для представления набора данных, к эле%
    ментам которого нужно обращаться по уникальному ключу. Если дерево организовано таким образом, что для каждого узла t
    i все ключи в его левом подде%
    реве меньше, чем ключ узла t
    i
    , а ключи в правом поддереве больше, чем ключ t
    i
    , то такое дерево называют деревом поиска. В дереве поиска можно найти любой ключ,
    стартуя с корня и спускаясь в левое или правое поддерево в зависимости только от значения ключа в текущем узле. Мы видели, что n
    элементов можно организовать в двоичное дерево высоты всего лишь log(n)
    . Поэтому поиск среди n
    элементов можно выполнить всего лишь за log(n)
    сравнений, если дерево идеально сбаланси%
    ровано. Очевидно, дерево гораздо лучше подходит для организации такого набора данных, чем линейный список из предыдущего раздела. Так как поиск здесь про%
    ходит по единственному пути от корня к искомому узлу, его легко запрограмми%
    ровать с помощью цикла:
    PROCEDURE locate (x: INTEGER; t: Node): Node;
    BEGIN
    WHILE (t # NIL) & (t.key # x) DO
    IF t.key < x THEN t := t.right ELSE t := t.left END
    END;
    RETURN t
    END locate
    Функция locate(x, t)
    возвращает значение
    NIL
    , если в дереве, начинающемся с корня t
    , не найдено узла со значением ключа x
    . Как и в случае поиска в списке,
    сложность условия завершения подсказывает лучшее решение, а именно исполь%
    зование барьера. Этот прием применим и при работе с деревом. Аппарат указателей позволяет использовать единственный, общий для всех ветвей узел%барьер. Полу%
    чится дерево, у которого все листья привязаны к общему «якорю» (рис. 4.25). Барь%
    ер можно считать общим представителем всех внешних узлов, которыми было расширено исходное дерево (см. рис. 4.19):
    PROCEDURE locate (x: INTEGER; t: Node): Node;
    BEGIN
    s.key := x; (*  *)
    WHILE t.key # x DO
    Деревья

    Динамические структуры данных
    200
    IF t.key < x THEN t := t.right ELSE t := t.left END
    END;
    RETURN t
    END locate
    Заметим, что в этом случае locate(x, t)
    возвращает значение s
    вместо
    NIL
    , то есть указатель на барьер, если в дереве с корнем t
    не найдено ключа со значением x
    4.4.3. Поиск и вставка в деревьях
    Вряд ли всю мощь метода динамического размещения с использованием указа%
    телей можно вполне оценить по примерам, в которых набор данных сначала стро%
    ится, а в дальнейшем не меняется. Интересней примеры, в которых дерево меня%
    ется, то есть растет и/или сокращается, во время выполнения программы. Это как раз тот случай, когда оказываются непригодными другие представления данных,
    например массив, и когда лучшее решение получается при использовании дерева с элементами, связанными посредством указателей.
    Мы сначала рассмотрим случай только растущего, но никогда не сокращаю%
    щегося дерева. Типичный пример – задача составления частотного словаря, кото%
    рую мы уже рассматривали в связи со связными списками. Сейчас мы рассмотрим ее снова. В этой задаче дана последовательность слов, и нужно определить число вхождений каждого слова. Это означает, что каждое слово ищется в дереве (кото%
    рое вначале пусто). Если слово найдено, то счетчик вхождений увеличивается;
    в противном случае оно включается в дерево со счетчиком, выставленным в 1. Мы будем называть эту задачу поиск по дереву со вставкой. Предполагается, что опре%
    делены следующие типы данных:
    Рис. 4.25. Дерево поиска с барьером (узел s)

    201
    TYPE Node = POINTER TO RECORD
    key, count: INTEGER;
    left, right: Node
    END
    Как и раньше, не составляет труда найти путь поиска. Однако если он заверша%
    ется тупиком (то есть приводит к пустому поддереву, обозначенному указателем со значением
    NIL
    ), то данное слово нужно вставить в дерево в той точке, где было пустое поддерево. Например, рассмотрим двоичное дерево на рис. 4.26 и вставку имени
    Paul
    . Результат показан пунктирными линиями на том же рисунке.
    Рис. 4.26. Вставка в упорядоченное двоичное дерево
    Процесс поиска формулируется в виде рекурсивной процедуры. Заметим, что ее параметр p
    передается по ссылке, а не по значению. Это важно, так как в случае вставки переменной, содержавшей значение
    NIL
    , нужно присвоить новое указа%
    тельное значение. Для входной последовательности из 21 числа, которую мы уже использовали для построения дерева на рис. 4.23, процедура поиска и вставок дает двоичное дерево, показанное на рис. 4.27; для каждого ключа k выполняется вызов search(k, root)
    , где root
    – переменная типа
    Node
    PROCEDURE PrintTree (t: Node; h: INTEGER);
    (* ADruS443_Tree *)
    (*       t  h  *)
    VAR i: INTEGER;
    BEGIN
    IF t # NIL THEN
    PrintTree(t.left, h+1);
    FOR i := 1 TO h DO Texts.Write(W, TAB) END;
    Texts.WriteInt(W, t.key, 6); Texts.WriteLn(W);
    PrintTree(t.right, h+1)
    END
    END PrintTree;
    Деревья

    Динамические структуры данных
    202
    PROCEDURE search (x: INTEGER; VAR p: Node);
    BEGIN
    IF p = NIL THEN (*x     ;  *)
    NEW(p); p.key := x; p.count := 1; p.left := NIL; p.right := NIL
    ELSIF x < p.key THEN search(x, p.left)
    ELSIF x > p.key THEN search(x, p.right)
    ELSE INC(p.count)
    END
    END search
    И снова использование барьера немного упрощает программу. Ясно, что в на%
    чале программы переменная root должна быть инициализирована указателем на барьер s вместо значения
    NIL
    , а перед каждым поиском искомое значение x
    долж%
    но быть присвоено полю ключа барьера:
    PROCEDURE search (x: INTEGER; VAR p: Node);
    BEGIN
    IF x < p.key THEN search(x, p.left)
    ELSIF x > p.key THEN search(x, p.right)
    ELSIF p # s THEN INC(p.count)
    ELSE (* *)
    NEW(p); p.key := x; p.left := s; p.right := s; p.count := 1
    END
    END search
    Хотя целью этого алгоритма был поиск, его можно использовать и для сор%
    тировки. На самом деле он очень похож на сортировку вставками, а благодаря использованию дерева вместо массива исчезает необходимость перемещать ком%
    поненты, стоящие выше точки вставки. Древесную сортировку можно запрограм%
    Рис. 4.27. Дерево поиска, порожденное процедурой поиска и вставки

    203
    мировать так, что она будет почти так же эффективна, как и лучшие известные методы сортировки массивов. Однако нужны некоторые предосторожности. Если обнаружено совпадение, новый элемент тоже нужно вставить. Если случай x = p.key обрабатывать так же, как и случай x > p.key
    , то алгоритм сортировки будет устой%
    чивым, то есть элементы с равными ключами будут прочитаны в том же относи%
    тельном порядке при просмотре дерева в нормальном порядке, в каком они встав%
    лялись.
    Вообще говоря, есть и более эффективные методы сортировки, но для прило%
    жений, где нужны как поиск, так и сортировка, можно уверенно рекомендовать алгоритм поиска и вставки. Он действительно очень часто применяется в компи%
    ляторах и банках данных для организации объектов, которые нужно хранить и искать. Хорошим примером здесь является построение указателя перекрестных ссылок для заданного текста; мы уже обращались к этому примеру для иллю%
    страции построения списков.
    Итак, наша задача – написать программу, которая читает текст и печатает его,
    последовательно нумеруя строки, и одновременно собирает все слова этого текста вместе с номерами строк, в которых встретилось каждое слово. По завершении просмотра должна быть построена таблица, содержащая все собранные слова в алфавитном порядке вместе с соответствующими списками номеров строк.
    Очевидно, дерево поиска (иначе лексикографическое дерево) будет прекрасным кандидатом для представления информации о словах, встречающихся в тексте.
    Теперь каждый узел будет не только содержать слово в качестве значения ключа,
    но и являться началом списка номеров строк. Каждую запись о вхождении слова в текст будем называть «элементом» (item; нехватка синонимов для перевода ком%
    пенсируется кавычками – прим. перев.).
    Таким образом, в данном примере фигури%
    руют как деревья, так и линейные списки. Программа состоит из двух главных час%
    тей, а именно из фазы просмотра и фазы печати таблицы. Последняя – простая адаптация процедуры обхода дерева; здесь при посещении каждого узла выполня%
    ется печать значения ключа (слова) вместе с соответствующим списком номеров строк. Ниже даются дополнительные пояснения о программе, приводимой далее.
    Таблица 4.3 показывает результаты обработки текста процедуры search
    1. Словом считается любая последовательность букв и цифр, начинающаяся с буквы.
    2. Так как длина слов может сильно меняться, их литеры хранятся в особом массиве%буфере, а узлы дерева содержат индекс первой буквы ключа.
    3. Желательно, чтобы в указателе перекрестных ссылок номера строк печата%
    лись в возрастающем порядке.
    Поэтому список «элементов» должен сохранять порядок соответствующих вхождений слова в тексте. Из этого требования вытекает, что нужны два указа%
    теля в каждом узле, причем один ссылается на первый, а другой – на последний
    «элемент» списка. Мы предполагаем наличие глобального объекта печати
    W
    ,
    а также переменной, представляющей собой текущий номер строки в тексте.
    Деревья

    Динамические структуры данных
    204
    CONST WordLen = 32;
    (* ADruS443_CrossRef *)
    TYPE Word = ARRAY WordLen OF CHAR;
    Item = POINTER TO RECORD (*"  "*)
    lno: INTEGER; next: Item
    END;
    Node = POINTER TO RECORD
    key: Word;
    first, last: Item; (**)
    left, right: Node (*  *)
    END;
    VAR line: INTEGER;
    PROCEDURE search (VAR w: Node; VAR a: Word);
    VAR q: Item;
    BEGIN
    IF w = NIL THEN (*     ;     *)
    NEW(w); NEW(q); q.lno := line;
    COPY(a, w.key); w.first := q; w.last := q; w.left := NIL; w.right := NIL
    ELSIF w.key < a THEN search(w.right, a)
    ELSIF w.key > a THEN search(w.left, a)
    ELSE (*     *)
    NEW(q); q.lno := line; w.last.next := q; w.last := q
    END
    END search;
    PROCEDURE Tabulate (w: Node);
    VAR m: INTEGER; item: Item;
    BEGIN
    IF w # NIL THEN
    Tabulate(w.left);
    Texts.WriteString(W, w.key); Texts.Write(W, TAB); item := w.first; m := 0;
    REPEAT
    IF m = 10 THEN
    Texts.WriteLn(W); Texts.Write(W, TAB); m := 0
    END;
    INC(m); Texts.WriteInt(W, item.lno, 6); item := item.next
    UNTIL item = NIL;
    Texts.WriteLn(W); Tabulate(w.right)
    END
    END Tabulate;
    PROCEDURE CrossRef (VAR R: Texts.Reader);
    VAR root: Node;
    i: INTEGER; ch: CHAR; w: Word;
    (*     #   ˆ    W*)
    BEGIN
    root := NIL; line := 0;
    Texts.WriteInt(W, 0, 6); Texts.Write(W, TAB);
    Texts.Read(R, ch);
    WHILE

    R.eot DO
    IF ch = 0DX THEN (*  *)
    Texts.WriteLn(W);

    205
    INC(line);
    Texts.WriteInt(W, line, 6); Texts.Write(W, 9X);
    Texts.Read(R, ch)
    ELSIF ("A" <= ch) & (ch <= "Z") OR ("a" <= ch) & (ch <= "z") THEN
    i := 0;
    REPEAT
    IF i < WordLen–1 THEN w[i] := ch; INC(i) END;
    Texts.Write(W, ch); Texts.Read(R, ch)
    UNTIL (i = WordLen–1) OR (("A" <= ch) & (ch <= "Z")) &
    (("a" <= ch) & (ch <= "z")) & (("0" <= ch) & (ch <= "9"));
    w[i] := 0X; (*      *)
    search(root, w)
    ELSE
    Texts.Write(W, ch);
    Texts.Read(R, ch)
    END;
    END;
    Texts.WriteLn(W); Texts.WriteLn(W); Tabulate(root)
    END CrossRef;
    Таблица 4.3.
    Таблица 4.3.
    Таблица 4.3.
    Таблица 4.3.
    Таблица 4.3. Пример выдачи генератора перекрестных ссылок
    0 PROCEDURE search (x: INTEGER; VAR p: Node);
    1 BEGIN
    2 IF x < p.key THEN search(x, p.left)
    3 ELSIF x > p^key THEN search(x, p.right)
    4 ELSIF p # s THEN INC(p.count)
    5 ELSE (*insert*) NEW(p);
    6 p.key := x; p.left := s; p.right := s; p.count := 1 7 END
    8 END
    BEGIN
    1
    ELSE
    5
    ELSIF
    3 4
    END
    7 8
    IF
    2
    INC
    4
    INTEGER
    0
    NEW
    5
    Node
    0
    PROCEDURE
    0
    THEN
    2 3 4
    VAR
    0
    count
    4 6
    insert
    5
    key
    2 3 6
    left
    2 6
    p
    0 2 2 3 3 4 4 5 6 6 6 6
    right
    3 6
    s
    4 6 6
    search
    0 2 3
    x
    0 2 2 3 3 6
    Деревья

    Динамические структуры данных
    206
    1   ...   12   13   14   15   16   17   18   19   ...   22


    написать администратору сайта