Опубликован: 06.12.2004 | Доступ: свободный | Студентов: 1180 / 143 | Оценка: 4.76 / 4.29 | Длительность: 20:58:00
ISBN: 978-5-9556-0021-5
Лекция 9:

Технологические интерфейсы

Аргумент   action – это функция, которую twalk() вызывает при попадании в узел во время обхода. Она, в свою очередь, имеет три аргумента. Первым из них служит адрес текущего узла. Структура, на которую указывает этот аргумент, стандартом не специфицируется; оговаривается только, что указатель на узел можно привести к типу "указатель на указатель на хранимые данные" (то есть на данные, ассоциированные с узлом, и содержащие, в частности, ключ   поиска ). Второй аргумент вызываемой функции – это значение определенного выше типа VISIT. Напомним еще раз, что оно показывает, который раз (первый, второй или третий) осуществляется доступ к неконцевому узлу во время обхода дерева в глубину и слева направо или свидетельствует, что узел является концевым ( листом ). Третий аргумент – это уровень узла в дереве (в предположении, что корень имеет уровень 0).

Читатель наверняка уже догадался, что далее последует пример программы, строящей бинарное дерево   поиска для случайных цепочек символов (см. листинг 9.27). Впрочем, приведенная программа делает и еще кое-что: подсчитывает число узлов и высоту дерева, распечатывает несколько первых по алфавиту цепочек из числа помещенных в дерево и, конечно, генерирует новые случайные цепочки, пока их поиск в дереве не окажется успешным. Для разнообразия узел с найденной цепочкой удаляется.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа осуществляет поиск с вставкой в бинарном    */
/* дереве, помещая в него  заданное число элементов с    */
/* указателями на случайные цепочки символов.            */
/* Затем подсчитывается число узлов и высота дерева.     */
/* Следующим действием является распечатка               */

/* нескольких первых цепочек.                            */
/* После этого выполняется поиск новых случайных цепочек,*/
/* пока он не окажется успешным.                         */
/* Найденный элемент удаляется из дерева                 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <search.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <setjmp.h>

/* Размер области для хранения цепочек символов */
#define SPACE_SIZE         10000000

/* Число элементов, помещаемых в дерево */
#define TREE_NEL             1000000

/* Длина одной цепочки символов     */
/* (включая завершающий нулевой байт) */
#define STRING_SIZE         10

/* Область для хранения цепочек символов */
static char StringSpace [SPACE_SIZE];

/* Число узлов в бинарном дереве поиска */
static size_t node_count;

/* Максимальный уровень узла дерева */
static int max_level;
/* Буфер для функций setjmp и longjmp */
static jmp_buf buf_env;

/* * * * * * * * * * * * * * * * * * * * * */
/* Формирование случайной цепочки символов */
/* * * * * * * * * * * * * * * * * * * * * */
static void str_rnd (char *buf, size_t str_siz) {
    for ( ; str_siz > 1; str_siz--) {
        *buf++ = 'A' + rand () % 26;
    }
    if (str_siz > 0) {
        *buf = 0;
    }
}

/* * * * * * * * * * * * * * * * * * * * * * * * */
/* Функция, которая вызывается при обходе дерева */
/* с целью подсчета числа узлов и высоты         */
/* * * * * * * * * * * * * * * * * * * * * * * * */
static void tw_nnh (const void *pnode, VISIT nv, int level) {
    if (nv == preorder) {
        node_count++;
    } else if (nv == leaf) {
        node_count++;
        if (level > max_level) {
            max_level = level;
        }
    }
}

/* * * * * * * * * * * * * * * * * * * * * * * * */
/* Функция, которая вызывается при обходе дерева */
/* с целью распечатки нескольких первых          */
/* по алфавиту цепочек символов                  */
/* * * * * * * * * * * * * * * * * * * * * * * * */
static void tw_pfs (const void *pnode, VISIT nv, int level) {
    if (node_count <= 0) {
        /* Нужное число цепочек выведено,*/
        /* прерываем обход дерева        */
        longjmp (buf_env, 1);
    }
    if ((nv == postorder) || (nv == leaf)) {
        printf ("%s\n", *((char **) pnode));
        node_count--;
    }
}

/* * * * * * * * * * * * * * * * * * */
/* Создание бинарного дерева поиска, */
/* определение его характеристик,    */
/* поиск повтора в последовательности*/
/* случайных цепочек символов        */
/* * * * * * * * * * * * * * * * * * */
int main (int argc, char *argv []) {
    void *root;                 /* Указатель на корень дерева */
    char *key;                  /* Указатель на искомую       */
                                /* цепочку символов           */
    char sbuf [STRING_SIZE];    /* Буфер для формирования     */
                                /* случайных цепочек          */
    double ntr;                 /* Номер найденной случайной  */
                                /* цепочки */
    size_t i;

    /* Создадим бинарное дерево поиска */
    root = NULL;
    for (key = StringSpace, i = 0; i < TREE_NEL; key += 
            STRING_SIZE, i++) {
        if (((key + STRING_SIZE) – (StringSpace + 
                SPACE_SIZE)) > 0) {
            fprintf (stderr, "%s: Исчерпано пространство "
                        "цепочек\n", argv [0]);
            return (1);
        }

        str_rnd (key, STRING_SIZE);

        if (tsearch (key, &root, (int (*) (const void *, 
                const void *)) strcoll) == NULL) {
            fprintf (stderr, "%s: Поиск с вставкой в бинарное"
                " дерево " "завершился неудачей\n", argv [0]);
            return (2);
        }
    } /* for */

    /* Подсчитаем число узлов и высоту созданного дерева */
    node_count = 0;
    max_level = 0;
    twalk (root, tw_nnh);
    printf ("В дереве оказалось %d узлов\n", node_count);
    printf ("Его высота равна %d\n", max_level);

    /* Распечатаем несколько первых (по алфавиту) цепочек, */
    /* помещенных в созданное дерево                       */
    node_count = 10;
    printf ("Первые %d по алфавиту цепочек в дереве:\n", 
                node_count);
    if (setjmp (buf_env) == 0) {
        twalk (root, tw_pfs);
    }

    /* Будем формировать и искать новые случайные цепочки */
    ntr = 0;
    do {
        str_rnd (sbuf, STRING_SIZE);
        ntr++;
    } while (tdelete (sbuf, &root, (int (*) (const void *, 
                const void *)) strcoll) == NULL);
    printf ("Удалось найти и удалить из дерева %g-ю по счету " 
            "случайную цепочку %s\n", ntr, sbuf);

    return 0;
}
Листинг 9.27. Пример применения функций управления бинарными деревьями поиска.

Отметим гибкость бинарных деревьев   поиска как структуры данных. Не требуется заранее резервировать определенное пространстводеревья растут не более, чем это необходимо. При добавлении и удалении узлов деревья динамически перестраиваются без специального переупорядочения или массовых передвижек.

Обратим внимание на частичный обход дерева при распечатке нескольких первых по алфавиту цепочек, реализуемый с использованием механизма нелокальных переходов. Сама по себе функция twalk() ориентирована, разумеется, на полный обход (также представленный в программе).

Возможные результаты выполнения приведенной программы показаны на листинге 9.28.

В дереве оказалось 1000000 узлов
Его высота равна 25
Первые 10 по алфавиту цепочек в дереве:
AAAATNRAS
AAACHCCLB
AAACSJQBP
AAADLHFAZ
AAAFWLRXM
AAAFXGQEC
AAAGBMHHA
AAAGFAXFI
AAAHKLCWW
AAAHLOSVQ
Удалось найти и удалить из дерева 168221-ю по счету случайную цепочку VBBDZTNMZ
real 20.24
user 20.25
sys 0.15
Листинг 9.28. Возможные результаты выполнения программы, применяющей функции управления бинарными деревьями поиска.

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

Полноты ради упомянем еще о двух функциях, описанных в заголовочном файле <search.h>: insque() и remque() (см. листинг 9.29). Они предназначены для выполнения операций над очередями, реализованными как двусвязанные списки.

#include <search.h>

void insque (void *element, void *pred);

void remque (void *element);
Листинг 9.29. Описание функций, выполняющих операции над очередями.

Функция insque() осуществляет вставку элемента, на который указывает аргумент   element, после элемента pred. В качестве элемента должна выступать структура, первые два поля которой являются указателями на структуры того же типа – соответственно, следующий и предыдущий элементы очереди. Наличие и назначение других полей определяются нуждами приложения. Имя структурного типа и двух первых полей не стандартизуются.

Функция remque() удаляет заданный элемент из очереди.

Очередь может быть линейной или циклической. В первом случае она ограничена пустыми указателями, во втором крайние указатели должны быть зациклены. Вставка первого элемента в линейную очередь осуществляется вызовом insque (&element, NULL) ; при инициализации циклической очереди о ссылках должно позаботиться приложение (см. листинг 9.30).

#include <search.h>

        . . .
struct qelem {
    struct qelem *q_forw;
    struct qelem *q_back;
    char *data;
        . . .
};

struct qelem element1;
struct qelem element2;

. . .
element1.q_forw = &element1;
element1.q_back = &element1;

insque (&element2, &element1);

         . . .
Листинг 9.30. Пример инициализации циклической очереди и вставки в нее второго элемента.

Трудно сказать, есть ли смысл в стандартизации функций, исходный текст которых занимает пару строк...

Из тех же соображений полноты вернемся к теме сортировки и упомянем служебную программу tsort:

tsort  [файл]

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

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

Например, если применить утилиту   tsort к файлу, содержащему строки, показанные на листинге 9.31, то можно получить результат, приведенный на листинге 9.32.

a b
c d
d e
f g
e f
h h
Листинг 9.31. Пример исходных данных для служебной программы tsort.
a
c
h
b
d
e
f
g
Листинг 9.32. Возможный результат применения служебной программы tsort.