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


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

function DoubleAdding(strSource){
    var  nNewStrSize = strSource.length + 1;
    var  arrSufix2 = new Array(MaxSize);// 第二关键字的排序数组
    var  arrRankTemp = new Array(MaxSize);// 用于从上次的arrRank计算更新后的arrRank

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

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

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

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

function CalHeight(strTest){
    var newTest = new Array(MaxSize);
    var  n = strTest.length;
    memcpy(newTest, strTest, n);
    newTest[n] = "$", newTest[n + 1] = "\0";
    // 因为计算Rank和Surfix数组时，给字符串末尾加了个$，其排名为0，于是原始字符串中后缀的排名为1--n
    var  k = 0;
    for (var  i = 0; i < n; i++) {
        if(k) {
            k--;
        } // arrHeight[arrRank[i]] >= arrHeight[arrRank[i - 1]] - 1
        for (var  j = arrSufix[arrRank[i] - 1]; newTest[i + k] == newTest[j + k]; k++){}// j为排名在i前一位的位置
        arrHeight[arrRank[i]] = k;
    }
}

function memcpy(a, b, n){
    for(var i = 0; i < n; i++){
        a[i] = b[i];
    }
}