zoukankan      html  css  js  c++  java
  • Suffix Array

    后缀排序

    std::vector<int> suffixSort(std::string s);
    

    传入一个字符串 (s),返回数组 (p)(p[i]) 表示按字典序从小到大排名为 (i) 的后缀位置。

    倍增法

    假设当前 (p) 数组表示字符串集 ({s[i dots i + 2^k - 1]}) 的排名信息(为处理和理解方便,这里将 (s) 当作循环串处理,这样每个字符串的长度都相等,但这样最后得到的是循环后缀的排名信息,可以通过在最后加一个字符集外的字符(例如 $)来解决这个问题)。

    为了得到 ({s[i dots i + 2^{k + 1} - 1]}) 的排名信息(新的 (p) 数组),我们实际上只需要对二元组 ((c_i, c_{i + 2^k})) 排序,这里 (c_i) 表示 (s[i dots i + 2^k - 1]) 的排名,且并列的排名相同。直接使用 std::sort 可以得到代码简单、复杂度 (O(nlog^2n)) 的优秀算法。

    但实际上由于 (c_i) 的值不会超过 (n),可以使用基数排序将复杂度优化为 (O(nlog n))

    双关键字的基数排序((pn) 数组是 (c_{i+2^k}))的排名信息):

    for (int i = 0; i < n; i++) cnt[c[i]]++;
    for (int i = 1; i < m; i++) cnt[i] += cnt[i - 1];
    for (int i = n - 1; i >= 0; i--) p[--cnt[c[pn[i]]]] = pn[i];
    

    完整代码:

    std::vector<int> suffixSort(std::string s) {
      int n = s.length();
      std::vector<int> cnt(std::max(n, 256), 0), c(n), cn(n), p(n), pn(n);
      for (int i = 0; i < n; i++) cnt[s[i]]++;
      for (int i = 1; i < 256; i++) cnt[i] += cnt[i - 1];
      for (int i = 0; i < n; i++) p[--cnt[s[i]]] = i;
      int m = 1;
      c[p[0]] = 0;
      for (int i = 1; i < n; i++) {
        c[p[i]] = s[p[i]] == s[p[i - 1]] ? m - 1 : m++;
      }
      for (int k = 1; k < n; k <<= 1) {
        for (int i = 0; i < n; i++) {
          pn[i] = p[i] >= k ? p[i] - k : p[i] - k + n;
        }
        memset(&cnt[0], 0, m << 2);
        for (int i = 0; i < n; i++) cnt[c[i]]++;
        for (int i = 1; i < m; i++) cnt[i] += cnt[i - 1];
        for (int i = n - 1; i >= 0; i--) p[--cnt[c[pn[i]]]] = pn[i];
        m = 1;
        cn[p[0]] = 0;
        for (int i = 1, x, y = c[(p[0] + k) % n]; i < n; i++) {
          x = y, y = p[i] + k;
          y = c[y >= n ? y - n : y];
          cn[p[i]] = x == y && c[p[i]] == c[p[i - 1]] ? m - 1 : m++;
        }
        if (m >= n) break;
        c.swap(cn);
      }
      return p;
    }
    

    LCP(最长公共前缀)

    int lcp(int i, int j);
    

    求后缀 (i) 和后缀 (j) 的最长公共前缀。

    不妨设 (rank_i < rank_j),那么实际上 (operatorname{lcp}(i, j) = min_{k = rank_i }^{rank_j - 1}operatorname{lcp}(p[k], p[k + 1]))

    (h[i] = operatorname{lcp}(p[i], p[i + 1])),我们只需要处理出 (h) 数组即可将该问题转化为RMQ问题。

    为理解方便,这里的 后缀 仍表示上述后缀排序中的 循环后缀,可以通过字符串末尾加很小的字符来得到正确的答案。

    对于相邻的两个循环后缀 (i - 1, i),很容易发现 (h[rank_{i}] ge h[rank_{i - 1}] - 1),所以计算 (h[rank_i]) 时从 (h[rank_{i - 1}] - 1) 暴力拓展,总拓展次数不会超过 (2n) 次,于是可以用 (O(n)) 时间构造出 (h) 数组。

    std::vector<int> getHeight(const std::vector<int> &p) {
      int n = p.size();
      std::vector<int> rank(n, 0);
      for (int i = 0; i < n; i++) rank[p[i]] = i;
      int k = 0;
      std::vector<int> h(n - 1, 0);
      for (int i = 0; i < n; i++) {
        if (rank[i] == n - 1) {
          k = 0; continue;
        }
        int j = p[rank[i] + 1];
        while (i + k < n && j + k < n && s[i + k] == s[j + k])
          k++;
        h[rank[i]] = k;
        if (k) k--;
      }
      return h;
    }
    
  • 相关阅读:
    Spark大数据处理 之 从WordCount看Spark大数据处理的核心机制(2)
    大话重构 之 消除巨无霸类
    Spark大数据处理 之 从WordCount看Spark大数据处理的核心机制(1)
    大话重构 之 消除过长方法
    Spark大数据处理 之 动手写WordCount
    高效法则 之 你还在用这么low的方法打开软件吗?
    大话重构 之 解决万恶之首“重复代码”
    高效法则 之 码龙们,你们还在浪费自己的生产率吗?!
    Mac版,mysql重置密码
    flask-- 基础篇
  • 原文地址:https://www.cnblogs.com/HolyK/p/13975494.html
Copyright © 2011-2022 走看看