后缀数组

约定:源字符串S,下标从0开始

后缀j: S[j,j+1,j+2....],就是从第j个字符开始的后缀

is[i]第i为开始的后缀
0aabracadabra
1bbracadabra
2rracadabra
3aacadabra
4ccadabra
5aadabra
6ddabra
7aabra
8bbra
9rra
10aa
11$(空)$(空)

后缀数组:通常在代码中定义为sa[],是把一个字符串的所有后缀进行排序后的数组

sa[i] = j:表示把所有后缀排序后处于第i小的字符串是后缀j

i表示的字符串S[sa[i]]后缀数组sa[i]
0$(空)11
1a10
2abra7
3abracadabra0
4acadabra3
5adabra5
6bra8
7bracadabra1
8cadabra4
9dabra6
10ra9
11racadabra2

高度数组lcp:表示后缀数组中相邻的两个的最长公共前缀lcp[i] = LongestCommonPrefix(S[sa[i]], S[sa[i+1]])

i后缀数组sa[i]lcp[i](右列相邻两个的最长前缀)第i为开始的后缀
0110$(空)
1101a
274abra
301abracadabra
431acadabra
550adabra
683bra
710bracadabra
840cadabra
960dabra
1092ra
1120(没有可比较的字符串)racadabra

下面是《挑战程序设计》的算法,复杂度是O(nlog^2n).

//挑战程序设计 p378 4.7.3 后缀数组
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;


const int MAX_N = 100010;
int n, k;
int rak[MAX_N + 1];//rank
int tmp[MAX_N + 1];
int sa[MAX_N + 1];
int lcp[MAX_N + 1];

//比较 (rank[i], rank[i+k]) 和 (rank[j], rank[j+k])
bool compare_sa(int i, int j) {
    if(rak[i] != rak[j]) return rak[i] < rak[j];
    else {
        int ri = i + k <= n ? rak[i + k]  : -1;
        int rj = j + k <= n ? rak[j + k]  : -1;
        return ri < rj;
    }
}

//计算字符串S的后缀数组
void construct_sa(string S) {
    n = S.length();
    //初始长度为1, rank直接取字符的编码
    for(int i = 0; i <= n; i++) {
        sa[i] = i;
        rak[i] = i < n ? S[i] : -1;
    }
    //利用对长度为k的排序结果对长度为2k的排序
    for(k = 1; k <= n; k <<= 1) {
        sort(sa, sa + n + 1, compare_sa);
        //先在tmp中临时存储新计算的rank,再转存回rank中
        tmp[sa[0]] = 0;
        for(int i = 1; i <= n; i++) {
            tmp[sa[i]] = tmp[sa[i - 1]] + (compare_sa(sa[i - 1], sa[i]) ? 1 : 0);
        }
        for(int i = 0; i <= n; i++) {
            rak[i] = tmp[i];
        }
    }
}

void construct_lcp(string S) {
    n = S.length();
    for(int i = 0; i < n ; i++) rak[sa[i]] = i;
    int h = 0;
    //lcp[i] = S[sa[i]]和S[sa[i+1]]的最长公共前缀
    lcp[0] = 0;
    for(int i = 0; i < n; i++) {
        //计算字符串中从位置i开始的后缀及其在后缀数组中的前一个后缀的lco
        int j = sa[rak[i] - 1];
        //将h先减去首字母的1长度,在保持前缀相同的前提下不断增加
        if(h > 0) h--;
        for(; j + h < n && i + h < n; h++) {
            if(S[j + h] != S[i + h]) break;
        }
        lcp[rak[i] - 1] = h;
    }
}

int main() {
    string s = "abracadabra";
    construct_sa(s);
    construct_lcp(s);
    cout << "s.length = " << s.length() << endl;
    for(int i = 0; i <= n; i++) {
        cout << i << ", " << "sa[" << i << "]:" << sa[i] << " " << lcp[i] << " " << s.substr(sa[i]) << endl;
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值