Конструкция суффиксного массива O(N LogN) — соревновательное программирование 3 Стивен Халим

Я читаю книгу «Соревновательное программирование 3» Стивена Халима и Феликса Халима.

Я читаю главу о строках. Я пытаюсь понять алгоритм построения массива суффиксов. Я не понимаю часть сортировки по основанию. (Хотя я понимаю, как работает сортировка по основанию и сортировка по подсчету)

Вот код из книги

#define MAX_N 100010 // second approach: O(n log n)
char T[MAX_N]; // the input string, up to 100K characters
int n; // the length of input string

int RA[MAX_N], tempRA[MAX_N]; // rank array and temporary rank array
int SA[MAX_N], tempSA[MAX_N]; // suffix array and temporary suffix array

int c[MAX_N]; // for counting/radix sort

void countingSort(int k) { // O(n)

    int i, sum, maxi = max(300, n); // up to 255 ASCII chars or length of n
    memset(c, 0, sizeof c); // clear frequency table

    for (i = 0; i < n; i++){ // count the frequency of each integer rank
        c[i + k < n ? RA[i + k] : 0]++;
    }
    for (i = sum = 0; i < maxi; i++) {
        int t = c[i]; c[i] = sum; sum += t; 
    }
    for (i = 0; i < n; i++){ // shuffle the suffix array if necessary
        tempSA[c[SA[i]+k < n ? RA[SA[i]+k] : 0]++] = SA[i];
    }
    for (i = 0; i < n; i++){ // update the suffix array SA
        SA[i] = tempSA[i];
    }
}

void constructSA() { // this version can go up to 100000 characters
    int i, k, r;
    for (i = 0; i < n; i++) RA[i] = T[i]; // initial rankings
    for (i = 0; i < n; i++) SA[i] = i; //initial SA: {0, 1, 2, ..., n-1}

    for (k = 1; k < n; k <<= 1) { // repeat sorting process log n times
        countingSort(k); //actually radix sort:sort based on the second item
        countingSort(0); // then (stable) sort based on the first item

        tempRA[SA[0]] = r = 0; // re-ranking; start from rank r = 0

        // compare adjacent suffixes
        for (i = 1; i < n; i++){
            // if same pair => same rank r; otherwise,increase r
            tempRA[SA[i]] = (RA[SA[i]] == RA[SA[i-1]] && RA[SA[i]+k] == RA[SA[i-1]+k]) ? r : ++r;           
        }

        for (i = 0; i < n; i++){// update the rank array RA
            RA[i] = tempRA[i];
        }

        if (RA[SA[n-1]] == n-1) break; // nice optimization trick
    } 
}

Может кто-нибудь объяснить, что происходит в этих строках функции countingSort()?

for (i = sum = 0; i < maxi; i++) {
    int t = c[i]; c[i] = sum; sum += t; 
}
for (i = 0; i < n; i++){ // shuffle the suffix array if necessary
    tempSA[c[SA[i]+k < n ? RA[SA[i]+k] : 0]++] = SA[i];
}
for (i = 0; i < n; i++){ // update the suffix array SA
    SA[i] = tempSA[i];
}

Большое спасибо за ваше драгоценное время.


person user1423561    schedule 21.12.2015    source источник


Ответы (1)


Сначала вычислите startIndex для каждого уникального рейтинга.

ПРИМЕЧАНИЕ: c[] здесь означает рейтинг, а не только отдельный персонаж.

// compute cumulates of rankings
for (i = sum = 0; i < maxi; i++) {
    int t = c[i]; c[i] = sum; sum += t; 
}

Переупорядочите массив суффиксов, используя только что вычисленные значения startIndices. На основе рейтинга суффикса SA[i]+k.

// shuffle the suffix array if necessary
for (i = 0; i < n; i++){ 
    tempSA[c[SA[i]+k < n ? RA[SA[i]+k] : 0]++] = SA[i];
}

Скопируйте обновленные значения из временного массива.

