后缀树(suffix tree & array)

定义:后缀数组(suffix array)是将字符串的所有后缀进行排序放入数组中。后缀树(suffix tree)则是所有后缀形成的字典树(trie)的一种压缩表示。后缀数组相对后缀树来说,使用的存储空间更小(只用保存原始字符串和一个长度相同的整数数组)。
后缀树在字符串的很多算法(例如查找,匹配,最长公共子串等)中有广泛应用,是一种非常实用的数据结构。



:对于单词banana,有如下后缀数组表示:

index suffix 排序后index 排序后suffix
0 banana 5 a
1 anana 3 ana
2 nana 1 anana
3 ana 0 banana
4 na 4 na
5 a 2 nana

后缀数组的表示为: [5,3,1,0,4,2], 其中数字表示后缀首字母再整个字符串中的位置(即从0开始的下标)。

使用最“笨”的方法可以在时间复杂度内构建后缀数组,其中是字符串长度。思路是使用的排序算法对后缀字符串排序,同时保持后缀起始字符下标。因为两个字符串比较大小需要复杂度,所以整体复杂度为。

本文介绍一种的后缀数组构建算法。简单期间,首先考虑复杂度的算法。出发点是充分利用所有后缀字符串都是来源于一个字符串的特点,同时借鉴radix排序思路。算法由轮排序完成,第1轮排序只对第1个字符排序,第2轮排序对前2个字符排序,第3轮排序对前4个字符排序,第k轮排序对前个字符排序。注意这里有一点很重要,如果所有后缀字符串已经按照前字符排序,则可以使用时间按照前排序,原因是两个suffix可以在常数(而不是)时间内比较大小。后缀数组

:构建字符串banana的后缀数组
为每个suffix分配一个序号rank,例如,对第个字符串分配rank为str[i]-'a'。这样,得到下面rank表:

index suffix rank
0 banana 1
1 anana 0
2 nana 13
3 ana 0
4 na 13
5 a 0

根据前2个字符排序
对于每个字符,同时保存和它相邻的下一个字符的序号(记为next rank)。特殊情况,对于最后一个字符,它的next rank标记为-1。得到下面表:

Index Suffix Rank Next Rank
0 banana 1 0
1 anana 0 13
2 nana 13 0
3 ana 0 13
4 na 13 0
5 a 0 -1

先根据rank,再根据next rank对所有后缀字符串排序,结果如下表:

Index Suffix Rank Next Rank
5 a 0 -1
1 anana 0 13
3 ana 0 13
0 banana 1 0
2 nana 13 0
4 na 13 0

根据前4个字符排序
对前面排序结果,给所有后缀字符串逐个分配新rank。第1个suffix的rank为0;从第2个suffix开始,根据其rank和next rank 组合是否和上一个组合相同分配rank,如果不同,rank加1,如果相同,rank不变。如下面表所示:

Index Suffix Rank
5 a 0 初始值
1 anana 1 (0, 13) 和前面不同
3 ana 1 (0, 13) 和前面相同
0 banana 2 (1, 0) 和前面不同
2 nana 3 (13, 0)和前面不同
4 na 3 (13, 0) 和前面相同

对每个suffix (str[i]),同时存储str[i+2]的rank为next rank。如果i+2>=n,则next rank为-1,得到下表:

Index Suffix Rank Next Rank
5 a 0 -1
1 anana 1 1
3 ana 1 0
0 banana 2 3
2 nana 3 3
4 na 3 -1

按照上面表中Rank和Next Rank对所有suffix排序,结果如下表:

Index Suffix Rank Next Rank
5 a 0 -1
3 ana 1 0
1 anana 1 1
0 banana 2 3
4 na 3 -1
2 nana 3 3

至此,排序结束,后缀数组生成。

#include 
#include 
#include   // sort 函数
using namespace std;

struct suffix
{
    int index;        // 后缀串原始index(起始下标)
    int rank[2];      // rank 和next rank,用于排序
};

// 比较两个后缀字符串
int cmp(struct suffix a, struct suffix b)
{
    return (a.rank[0] == b.rank[0])?
    (a.rank[1] < b.rank[1] ?1: 0):
    (a.rank[0] < b.rank[0] ?1: 0);
}

// 构建长度为n的输入字符串txt的后缀数组
int *buildSuffixArray(char *txt, int n)
{
    // 使用struct方便按照rank排序,同时保留index
    struct suffix suffixes[n];
    // 初始化index和rank
    for (int i = 0; i < n; i++)
    {
        suffixes[i].index = i;
        suffixes[i].rank[0] = txt[i] - 'a';
        suffixes[i].rank[1] = ((i+1) < n)? (txt[i + 1] - 'a') : -1;
    }
    // 根据前2个字符排序
    sort(suffixes, suffixes+n, cmp);
    
    int ind[n]; // 保存suffix的在数组suffixes中的index 从原始index得到它在suffixes中的位置
                // 用于获取next rank.
    
    // 根据前k (4,8, 16, 32, ...) 个字符排序
    for (int k = 4; k < 2*n; k = k*2)
    {
        // 第一个suffix rank为0
        int rank = 0;
        int prev_rank = suffixes[0].rank[0];
        suffixes[0].rank[0] = rank;
        
        ind[suffixes[0].index] = 0;
        
        // 赋值rank
        for (int i = 1; i < n; i++)
        {
            
            // 和前面相同
            if (suffixes[i].rank[0] == prev_rank &&
                suffixes[i].rank[1] == suffixes[i-1].rank[1])
            {
                prev_rank = suffixes[i].rank[0];
                suffixes[i].rank[0] = rank;
            }
            else
            {
                prev_rank = suffixes[i].rank[0];
                suffixes[i].rank[0] = ++rank;
            }
            
            // 第suffixes[i].index个suffix是数组suffixes中的i个元素
            ind[suffixes[i].index] = i;
        }
        
        // 赋值next rank
        for (int i = 0; i < n; i++)
        {
            int nextindex = suffixes[i].index + k/2;
            suffixes[i].rank[1] = (nextindex < n)?
                suffixes[ind[nextindex]].rank[0] : -1;
        }
        
        sort(suffixes, suffixes+n, cmp);
    }
    
    // (有序)后缀数组,每个元素为后缀字符串的起始下标
    int *suffixArr = new int[n];
    for (int i = 0; i < n; i++)
        suffixArr[i] = suffixes[i].index;
    
    return suffixArr;
}

void printArr(int arr[], int n)
{
    for (int i = 0; i < n; i++)
        cout << arr[i] << " ";
    
    cout << endl;
}

int main()
{
    char txt[] = "banana";
    int n = (int)strlen(txt);
    
    int *suffixArr = buildSuffixArray(txt, n);
    cout << "Following is suffix array for "<< txt << endl;
    
    printArr(suffixArr, n);
    
    return 0;
}


程序输出:

Following is suffix array for banana
5 3 1 4 2 0


总结
可以发现,后缀数组的构建充分利用了后缀数组的特性(相邻suffix的rank)来快速比较两个字符串,来降低构建复杂性,rank的计算复杂性为。同时注意上面算法使用了标准(快速)排序,时间复杂度为,可以使用基数Radix排序(时间复杂度为)来降低整个算法复杂性为。

参考资料

  1. suffix -array
  2. http://www.cbcb.umd.edu/confcour/Fall2012/lec14b.pdf

你可能感兴趣的:(后缀树(suffix tree & array))