后缀数组

详细的学习了后缀数组,及其应用。由于罗穗骞论文中的代码写的有些随意,我按照自己的思路,算是整理了一下吧。

倍增法求 后缀数组,以及height数组

 

char* strTest = "aabaaaab";
const int MaxSize = 256;
// arrRank为各位置后缀排名,末位的$排名为0; arrSufix为排名对应的位置索引,arrSufix[0]为strlen(char*);arrHeight[i]为排名第i的后缀与排名为i-1的后缀的公共串长度,从arrHeight[1] = 0有效
int arrRank[MaxSize], arrSufix[MaxSize], arrHeight[MaxSize];

void prepairData()
{
}

bool CompareRank(int a, int b, int nStep)
{
    // 若arrRank[a] == arrRank[b],则说明位置a,b开头的nStep长的串中不包含$,于是它们的第二关键字(nStep长的串)仍然是上轮第一关键字的延续
    return arrRank[a] == arrRank[b] && arrRank[a + nStep] == arrRank[b + nStep];
}

void DoubleAdding(const char* strSource)
{
    int nNewStrSize = strlen(strSource) + 1;
    int arrSufix2[MaxSize];// 第二关键字的排序数组
    int arrRankTemp[MaxSize];// 用于从上次的arrRank计算更新后的arrRank

    for (int i = 0; i < nNewStrSize - 1; i++)
        arrRank[i] = strSource[i] - '$';// 将字符串转换为整数数组
    arrRank[nNewStrSize - 1] = 0;// 在原字符串的基础上增加'$'(ascii码小于数字和字母,且其为vi中的终结符,有意义)

    int arrCount[MaxSize];// 计数用于基数排序
    memset(arrCount, 0, sizeof(arrCount));
    // 1.首次基数排序
    for (int i = 0; i < nNewStrSize; i++)
        arrCount[arrRank[i]]++;
    int nMaxVal = 128;
    for (int i = 1; i < nMaxVal; i++)
        arrCount[i] += arrCount[i - 1];
    for (int i = nNewStrSize - 1; i >= 0; i--)// 用--的方向:在元素相等的情况下,其顺序为下标的顺序.(即同样大小的,先把后面的排在后边)
        arrSufix[--arrCount[arrRank[i]]] = i;// 小于等于第i个元素--arrRank[i]的共有arrCount[arrRank[i]]个,于是第i个元素的排名为个数-1,然后更新个数

    int nRankNum = 0;// 已排序序列的rank数值个数,当nRankNum == nNewStrSize时,表明此时的rank数组已经不重复,即已求出了顺序
    for (int nStep = 1; nRankNum < nNewStrSize; nStep *= 2)
    {
        cout << "nRankNum = "<<nRankNum<<endl;
        // 2.1 计算第二关键字的排序-->arrSufix2
        int nRank = 0;
        for (int i = nNewStrSize - nStep; i < nNewStrSize; i++)// 第m个第二关键字实际为第m+nStep个第一关键字,最后从nNewStrSize - nStep位置开始补了nStep个0.
            arrSufix2[nRank++] = i; // 固第二关键字排序时前nStep个为末尾补的0
        for (int i = 0; i < nNewStrSize; i++)// 第一关键字按排名从小到大追加到第二关键字排序中(剔除前nStep个第一关键字)。
            if (arrSufix[i] >= nStep) arrSufix2[nRank++] = arrSufix[i] - nStep;

        memset(arrCount, 0, sizeof(arrCount));
        // 2.2 基数排序, 用双关键字排序结果更新arrSufix
        //   排序的过程为:先取第二关键字排名在最后的位置k,拿到其第一关键字排名arrRank[k] = rk1, 取出小于等于rk1的个数arrCount[rk1]。由于上一轮的排名数组arrRank是有并列排名的,
        //   即有重复数值这个位置k的双关键字的排名应该为其并列第一排名中的最后一名,即应为arrCount[rk1] - 1. 记录其排名,并更新arrCount[rk1]为自减1.
        // --循环,取下一个第二关键字排名在最后的位置-----至排名为0
        for (int i = 0; i < nNewStrSize; i++)
            arrCount[arrRank[i]]++;
        for (int i = 1; i < nMaxVal; i++)
            arrCount[i] += arrCount[i - 1];
        for (int i = nNewStrSize - 1; i >= 0; i--)
            arrSufix[--arrCount[arrRank[arrSufix2[i]]]] = arrSufix2[i];// arrSufix2[i]即为注释中的k

        // 2.3 更新arrRank
        // 排序后arrSufix中相邻位置的nStep长度字符串可能等,计算排名时要用并列排名
        nRankNum = 1;
        arrRankTemp[arrSufix[0]] = 0; // 特殊字符对应的位置排名最小
        for (int i = 1; i < nNewStrSize; i++)// 若排名i的与i-1相邻
            arrRankTemp[arrSufix[i]] = CompareRank(arrSufix[i], arrSufix[i - 1], nStep) ? nRankNum - 1 : nRankNum++;
        memcpy(arrRank, arrRankTemp, nNewStrSize * sizeof(int));
nMaxVal = nRankNum; } }
void CalHeight(const char* strTest) { char newTest[MaxSize]; int n = strlen(strTest); memcpy(newTest, strTest, n); newTest[n] = '$', newTest[n + 1] = '\0'; // 因为计算Rank和Surfix数组时,给字符串末尾加了个$,其排名为0,于是原始字符串中后缀的排名为1--n int k = 0; for (int i = 0; i < n; i++) { if(k) k--; // arrHeight[arrRank[i]] >= arrHeight[arrRank[i - 1]] - 1 for (int j = arrSufix[arrRank[i] - 1]; newTest[i + k] == newTest[j + k]; k++);// j为排名在i前一位的位置 arrHeight[arrRank[i]] = k; } }

 

posted @ 2013-04-25 10:41  J.Z's World  阅读(182)  评论(0编辑  收藏  举报