Подсчет особых вхождений подстрок в строке

Проблема: мне дана строка s размера n, и у меня есть много запросов типа
[L,R]
, и для каждого запроса я должен вернуть количество этих подстрок из s, равных s[L..R] и начинающихся перед индексом L в s .

ограничения:
n ‹= 2*10^6
запросов ‹= 10^5

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

Я думаю, что подход O (q log n) хорош для этой проблемы. Пожалуйста, предложите любой ??

Заранее спасибо ..


person v78    schedule 12.10.2014    source источник


Ответы (2)


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

  • Читать все запросы. Запрос имеет левый и правый индексы, а также счетчик.
  • Создайте попытку для всех запросов. Указатель на запрос служит конечным маркером для каждого запроса.
  • Пересеките строку один раз. Для каждого символа пройдите по дереву и подсчитайте количество узлов, у которых есть указатель на запрос.

Этот метод кажется более быстрым. Он не может обрабатывать повторяющиеся запросы. Это также имеет смысл только тогда, когда все запросы известны заранее. (К сожалению, мой пример реализации показал, что мой код для другого ответа имеет серьезную ошибку, вероятно, в ранжированном бинарном поиске; он неправильно считает некоторые запросы.)

Изменить: я добавил свой пример реализации на C — извините, не на C++ — ниже.

Реализация расточительна, потому что она выделяет дочерние узлы для всего диапазона возможных символов, кроме '\0', из которых подавляющее большинство будет NULL. Лучшая реализация сопоставила бы символы запросов с более узким алфавитом.

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

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

#define die(...) exit((fprintf(stderr, "Fatal: " __VA_ARGS__), \
    putc(10, stderr), 1))



typedef struct Query Query;
typedef struct Trie Trie;
typedef struct Trienode Trienode;

struct Query {
    char *p;                /* starting point in buffer */
    size_t len;             /* length of query in chars */
    size_t count;           /* number of occurrences */
};

struct Trie {
    Trienode *head;
};

struct Trienode {
    Query *query;           /* query reference */
    Trienode *next[255];    /* child nodes */
};

/*
 *      Read whole file to buffer and return size in n.
 */
char *slurp(const char *fn, size_t *n)
{
    FILE *f = fopen(fn, "r");
    size_t size;
    char *buf;

    if (f == NULL) die("Couldn't open %s", fn);

    fseek(f, 0, SEEK_END);
    size = ftell(f);
    fseek(f, 0, SEEK_SET);

    buf = malloc(size + 1);

    if (buf == NULL) die("Allocation failed");

    fread(buf, 1, size, f);
    buf[size] = '\0';
    fclose(f);

    if (n) *n = size;
    return buf;
}

/*
 *      Insert query string and reference into trie.
 */
void trie_insert(Trie *trie, Query *q)
{
    char *str = q->p;
    Trienode **n = &trie->head;
    size_t i;

    for (i = 0; i < q->len; i++) {    
        if (*n == NULL) {
            *n = malloc(sizeof(**n));
            if (*n == NULL) die("Coudn't allocate node");
            memset(*n, 0, sizeof(**n));
        }

        n = &(*n)->next[(unsigned char) str[i] - 1];
    }   

    if (*n == NULL) {
        *n = malloc(sizeof(**n));
        if (*n == NULL) die("Coudn't allocate node");
        memset(*n, 0, sizeof(**n));
    }

    (*n)->query = q;
}

static void trie_delete_node(Trienode *n)
{
    size_t i;

    for (i = 0; i < 255; i++) {
        if (n->next[i]) trie_delete_node(n->next[i]);
    }

    free(n);
}

/*
 *      Destroy trie and its nodes.
 */
void trie_delete(Trie *trie)
{
    if (trie->head) trie_delete_node(trie->head);
}

/*
 *      Find occurrences of all queries. The count member of all
 *      queries must be 0 before calling this routine.
 */
void search(char *buf, Trie *trie)
{
    while (*buf) {
        Trienode *n = trie->head;
        char *p = buf;

        while (n && *p) {
            if (n->query) {
                Query *q = n->query;

                if (buf < q->p) q->count++;
            }
            n = n->next[(unsigned char) *p - 1];
            p++;
        }

        buf++;
    }
}

/*
 *      Example client code
 */
int main(int argc, char **argv)
{
    Query *query = NULL;
    size_t nquery = 0;
    size_t squery = 0;

    char *buf;
    size_t nbuf;

    Trie trie = {NULL};
    FILE *f;
    size_t i;

    if (argc != 3) die("Usage: %s string queries", *argv);

    // Read string buffer from file
    buf = slurp(argv[1], &nbuf);

    // Read query array
    f = fopen(argv[2], "r");
    if (f == NULL) die("Can't open %s.", argv[1]);
    for (;;) {
        char str[80];
        size_t p, len;

        if (fgets(str, sizeof(str), f) == NULL) break;
        if (sscanf(str, "%zu %zu", &p, &len) < 2) continue;

        if (nquery >= squery) {
            squery *= 2;
            if (squery == 0) squery = 0x400;
            query = realloc(query, squery * sizeof(*query));
            if (query == NULL) die("Reallocation failed.");
        }

        query[nquery].p = buf + p;
        query[nquery].len = len;
        query[nquery].count = 0;
        nquery++;
    }
    fclose(f);

    // Build tree from queries
    for (i = 0; i < nquery; i++) {
        Query *q = query + i;
        trie_insert(&trie, q);
    }

    // Assign the query counts
    search(buf, &trie);

    // Print results
    for (i = 0; i < nquery; i++) {
        Query *q = query + i;
        printf("%8zu %.*s\n", q->count, (int) q->len, q->p);
    }

    // Clean up    
    trie_delete(&trie);
    free(buf);
    free(query);

    return 0;
}
person M Oehm    schedule 12.10.2014
comment
ага, все запросы известны заранее - person v78; 12.10.2014
comment
Тогда этот метод должен работать для вас. Я добавил пример кода на C. (Извините, я не очень хорошо разбираюсь в C++.) - person M Oehm; 12.10.2014