// copy the updated values back to SA
for (i = 0; i < n; i++){ 
    SA[i] = tempSA[i];
}

Это означает, что суффикс, начинающийся с позиции i, сортируется по рейтингу суффикса на позиции (i+k).

Мы сортируем каждый суффикс длины k по суффиксу длины k на месте i+k. Мы можем это сделать, потому что на предыдущей итерации все суффиксы были отсортированы по длине k.

После этого снова сортируем по первому индексу. Который удерживал рейтинг по размеру k. Поскольку сортировка стабильна, все суффиксы теперь сортируются по длине k*2.

Наш следующий шаг — обновить рейтинг, если два смежных массива суффиксов в рейтинге больше не равны.

for (i = 1; i < n; i++){
    // if same pair => same rank r; otherwise,increase r
    tempRA[SA[i]] = (RA[SA[i]] == RA[SA[i-1]] && RA[SA[i]+k] == RA[SA[i-1]+k]) ? r : ++r;           
}

Если рейтинг для размера k в их startIndex одинаков, и рейтинг в их startIndex+k такой же. Тогда ранжирование в startIndex такое же, как и для размера k*2.

Это также должно объяснить следующее:

if (RA[SA[n-1]] == n-1) break; // nice optimization trick

Это означает, что в этот момент все рейтинги для текущего размера уникальны. Таким образом, все суффиксы также уникальны, и дальнейшая сортировка не требуется.


Пошаговый пример:

  a   b   c   x   a   b   c   d 
--------------------------------INIT-
  0   1   2   3   4   5   6   7 // SA
 97  98  99 120 97  98  99  100 // RA
---------------------------------K=1-
  0   2   5   7   1   3   4   6 // SA
  0   1   2   4   0   1   2   3 // RA
---------------------------------K=2-
  1   3   5   7   0   2   4   6 // SA
  1   3   5   7   0   2   4   6 // RA

Пример countintSort для шага K=1:

// count frequencies
c['a']=2;
c['b']=2;
c['c']=2;
c['d']=1;
c['x']=1;

// switch them to startindices
c['a']=0;
c['b']=2;
c['c']=4;
c['d']=6; // e.g. in total there are 6 suffixes smaller than starting with d (2 x a, 2 x b, 2 x c)
c['x']=7;

// determine the new SA position
tempSA[c[rank(SA[i]+k)]++] = SA[i];
// decomposing first iteration
tempSA[c[rank(SA[0]+k)]++] = SA[0]; // i = 0
tempSA[c[rank(SA[0]+1)]++] = SA[0]; // k = 1
tempSA[c[rank(1)]++] = 0; // SA[0] = 0
tempSA[c['b']++] = 0; // rank(1) = 'B'
tempSA[2] = 0; // c['b']=2 => 2++ = 3

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

// all other iterations resulting in:
tempSA[0] = 7 // d (sorted by EMPTY)
tempSA[1] = 3 // x (sorted by a)
tempSA[2] = 0 // a (sorted by b)
tempSA[3] = 4 // a (sorted by b)
tempSA[4] = 1 // b (sorted by c)
tempSA[5] = 5 // b (sorted by c)
tempSA[6] = 6 // c (sorted by d) 
tempSA[7] = 2 // c (sorted by d)

// last step is simply copying those values to SA (I suppose you know why this is)

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

person Sam Segers    schedule 21.12.2015
comment
Большое спасибо за ваш ответ Сэм. Если это возможно, не могли бы вы объяснить шаги процедуры сортировки подсчетом для ввода abcxabcd? Большое спасибо за ваше время. - person user1423561; 23.12.2015
comment
Кроме того, не могли бы вы сказать, почему символ «$» добавляется к строке? - person user1423561; 23.12.2015
comment
stackoverflow.com/questions/13422573/ - person Sam Segers; 24.12.2015
comment
Я попытался добавить некоторую информацию о abcxabcd, но если это все еще не ясно. Я рекомендую вам сделать некоторую отладку с некоторыми примерами, где это для вас не ясно. - person Sam Segers; 24.12.2015