Сходство строк в c

Для двух строк A и B мы определяем сходство строк как длину самого длинного префикса, общего для обеих строк. Например, сходство строк «abc» и «abd» равно 2, а сходство строк «aaa» и «aaab» равно 3. Вычислите сумму сходств строки S с каждым из ее суффиксов.

Вот мое решение...

#include<stdio.h>
#include<string.h>
int getSim(char str[],int subindex)
{
    int l2=subindex
    int i=0;
    int count=0;
    for(i=0;i<l2;i++)
        if(str[i]==str[subindex])
        {
            count++;
            subindex++;
        }
        else
            break;
    return count;   
}
int main()
{
    int testcase=0;
    int len=0;
    int sum=0;
    int i=0;
    char s[100000];
    scanf("%d",&testcase);
    while(testcase--)
    {
        sum=0;
        scanf("%s",s);
        for(i=0;i<strlen(s);i++)
            if(s[i]==s[0])
            {
                sum=sum+getSim(s,i);
            }
        printf("%d\n",sum);
    }
}

Как мы можем решить эту проблему, используя массив суффиксов??


person agasthyan    schedule 15.01.2012    source источник
comment
Какой вывод вашей программы вы считаете неправильным? Если вы ищете обзор кода, попробуйте codereview.stackexchange... StackOverflow предназначен для тех случаев, когда у вас есть конкретный вопрос, на который нужно ответить.   -  person corsiKa    schedule 15.01.2012
comment
почему вы говорите о массиве суффиксов? Разве это не должен быть префикс?   -  person Basile Starynkevitch    schedule 15.01.2012
comment
@glowcoder нет ничего плохого в выводе моей программы.... но она немного замедляется, когда размер ввода увеличивается... скажем, строка длиной 100000.... поэтому кто-то предложил использовать массив суффиксов.....   -  person agasthyan    schedule 15.01.2012


Ответы (2)


Я не уверен, что это лучший алгоритм, но вот решение.

Прежде всего, создайте массив суффиксов. Наивный алгоритм (помещение всех суффиксов в массив, а затем его сортировка) довольно медленный - операции O (n ^ 2 * log (n)), существует несколько алгоритмов для выполнения этого за время O (nlogn).

Я предполагаю, что строки имеют индекс 0.

  1. Теперь возьмите первую букву l в строке s и используйте один двоичный поиск, чтобы найти индекс i первой строки в массиве суффиксов, который начинается с l, и другой двоичный поиск, чтобы найти индекс j первой строки в диапазоне. [i..n], который не начинается с l. Тогда вы получите, что все строки в диапазоне [i..j-1] начинаются с одной и той же буквы l. Так что ответ на задачу как минимум j-i.

  2. Затем примените аналогичную процедуру к строкам в диапазоне [i..j). Возьмите вторую букву l2, найдите индексы i2 и j2, соответствующие первой строке s[i2] такой, что s[i2][1] == l2, и первой строке s[j2] такой, что s[j2][1] != l2. Добавьте j2-i2 к ответу.

  3. Повторите эту процедуру n раз, пока не закончатся буквы в исходной строке. Ответ на задачу j1-i1+j2-i2+...+jn-in

person Alexander Putilin    schedule 15.01.2012

Вы упоминаете в комментариях, что это правильно, но очень медленно.

В Java вы можете получить длину String с помощью s.length() — это значение кэшируется в объекте, и его нужно получить O(1).

Но когда вы переходите к C, вы получаете длину строки с strlen(s), которая каждый раз пересчитывается (в O(n)). Итак, пока вы должны делать O(n), поскольку у вас есть операция O(n), вся функция становится O(n^2).

Чтобы обойти это, кэшируйте значение один раз при его запуске. Это вернет вас в линейное время.

Плохой:

scanf("%s",s);
for(i=0;i<strlen(s);i++)
    if(s[i]==s[0])
    {
        sum=sum+getSim(s,i);
    }

Хорошо:

scanf("%s",s);
strlen = strlen(s); /* assume you declared "int strlen" earlier */
for(i=0;i<strlen;i++) /* this is now constant time to run */
    if(s[i]==s[0])
    {
        sum=sum+getSim(s,i);
    }