Возможное решение может быть похоже на предложенное мной решение для быстрого поиска строки с большим количеством запросов к одной и той же строке. Это решение имеет пример реализации на C.

  • Создайте массив указателей на каждый символ вашей строки. Сортируй.
  • Найдите первое вхождение запроса в отсортированном массиве суффиксов с помощью двоичного поиска.
  • Найдите последнее вхождение запроса. Вы можете сделать это, увеличив последний символ, чтобы вы искали «abd» вместо «abc», чтобы найти первое несоответствие.
  • Подсчитайте все совпадения между двумя совпадениями, начавшимися до L.

Однако это решение не равно O(q log n), потому что сортировка уже O(n log n >), а поиск запросов q — O(q log n).

Я переписал пример, на который я ссылался, для вашей проблемы. Это C, а не C++, и он не будет компилироваться компилятором C++ из-за того, как используется malloc. Не должно быть слишком сложно переписать его на идиоматическом C++.

Решение требует много памяти для массива суффиксов. Он может обработать около 130 000 запросов к файлу размером 1,3 мегабайта чуть более чем за минуту.

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

#define die(...) exit((fprintf(stderr, "Fatal: " __VA_ARGS__), \
    putc(10, stderr), 1))



typedef struct Haystack Haystack;    

struct Haystack {
    size_t size;    /* Number of chars in string */
    char *buf;      /* Null-terminated char buffer */
    char **ptr;     /* Pointers into char buffer */
};

/*
 *      Count occurrence of c in zero-terminated string p.
 */
size_t strcount(const char *p, int c)
{
    size_t n = 0;

    for (;;) {
        p = strchr(p, c);
        if (p == NULL) return n;
        p++;
        n++;        
    }

    return 0;
}

/*
 *      String comparison via pointers to strings.
 */
int pstrcmp(const void *a, const void *b)
{
    const char *const *aa = a;
    const char *const *bb = b;

    return strcmp(*aa, *bb);
}

/*
 *      Create and prepare a hayst, i.e. a text file to search.
 */
Haystack *hayst_new(const char *fn)
{
    Haystack *hayst;
    FILE *f = fopen(fn, "r");
    char *p;
    char **pp;

    if (f == NULL) die("Couldn't open %s", fn);

    hayst = malloc(sizeof(*hayst));
    if (hayst == NULL) die("Allocation failed");

    fseek(f, 0, SEEK_END);
    hayst->size = ftell(f);
    fseek(f, 0, SEEK_SET);

    hayst->buf = malloc(hayst->size + 1);
    hayst->ptr = malloc(hayst->size * sizeof(*hayst->ptr));

    if (hayst->buf == NULL) die("Allocation failed");
    if (hayst->ptr == NULL) die("Allocation failed");

    fread(hayst->buf, 1, hayst->size, f);
    hayst->buf[hayst->size] = '\0';
    fclose(f);

    p = hayst->buf;
    pp = hayst->ptr;
    while (*p) *pp++ = p++;

    qsort(hayst->ptr, hayst->size, sizeof(*hayst->ptr), pstrcmp);

    return hayst;
}

/*
 *      Clean up hayst.
 */
void hayst_delete(Haystack *hayst)
{
    free(hayst->buf);
    free(hayst->ptr);
    free(hayst);
}

/*
 *      Binary range search for string pointers.
 */
static char **pstr_bsearch(const char *key, size_t len,
    char **arr, size_t high)
{
    size_t low = 0;

    while (low < high) {
        size_t mid = (low + high) / 2;
        int diff = strncmp(key, arr[mid], len);

        if (diff <= 0) high = mid;
        else low = mid + 1;
    }

    return arr + low;
}

/*
 *      Count occurrences of the string key in the haystack.
 */
size_t hayst_find(Haystack *hayst, size_t offset, size_t len)
{
    char *key = hayst->buf + offset;
    char **begin, **end;
    size_t n = 0;

    if (offset + len > hayst->size) return 0;

    begin = pstr_bsearch(key, len, hayst->ptr, hayst->size);
    if (begin == NULL) return 0;

    key[len - 1]++;
    end = pstr_bsearch(key, len, hayst->ptr, hayst->size);
    key[len - 1]--;
    if (end == NULL) return 0;
    if (end == begin) return 0;

    while (begin < end) {
        if (*begin < key) n++;
        begin++;
    }

    return n;
}

/*
 *      Example client code
 */
int main(int argc, char **argv)
{
    Haystack *hayst;
    FILE *f;

    if (argc != 3) die("Usage: %s string queries", *argv);

    hayst = hayst_new(argv[1]);

    f = fopen(argv[2], "r");
    if (f == NULL) die("Can't open %s.", argv[1]);
    for (;;) {
        char str[80];
        size_t p, len;
        size_t n;

        if (fgets(str, sizeof(str), f) == NULL) break;
        if (sscanf(str, "%zu %zu", &p, &len) < 2) continue;
        n = hayst_find(hayst, p, len);
        printf("%8zu %.*s\n", n, (int) len, hayst->buf + p);
    }
    fclose(f);

    hayst_delete(hayst);

    return 0;
}
person M Oehm    schedule 12.10.2014