Подсчитать специальные вхождения подстрок в строку

0

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

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

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

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

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

Теги:
algorithm
counting
substring
query-optimization

2 ответа

1
Лучший ответ

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

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

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

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

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

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

#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;
}
  • 0
    да, все запросы известны заранее
  • 0
    Тогда этот метод должен работать для вас. Я добавил пример кода на C. (Извините за это, я не очень хорош в C ++.)
1

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

  • Создайте массив указателей в каждый символ вашей строки. Сортируйте его.
  • Посмотрите первое вхождение запроса в отсортированном массиве auffix с бинарным поиском.
  • Посмотрите последнее вхождение запроса. Вы можете сделать это, увеличив последний символ, чтобы вы искали "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;
}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню