📜  后缀数组|设置1(简介)

📅  最后修改于: 2021-04-17 08:46:32             🧑  作者: Mango

我们强烈建议您阅读以下关于后缀树的文章,作为该文章的前提条件。

模式搜索设置8(后缀树介绍)

后缀数组是给定字符串的所有后缀的排序数组。定义类似于后缀树,后缀树是给定文本的所有后缀的压缩特里。任何基于后缀树的算法都可以替换为使用带有附加信息增强后缀数组并在相同的时间复杂度下解决相同问题的算法(源Wiki)。
通过对后缀树进行DFS遍历,可以从后缀树构造后缀数组。实际上,后缀数组和后缀树都可以在线性时间内相互构造。
与后缀树相比,后缀数组的优势包括改进的空间需求,更简单的线性时间构造算法(例如,与Ukkonen算法相比)和改进的缓存局部性(来源:Wiki)

例子:

Let the given string be "banana".

0 banana                          5 a
1 anana     Sort the Suffixes     3 ana
2 nana      ---------------->     1 anana  
3 ana        alphabetically       0 banana  
4 na                              4 na   
5 a                               2 nana

So the suffix array for "banana" is {5, 3, 1, 0, 4, 2}

幼稚的方法来构建后缀数组
构造后缀数组的一种简单方法是将所有后缀组成一个数组,然后对该数组进行排序。以下是简单方法的实现。

// Naive algorithm for building suffix array of a given text
#include 
#include 
#include 
using namespace std;
  
// Structure to store information of a suffix
struct suffix
{
    int index;
    char *suff;
};
  
// A comparison function used by sort() to compare two suffixes
int cmp(struct suffix a, struct suffix b)
{
    return strcmp(a.suff, b.suff) < 0? 1 : 0;
}
  
// This is the main function that takes a string 'txt' of size n as an
// argument, builds and return the suffix array for the given string
int *buildSuffixArray(char *txt, int n)
{
    // A structure to store suffixes and their indexes
    struct suffix suffixes[n];
  
    // Store suffixes and their indexes in an array of structures.
    // The structure is needed to sort the suffixes alphabatically
    // and maintain their old indexes while sorting
    for (int i = 0; i < n; i++)
    {
        suffixes[i].index = i;
        suffixes[i].suff = (txt+i);
    }
  
    // Sort the suffixes using the comparison function
    // defined above.
    sort(suffixes, suffixes+n, cmp);
  
    // Store indexes of all sorted suffixes in the suffix array
    int *suffixArr = new int[n];
    for (int i = 0; i < n; i++)
        suffixArr[i] = suffixes[i].index;
  
    // Return the suffix array
    return  suffixArr;
}
  
// A utility function to print an array of given size
void printArr(int arr[], int n)
{
    for(int i = 0; i < n; i++)
        cout << arr[i] << " ";
    cout << endl;
}
  
// Driver program to test above functions
int main()
{
    char txt[] = "banana";
    int n = 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 0 4 2

如果考虑将O(nLogn)算法用于排序,则上述构建后缀数组的方法的时间复杂度为O(n 2 Logn)。排序步骤本身需要O(n 2 Logn)时间,因为每个比较都是两个字符串的比较,而比较则需要O(n)时间。
有许多有效的算法可以构建后缀数组。我们很快将在单独的文章中介绍它们。

使用内置的后缀数组搜索模式
为了搜索文本中的模式,我们对文本进行预处理并构建文本的后缀数组。由于我们具有所有后缀的排序数组,因此可以使用二进制搜索进行搜索。以下是搜索函数。请注意,该函数不会报告所有出现的模式,而只会报告其中之一。

// This code only contains search() and main. To make it a complete running
// above code or see https://ide.geeksforgeeks.org/oY7OkD
  
// A suffix array based search function to search a given pattern
// 'pat' in given text 'txt' using suffix array suffArr[]
void search(char *pat, char *txt, int *suffArr, int n)
{
    int m = strlen(pat);  // get length of pattern, needed for strncmp()
  
    // Do simple binary search for the pat in txt using the
    // built suffix array
    int l = 0, r = n-1;  // Initilize left and right indexes
    while (l <= r)
    {
        // See if 'pat' is prefix of middle suffix in suffix array
        int mid = l + (r - l)/2;
        int res = strncmp(pat, txt+suffArr[mid], m);
  
        // If match found at the middle, print it and return
        if (res == 0)
        {
            cout << "Pattern found at index " << suffArr[mid];
            return;
        }
  
        // Move to left half if pattern is alphabtically less than
        // the mid suffix
        if (res < 0) r = mid - 1;
  
        // Otherwise move to right half
        else l = mid + 1;
    }
  
    // We reach here if return statement in loop is not executed
    cout << "Pattern not found";
}
  
// Driver program to test above function
int main()
{
    char txt[] = "banana";  // text
    char pat[] = "nan";   // pattern to be searched in text
  
    // Build suffix array
    int n = strlen(txt);
    int *suffArr = buildSuffixArray(txt, n);
  
    // search pat in txt using the built suffix array
    search(pat, txt, suffArr, n);
  
    return 0;
}

输出:

Pattern found at index 2

上述搜索函数的时间复杂度为O(mLogn)。建立后缀数组后,将有更有效的算法来搜索模式。实际上,存在基于O(m)后缀数组的算法来搜索模式。我们将很快讨论有效的搜索算法。

后缀数组的应用
后缀数组是一种非常有用的数据结构,它可以用于各种各样的问题。以下是一些可以使用后缀数组的著名问题。
1)模式搜索
2)找到最长的重复子串
3)找到最长的公共子串
4)查找字符串最长的回文

有关可以使用后缀数组的更多问题,请参见此内容。

这篇文章是一个简单的介绍。后缀数组有很多要讲的内容。我们在这里讨论了后缀数组构造的O(nLogn)算法。我们将很快讨论更有效的后缀数组算法。

参考:
http://www.stanford.edu/class/cs97si/suffix-array.pdf
http://en.wikipedia.org/wiki/Suffix_array