后缀数组
详细的学习了后缀数组,及其应用。由于罗穗骞论文中的代码写的有些随意,我按照自己的思路,算是整理了一下吧。
倍增法求 后缀数组,以及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; } }