• 后缀数组处理字符串的利器


    后缀数组是处理字符串的有力工具。后缀数组是后缀树的一个非常精巧的替代品,它比后缀树容易编程实现,能够实现后缀树的很多功能而时间复杂度也并不逊色,而且它比后缀树所占用的内存空间小很多。

    子串:字符串S的子串r[i..j],i<=j,表示r串中从i到j这一段,也就是顺次排列r[i],r[i+1],...,r[j]形成的字符串。

    后缀:后缀是指从某个位置i开始到整个串末尾结束的一个特殊子串。字符串 s 的从第i个字符开始的后缀表示为Suffix(i), 也就是Suffix(i)=r[i..len(s)]。

    大小比较:关于字符串的大小比较,是指通常所说的“字典顺序”比较,也就是对于两个字符串u、v,令i 从1 开始顺次比较u[i]和v[i],如果u[i]=v[i]则令i加1,否则若u[i]<v[i]则认为u<v,u[i]>v[i]则认为u>v(也就是v<u),比较结束。如果 i>len(u)或者i>len(v)仍比较不出结果,那么,若len(u)<len(v)则认为u<v,若len(u)=len(v)则认为u=v,若len(u)>len(v)则u>v。
    从字符串的大小比较的定义来看,S的两个开头位置不同的后缀u和v进行比较的结果不可能是相等,因为u=v的必要条件len(u)=len(v)在这里不可能满足。

    后缀数组:后缀数组SA是一个一维数组,它保存1..n的某个排列SA[1],SA[2],……,SA[n],并且保证Suffix(SA[i])<Suffix(SA[i+1]),1<=i<n。也就是将S的n个后缀从小到大进行排序之后把排好序的后缀的开头位置顺次放入SA中。

    1 后缀数组求最长公共子串(LCS)

    解法:将两个字符串用一个特殊符号(两个字符串中都没有,比如‘#’)连接起来,构造连接后字符串的后缀数组,求后缀数组中的最长公共前缀,要保证最长的公共前缀在原来两个字符串中都出现,而不是只出现在一个字符串中,这就是特殊连接符号的作用。

    #include <iostream>
    using namespace std;
    
    //用于qsort的比较函数
    int pstrcmp(const void *p, const void *q) 
    {     
        return strcmp(*(char**)p,*(char**)q); 
    }
    
    //最长公共前缀
    int comlen_suff(char * p, char * q) 
    {    
        int len = 0;
        int count = 0; //保证两个子串中只有一个含有‘#’,LCS才来自两个字符串,否则可能来自同一个字符串
        while(*p && *q && *p++ == *q++)     
        {         
            ++len;         
            if(*p == '#' || *q == '#')
            {
                break;
            }
        }
        while(*p)
        {
            if(*p++ == '#')
            {
                ++ count;
                break;
            }
        }
        while(*q)
        {
            if(*q++ == '#')
            {
                ++ count;
                break;
            }
        }
        if(count == 1)
            return len;
        return 0; 
    }   
    
    //最长公共子串
    int LCS(char * X, char * Y) 
    {
        char * suff[999];
        int maxlen = 0;
        int suf_index;
        int xlen = strlen(X);
        int ylen = strlen(Y);
        int len_suff = xlen + ylen + 1;     
        char * arr = new char[len_suff + 1];  // 将X和Y连接到一起
        strcpy(arr,X);   
        arr[xlen] = '#';
        strcpy(arr + xlen + 1, Y);       
        for(int i = 0; i < len_suff; ++i)  // 初始化后缀数组
        {
            suff[i] = &arr[i];     
        }
        qsort(suff, len_suff, sizeof(char *), pstrcmp);    
        for(int i = 0; i < len_suff-1; ++i)    
        {
            int len = comlen_suff(suff[i],suff[i+1]);
            if(len > maxlen)        
            {
                maxlen = len;       
                suf_index = i;   
            }
        }
        printf("%.*s\n", maxlen, suff[suf_index]);
        delete[] arr;
        return maxlen;
    }
    
    int main()
    {
        cout<<LCS("aabaaba","aba")<<endl;
        return 0;
    }

    2 后缀数组求最长回文子串(LPS)

    解法:将字符串的逆序与原来字符串用特殊符号连接,构造后缀数组,求后缀数组中的最长公共前缀,保证最长公共前缀出现在特殊连接符号的两端。

    #include <iostream>
    using namespace std;
    
    //用于qsort的比较函数
    int pstrcmp(const void *p, const void *q) 
    {     
        return strcmp(*(char**)p,*(char**)q); 
    }
    
    //最长公共前缀
    int comlen_suff(char * p, char * q) 
    {    
        int len = 0;
        int count = 0; //保证两个子串中只有一个含有‘#’,LCS才来自两个字符串,否则可能来自同一个字符串
        while(*p && *q && *p++ == *q++)     
        {         
            ++len;         
            if(*p == '#' || *q == '#')
            {
                break;
            }
        }
        while(*p)
        {
            if(*p++ == '#')
            {
                ++ count;
                break;
            }
        }
        while(*q)
        {
            if(*q++ == '#')
            {
                ++ count;
                break;
            }
        }
        if(count == 1)
            return len;
        return 0; 
    }
    
    //最长回文子串
    int LPS(char * X) 
    {
        char * suff[999];
        int maxlen = 0;
        int suf_index;
        int xlen = strlen(X);
        int len_suff = 2 * xlen + 1;    
        char * arr = new char[len_suff + 1];  // 将X和逆序X连接到一起  
        strcpy(arr,X);
        arr[xlen] = '#';
        char *p = X;
        char *q = arr + len_suff;  
        *q = '\0';
        while(*p && (*--q = *p++)); // 逆序复制
        for(int i = 0; i < len_suff; ++i)  // 初始化后缀数组
        {
            suff[i] = &arr[i];   
        }
        qsort(suff, len_suff, sizeof(char *), pstrcmp);   
        for(int i = 0; i < len_suff-1; ++i)    
        {        
            int len = comlen_suff(suff[i],suff[i+1]);    
            if(len > maxlen)
            {          
                maxlen = len;
                suf_index = i;
            }  
        }
        printf("%.*s\n", maxlen, suff[suf_index]);
        delete[] arr;
        return maxlen;
    }
    
    int main()
    {
        cout<<LPS("aabaab")<<endl;
        return 0;
    }

    3 后缀数组求最长重复子串(LRS)

    解法:构造字符串的后缀数组,对后缀数组排序,再两两比较得到最长的重复子串

    //compare funciton used by qsort()
    int pstrcmp(const void *p, const void *q)
    {
        return strcmp(*(char **)p, *(char **)q);
    }
    
    //get max common length of string p and q
    int comlen(char *p, char *q)
    {
        int len = 0;
        while (*p && (*p++ == *q++))
            len++;
        return len;
    }
    
    //get max repeat substring of str 
    int find_max_repeat(char* str, char* result, int & len)
    {
        int temlen, maxi, maxlen = -1;
        char *a[99999];
        int n = 0;
    
        while (*str != '\0')
        {
            a[n++] = str++;
        }
        qsort(a, n, sizeof(char *), pstrcmp);
        for (int i = 0; i < n-1; i++)
        {
            temlen = comlen(a[i], a[i+1]);
            if (temlen > maxlen)
            {
                maxlen = temlen;
                maxi = i;
            }
        }
        result = a[maxi];
        len = maxlen;
        printf("%.*s\n", maxlen, result);
        return maxlen;
    }

    4 后缀数组求最长的没有重复字符的子串

    解法:对这个字符串构造后缀数组,在每个后缀数组中,寻找没有重复字符的最长前缀,就是要找的子串。

    //得到字符串最长的无重复的前缀长度
    int longestlen(char * p)
    {
        int hash[256];
        int len = 0;
        memset(hash,0,sizeof(hash));
        while (*p && !hash[*p])
        {
            hash[*p] = 1;
            ++ len;
            ++ p;
        }
        return len;
    }
    
    //使用后缀数组解法
    int longest_unique_substring(char * str)
    {
        int maxlen = -1;
        int begin = 0;
        char *a[99999];
        int n = 0;
        while(*str != '\0')
        {
            a[n++] = str++;
        }
        for (int i=0; i<n; i++)
        {
            int temlen = longestlen(a[i]);
            if (temlen > maxlen)
            {
                maxlen = temlen;
                begin = i;
            }
        }
        printf("%.*s\n", maxlen, a[begin]);
        return maxlen;
    }
  • 相关阅读:
    Java服务,内存OOM了,如何快速定位?
    Java内存分析工具MAT(Memory Analyzer Tool)安装使用实例
    jmap使用方法及原理
    可能发生Full gc 的情况
    java--jvm GC-常用参数配置
    JVM. GC 性能调优方法与思路
    《嫌疑人X的献身》——两个天才之间的思想火花
    爱的纯粹与代价
    18年下半年计划表
    阿里校招准备-总纲
  • 原文地址:https://www.cnblogs.com/luxiaoxun/p/2712131.html
Copyright © 2020-2023  润新知