person corsiKa    schedule 15.01.2012
comment
Это не дает линейного времени для ввода с высоким сходством, getSim() также может быть O (n). Рассмотрим случай memset(str, 'a', n). - person Daniel Fischer; 15.01.2012
comment
@ Даниэль достаточно честно. На самом деле я не знаю текущее время его алгоритма. Я предполагал, что это было O(n), потому что это только кажется (назовем это интуицией программиста) чем-то, что делается за O(n) времени. Каким бы ни было его текущее время, он может уменьшить его в n раза с помощью описанной выше уловки. - person corsiKa; 16.01.2012
comment
Только если работа, проделанная внутри цикла, равна O(1). Конечно, всегда полезно вычислить strlen() только один раз. Это переводит вас из O(n*(n + body)) в O(n*body), но если body также равно O(n), это постоянное уменьшение коэффициента, если body равно O(n^2), это арахис. , если body равно O(sqrt(n)) это сокращение на коэффициент sqrt(n). - person Daniel Fischer; 16.01.2012
comment
@ Даниэль ммм, не совсем арахис. Если тело равно O(n^2), то без кэширования strlen ваше общее количество равно O(n^3). Переход от кубического к квадратичному — это ОГРОМНОЕ увеличение скорости. На самом деле, чем менее работоспособен корпус, тем лучше прибавку дает кеш. Несмотря ни на что, это всегда фактор n. - person corsiKa; 16.01.2012
comment
Нет. Скажем, body это sum += strlen(s);. Без кэширования у вас есть два вызова strlen() на итерацию, с кэшированием — один. Вы не умножаете стоимость body на стоимость strlen(), вы их суммируете. - person Daniel Fischer; 16.01.2012
comment
@ Даниэль Это просто неточно. Расчет strlen(s) находится в цикле for. Это означает, что он будет рассчитан n раза... При кэшировании у вас будет 0 вызовов strlen на каждой итерации. Как правило, если строка остается неизменной, вам никогда не придется вычислять ее длину более одного раза. - person corsiKa; 16.01.2012
comment
давайте продолжим обсуждение в чате - person Daniel Fischer; 16.01.2012
comment
Я абсолютно согласен с последней фразой. Но если тело sum += strlen(s);, то мы вычисляем strlen(s) дважды для каждого индекса, что составляет 2*n раз для общей стоимости 2*n*n. Если мы переместим вычисление strlen(s) из заголовка цикла, но не изменим тело, мы вызовем strlen(s) всего (n+1) раз, один раз для предварительного вычисления и один раз для каждого индекса. Всего (n+1)*n затрат. - person Daniel Fischer; 16.01.2012
comment
Если бы это было так, вы могли бы так же легко использовать кэшированное значение внутри тела цикла... Несмотря на это, я не вижу, какое отношение это имеет к вопросу, поскольку strlen не находится внутри тела... - person corsiKa; 16.01.2012
comment
Нет, но getSim(s,i) имеет сложность до n - i, strlen был просто простым примером O(n) в теле цикла. Стоимость итерации цикла составляет O (стоимость (условие) + стоимость (тело) + стоимость (обновление)). Если стоимость (тело) хуже, чем стоимость (условие), уменьшение стоимости условия до O (1) не даст вам многого. Если оба имеют одинаковую сложность, вы получаете постоянное ускорение. - person Daniel Fischer; 16.01.2012
comment
@ Даниэль, извините, ваша формула O(cost(condition) + cost(body) + cost(update)) не точна. Это больше похоже на O(cost(for loop) * cost(getSim())). Независимо от того, какова стоимость функции getSim(), стоимость программы сейчас составляет n * cost(getSim()) из-за strlen() в цикле for. В настоящее время getSim() работает в O(i), то есть O(strlen(s)). Так что моя оценка перехода от O(n^2) к O(n) верна. - person corsiKa; 16.01.2012
comment
Я добавил код в свой ответ, запустите его, если вы мне не верите. Это не коэффициент 100000, а 2, более или менее. Конечно, он специально разработан таким образом, что тело цикла имеет стоимость n - i, для обычных строк стоимость getSim будет в среднем O(1) или O(маленькая), тогда перемещение strlen из условия цикла даст коэффициент н (или закрыть). - person Daniel Fischer; 16.01.2012