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

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

Поиск и сортировка

Поиск и сортировка данных, располагающихся в оперативной памяти, – типичная и очень важная часть многих приложений, качество реализации которой во многом определяет технические характеристики программной системы в целом.

Стандарт POSIX-2001 предлагает несколько способов поиска в таблицах разных форматов.

Бинарный поиск (см. листинг 9.13) является самым быстрым среди методов, основанных на сравнении ключей. Он применим к предварительно отсортированным одномерным массивам.

#include <stdlib.h>
void *bsearch (const void *key, 
    const void *base, 
    size_t nel, size_t width, 
    int (*compar) (const void *, 
        const void *));
Листинг 9.13. Описание функции бинарного поиска bsearch().

Функция bsearch() предназначена для выполнения бинарного поиска в соответствии с алгоритмом, описанным Д. Кнутом (см. [ 4 ] в дополнительной литературе, пункт 6.2.1, алгоритм B).

Функция bsearch() возвращает указатель внутрь массива на искомые данные или NULL в случае неудачи поиска. Предварительно массив должен быть отсортирован в возрастающем порядке согласно предоставленной функции сравнения   compar().

Аргумент   key указывает на объект данных, разыскиваемый в массиве ( ключ   поиска ); base указывает на начало (первый элемент) массива; nel задает количество элементов в массиве; width специфицирует размер элемента в массиве.

Аргумент compar() – это функция сравнения, аргументами которой служат два указателя на сравниваемые объекты – ключ и элемент массива. В соответствии с тем, какое целое число она возвращает: меньшее нуля, равное нулю или большее нуля, ключ считается меньшим, равным или большим по отношению к элементу массива.

Для сортировки массивов целесообразно пользоваться функцией qsort() (см. листинг 9.14), реализующей метод быстрой сортировки (называемый также методом обменной сортировки с разделением, см. [ 4 ] в дополнительной литературе, пункт 5.2.2, алгоритм Q). Ее аргументы имеют тот же смысл, что и одноименные аргументы функции bsearch().

#include <stdlib.h>
void qsort (void *base, size_t nel, 
    size_t width, 
    int (*compar) (const void *, 
        const void *));
Листинг 9.14. Описание функции быстрой сортировки qsort().

Рассмотрим пример последовательного применения функций qsort() и bsearch() (см. листинг 9.15). Здесь в роли элементов массива выступают указатели на цепочки символов, которые размещены в области StringSpace ; тот же тип имеет и ключ   поиска. Критерием сравнения служит алфавитная упорядоченность указуемых цепочек.

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

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

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

/* Число элементов в таблице указателей на цепочки символов */
#define TAB_SIZE                 1000000

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

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

/* Массив указателей на цепочки символов */
static char *PtsTable [TAB_SIZE];
/* Число занятых элементов в массиве указателей */
static size_t nelst;

/* * * * * * * * * * * * * * * * * * * * * */
/* Формирование случайной цепочки символов */
/* * * * * * * * * * * * * * * * * * * * * */
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 tabl_fill (void) {
    char *pss;          /* Указатель на свободное место */
                        /* в области StringSpace        */
    int i;

    for (pss = StringSpace, i = 0; i < TAB_SIZE; 
            pss += STRING_SIZE, i++) {
        if (((pss + STRING_SIZE) – 
            (StringSpace + SPACE_SIZE)) > 0) {
            fprintf (stderr, "tabl_fill: исчерпано "
                        "пространство цепочек\n");
            nelst = i;
            return;
        }
        str_rnd (pss, STRING_SIZE);
        PtsTable [i] = pss;
    }
    nelst = TAB_SIZE;
}

/* * * * * * * * * * */
/* Функция сравнения */
/* * * * * * * * * * */
static int str_compar (const void *pkey, 
                                    const void *pelem) {
    return strcoll (*((char **) pkey), *((char **) pelem));
}

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

    /* Заполнение массивов */
    tabl_fill ();

    /* Сортировка массива указателей */
    qsort (PtsTable, nelst, sizeof (PtsTable [0]), 
                str_compar);

    /* Формирование ключа поиска                    */
    /* (будем искать первую из случайных цепочек)   */
    skey = StringSpace;
     if ((res = (char **) bsearch (&skey, PtsTable, 
            nelst, sizeof (PtsTable [0]), str_compar)) != NULL) {
        printf ("Указатель на первую цепочку %s\n" 
                "после сортировки стал %d-м элементом массива\n", 
                    skey, (res – PtsTable) / sizeof (PtsTable [0]));
    } else {
        printf ("Не удалось найти цепочку %s\n", skey);
    }

    /* Будем формировать и искать новые случайные цепочки */
    skey = sbuf;
    ntr = 0;
    do {
        str_rnd (skey, STRING_SIZE);
    ntr++;

    } while (bsearch (&skey, PtsTable, nelst, 
                sizeof (PtsTable [0]), str_compar) == NULL);
    printf ("Удалось найти %g-ю по счету случайную цепочку" 
                " %s\n", ntr, skey);

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

Отметим, что при сортировке будут перемещаться элементы массива PtsTable [] – указатели на цепочки символов, но, конечно, не сами цепочки.

Если на компьютере, которым в данный момент пользуется автор, измерить время выполнения приведенной программы посредством утилиты   time с опцией   -p, результаты будут выглядеть следующим образом (см. листинг 9.16).

Указатель на первую цепочку NWLRBBMQB
после сортировки стал 133253-м элементом массива
Удалось найти 168221-ю по счету случайную цепочку VBBDZTNMZ
real 15.67
user 15.57
sys 0.10
Листинг 9.16. Возможные результаты выполнения программы, применяющей функции быстрой сортировки и бинарного поиска.

Читателю предлагается сравнить эти результаты с экспериментально полученными собственными (и с гордостью убедиться, что его компьютер гораздо мощнее), а также оценить зависимость длительности быстрой сортировки и бинарного поиска от размера массива (и подтвердить теоретические оценки из [ 4 ] ).

Стандартом POSIX-2001, помимо бинарного, предусматривается еще несколько способов поискапоследовательный, с помощью хэш-таблиц и бинарных деревьев. Соответствующие описания сосредоточены в заголовочном файле <search.h>.

В идейном плане самым простым является последовательный поиск. Он может производиться с вставкой (функция lsearch() ) или без таковой ( lfind() ) (см. листинг 9.17).

#include <search.h>

void *lsearch (const void *key, 
    void *base, size_t *nelp, 
    size_t width, 
    int (*compar) (const void *,
        const void *));

void *lfind (const void *key, 
    const void *base, size_t *nelp, 
    size_t width, int (*compar) (const void *, 
        const void *));
Листинг 9.17. Описание функций последовательного поиска.

Функции, реализующие последовательный поиск, по способу вызова напоминают bsearch(), только аргумент   nelp является указателем на число элементов в массиве, которое функция lsearch() может увеличить на единицу (если искомого элемента в массиве не было, его добавляют в конец). Разумеется, для последовательного поиска не требуется, чтобы массив был предварительно отсортирован. Упрощены и требования к функции сравнения   compar(): в случае неравенства ее результат должен быть отличен от нуля.

В качестве иллюстрации применения функций последовательного поиска рассмотрим программу, генерирующую случайные цепочки символов до первого повторения (см. листинг 9.18).

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа генерирует случайные цепочки символов до первого  */
/* повторения (или до исчерпания отведенного пространства).    */
/* Для выявления повторения применяется                        */
/* последовательный поиск с вставкой                           */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

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

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

/* Число элементов в таблице указателей на цепочки символов */
#define TAB_SIZE             20000

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

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

/* Массив указателей на цепочки символов */
static char *PtsTable [TAB_SIZE];

/* * * * * * * * * * */
/* Функция сравнения */
/* * * * * * * * * * */
static int str_compar (const void *pkey, 
                        const void *pelem) {
    return strcoll (*((char **) pkey), *((char **) pelem));
}

/* * * * * * * * * * * * * * * * * * * * * */
/* Формирование случайной цепочки символов */
/* * * * * * * * * * * * * * * * * * * * * */
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;
    }
}

/* * * * * * * * * * * * * * * * * * * * * * * * */
/* Поиск первого повтора в последовательности    */
/* случайных цепочек символов                    */
/* * * * * * * * * * * * * * * * * * * * * * * * */
int main (int argc, char *argv []) {
    char *pss;          /* Указатель на свободное место */
                        /* в области StringSpace        */
    char **res;         /* Результат поиска с вставкой  */
    size_t nelst;       /* Число занятых элементов      */
                        /* в массиве указателей         */
    size_t onelst;      /* Число элементов в массиве    */
                        /* до поиска с вставкой         */
for (pss = StringSpace, nelst = 0; nelst < TAB_SIZE; 
            pss += STRING_SIZE) {
        if (((pss + STRING_SIZE) – (StringSpace + 
                SPACE_SIZE)) > 0) {
            fprintf (stderr, "%s: Исчерпано пространство "
                        "цепочек\n", argv [0]);
            return (1);
        }

        str_rnd (pss, STRING_SIZE);

        onelst = nelst;
        res = (char **) lsearch (&pss, PtsTable, &nelst, 
                    sizeof (PtsTable [0]), str_compar);
        if (onelst == nelst) {
            /* Искомая цепочка уже была порождена ранее */
            printf ("Для случайных цепочек длины %d\n" 
                        "первое совпадение получено на цепочке "
                        "%s\n", STRING_SIZE, pss);
            printf ("Первый раз цепочка была порождена "
                        "под номером %d,\n" "второй – под номером "
                        "%d\n", (res – PtsTable) / sizeof 
                        (PtsTable [0]) + 1, nelst + 1);
            return 0;
        }
    } /* for */

    printf ("Из %d случайных цепочек длины %d все "
                "оказались уникальными\n", TAB_SIZE, STRING_SIZE);

    return 0;
}
Листинг 9.18. Пример применения последовательного поиска с вставкой.

Указатели на порождаемые случайные цепочки помещаются в массив PtsTable [] функцией lsearch(). В этой связи обратим внимание на нескольку вычурную организацию цикла for в функции main(). По сути здесь две переменные циклаpss и nelst. Первая продвигается стандартным образом, в заголовке цикла, но проверяется на выход за допустимые границы в его теле; вторая, напротив, стандартно проверяется, но нестандартно продвигается (в результате вызова lsearch() ).

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

Для случайных цепочек длины 7
первое совпадение получено на цепочке GLPCSX
Первый раз цепочка была порождена под номером 2548,
второй - под номером 12530
real 34.80
user 13.70
sys 0.03
Листинг 9.19. Возможные результаты выполнения программы, применяющей функцию последовательного поиска с вставкой.

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

Павел Храмцов
Павел Храмцов
Россия
Денис Комаров
Денис Комаров
Россия, Москва