后缀数组原理以及实现
后缀数组可以解决后缀排序,字符串查找以及最长重复子串等问题。
给定一个字符串求出其中最大的重复子串,例如s="it was the best time it was" 返回 “it was”;
求出两个字符串的最大公共前缀
从两个字符串起始位置开始遍历比较,直到两个子串某个位置字符不一致,时间复杂度O(N)
//两个字符串最大公共前缀private static int lcp(String s, String t){int min = Math.min(s.length(), t.length());for(int i=0; i<min; i++){if(s.charAt(i)!=t.charAt(i))return i;}return min;}
暴力方法求解最大重复子串
利用双层循环,遍历所有子串对的所有情况,对于每一对子串都使用lcp来求解最大公共前缀,时间复杂度为O(N3)
public String forcelrs(String s){String lrs = "";for(int i=0; i<s.length(); i++){for(int j=i+1; j<s.length(); j++){int l = lcp(s.substring(i), s.substring(j));if(l>lrs.length()){lrs = s.substring(0, l);}}}return lrs;}
利用后缀数组求解最大重复子串
输入字符串s,获取s的所有后缀子串,将s所有后缀子串排序(排序之后最大重复子串必定相邻),然后依次比较相邻后缀子串的最大公共前缀,可以得到最终的最大重复子串。
在此定义一种数据结构后缀数组来处理字符串,将字符串的后缀子串进行排序。
一个长度为N的字符串,后缀子串有N种,可以利用String substring方法来求解。
后缀数组API
SuffixArray(String s) 有一个字符串构造后缀数组 获取该字符串所有后缀 排序
int length() 返回字符串后缀数组大小
String select(int i) 返回后缀数组中索引为i的子字符串
int index(int i) 返回后缀数组中索引为i的子字符串在原始字符串中的起始位置
int lcp(int i) 返回后缀数组中i以及i-1字符串的最大公共前缀
例如输入s="it was the best time it was"
构造的后缀数组为:
best time it wasit wasthe best time it wastime it waswaswas the best time it was
as
as the best time it was
best time it was
e best time it was
e it was
est time it was
he best time it was
ime it was
it was
it was the best time it was
me it was
s
s the best time it was
st time it was
t time it was
t was
t was the best time it was
the best time it was
time it was
was
was the best time it was
it was
将该后缀数组遍历一遍,依次比较相邻两个子字符串的最大公共前缀,即可得字符串的最大重复子串
String str = "it was the best time it was";//求字符串的最大重复子串SuffixArray sa = new SuffixArray(str);String lrs = "";for(int i=1; i<sa.N; i++){if(sa.lcp(i) > lrs.length())lrs = sa.select(i).substring(0, sa.lcp(i));}System.out.println(lrs);
由以上可知,该算法的时间复杂度为O(N2)
除此以外,后缀数组能够解决字符串的查找问题,在一段文本中需要查找某一特定字符串,可以先将文本处理得到后缀数组,此时所有的子字符串是有序的,可以使用二分查找来寻找待检索的字符串。
//返回某个后缀字符串key在后缀数组中的索引值//由于后缀数组已经有序 可以使用二分查找public int rank(String key){int low = 0; int high = N-1;while(low<=high){int mid = (low + high) / 2;int cmp = key.compareTo(suffixs[mid]);if(cmp==0) return mid;else if(cmp<0) high = mid-1;else low = mid + 1;}return -1;}
全部实现代码如下:
public class SuffixArray {public static void main(String[] args) {String str = "it was the best time it was";//求字符串的最大重复子串SuffixArray sa = new SuffixArray(str);String lrs = "";for(int i=1; i<sa.N; i++){if(sa.lcp(i) > lrs.length())lrs = sa.select(i).substring(0, sa.lcp(i));}System.out.println(lrs);//System.out.println(sa.forcelrs(str));}private final String[] suffixs;private final int N;public SuffixArray(String s){N = s.length();suffixs = new String[N];for(int i=0; i<N; i++){suffixs[i] = s.substring(i);}System.out.println(Arrays.toString(suffixs));Arrays.sort(suffixs);System.out.println(Arrays.toString(suffixs));}//返回某个后缀字符串key在后缀数组中的索引值//由于后缀数组已经有序 可以使用二分查找public int rank(String key){int low = 0; int high = N-1;while(low<=high){int mid = (low + high) / 2;int cmp = key.compareTo(suffixs[mid]);if(cmp==0) return mid;else if(cmp<0) high = mid-1;else low = mid + 1;}return -1;}public int length(){return N;}//后缀索引中i位置字符串public String select(int i){return suffixs[i];}//后缀索引中i位置字符串在原始字符串中的索引public int index(int i){return N - suffixs[i].length();}//一个字符串后缀数组索引位置i与前一个位置子字符串的最长重复子串public int lcp(int i){return lcp(suffixs[i], suffixs[i-1]);}//两个字符串最大公共前缀private static int lcp(String s, String t){int min = Math.min(s.length(), t.length());for(int i=0; i<min; i++){if(s.charAt(i)!=t.charAt(i))return i;}return min;}
}
上述后缀数组实现需要大量的空间O(N)以及如果所有字符串都相等,则字符串的排序效率过低以及最长重复子串会变为子字符串长度平方级别,因此实现效率不高。
优化后缀数组实现
在空间上,不需要存储每一个后缀字符串。通过创建一个索引数组index来表示字符串在后缀数组中的顺序信息。
核心变量
//字符串字符存储private final char[] text;//index[j]=i 表示text.substring(j)是第i大子串private final int[] index; //字符串长度private final int n;
存储字符串的每一个字符,保存字符串长度以及保存字符串后缀子串的排序位置;
其中index[j]=i表示text.substring(j)后缀子串在整个后缀数组中排序索引为i,即第i大的子串;
由此可以不需要保存字符串的每一个后缀子串。
后缀数组的构造
给定字符串,将字符串转化为字符数组,然后利用三向快速排序,将字符串所有后缀索引排序
public SuffixArrayX(String text){n = text.length();text = text + '\0';this.text = text.toCharArray();this.index = new int[n];for(int i=0; i<n; i++){index[i] = i;}//System.out.println(this.text);//System.out.println(Arrays.toString(this.index));//三向快速排序sort(0, n-1, 0);//System.out.println(Arrays.toString(this.index));//for(int i=0; i<n; i++){//System.out.println(select(i));//}}
三向快速排序的实现
如果数组元素个数小于一定阈值,则使用插入排序来处理。
其余使用三向快速排序,避免元素大量相同时,快速排序效率过低。
//三向快速排序private void sort(int lo, int hi, int d) {//如果数组较小使用插入排序if(hi<=lo+CUTOFF){insertion(lo, hi, d);return;}int lt = lo;int gt = hi;char v = text[index[lo]+d];int i = lo + 1;while(i<gt){char t = text[index[i]+d];//交换后i指向lt值,此时可以向前移动一个位置if(t<v) exch(lt++, i++);//交换之后i位置值为gt值,其大小需要与t进行比较,因此i不需要移动else if(t>v) exch(i, gt--);else i++;}// a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi]. sort(lo, lt-1, d);if(v>0) sort(lt, gt, d+1);sort(gt+1, hi, d);}// sort from a[lo] to a[hi], starting at the dth characterprivate void insertion(int lo, int hi, int d) {for(int i=lo; i<=hi; i++){for(int j=i; j>lo&&less(index[j], index[j-1], d); j--){exch(j, j-1);}} }private void exch(int j, int i) {int tmp = index[i];index[i] = index[j];index[j] = tmp;}//is text[i+d..n) < text[j+d..n) ?private boolean less(int i, int j, int d) {if(i==j) return false;i = i + d;j = j + d;while(i<n &&j<n){if(text[i]<text[j]) return true;if(text[i]>text[j]) return false;i++;j++;}return i > j;}
完整代码实现:
public class SuffixArrayX {public static void main(String[] args) {String str = "it was the best time it was";//求字符串的最大重复子串SuffixArrayX sax = new SuffixArrayX(str);String lrs = "";for(int i=1; i<sax.n; i++){System.out.println(sax.lcp(i) + " " + lrs.length());if(sax.lcp(i) > lrs.length()){ lrs = sax.select(i).substring(0, sax.lcp(i));System.out.println("lrs = " + lrs);}//System.out.println("lrs = " + lrs);}System.out.println("lrs = " + lrs);System.out.println(lrs);}private static final int CUTOFF = 5;//字符串字符存储private final char[] text;//index[j]=i 表示text.substring(j)是第i大子串private final int[] index; //字符串长度private final int n;public SuffixArrayX(String text){n = text.length();text = text + '\0';this.text = text.toCharArray();this.index = new int[n];for(int i=0; i<n; i++){index[i] = i;}//System.out.println(this.text);//System.out.println(Arrays.toString(this.index));//三向快速排序sort(0, n-1, 0);//System.out.println(Arrays.toString(this.index));//for(int i=0; i<n; i++){//System.out.println(select(i));//}}//三向快速排序private void sort(int lo, int hi, int d) {//如果数组较小使用插入排序if(hi<=lo+CUTOFF){insertion(lo, hi, d);return;}int lt = lo;int gt = hi;char v = text[index[lo]+d];int i = lo + 1;while(i<gt){char t = text[index[i]+d];//交换后i指向lt值,此时可以向前移动一个位置if(t<v) exch(lt++, i++);//交换之后i位置值为gt值,其大小需要与t进行比较,因此i不需要移动else if(t>v) exch(i, gt--);else i++;}// a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi]. sort(lo, lt-1, d);if(v>0) sort(lt, gt, d+1);sort(gt+1, hi, d);}// sort from a[lo] to a[hi], starting at the dth characterprivate void insertion(int lo, int hi, int d) {for(int i=lo; i<=hi; i++){for(int j=i; j>lo&&less(index[j], index[j-1], d); j--){exch(j, j-1);}} }private void exch(int j, int i) {int tmp = index[i];index[i] = index[j];index[j] = tmp;}//is text[i+d..n) < text[j+d..n) ?private boolean less(int i, int j, int d) {if(i==j) return false;i = i + d;j = j + d;while(i<n &&j<n){if(text[i]<text[j]) return true;if(text[i]>text[j]) return false;i++;j++;}return i > j;}public int length(){return n;}//第i大子字符串在原始字符串的起始位置public int index(int i){return index[i];}public int lcp(int i){return lcp(index[i], index[i-1]);}//返回text从i,j位置开始的最大公共前缀private int lcp(int i, int j){int length = 0;while(i<n && j<n){//System.out.println(text[i] + " " + text[j]);//System.out.println(i + " " + j + " " + select(i) + " " + select(j) + "----" + text[i] + " " + text[j]);if(text[i]!=text[j]) return length;i++;j++;length++;}return length;}public String select(int i){return new String(text, index[i], n-index[i]);}public int rank(String query) {int lo = 0, hi = n - 1;while (lo <= hi) {int mid = lo + (hi - lo) / 2;int cmp = compare(query, index[mid]);if (cmp < 0) hi = mid - 1;else if (cmp > 0) lo = mid + 1;else return mid;}return lo;} // is query < text[i..n) ?private int compare(String query, int i) {int m = query.length();int j = 0;while (i < n && j < m) {if (query.charAt(j) != text[i]) return query.charAt(j) - text[i];i++;j++;}if (i < n) return -1;if (j < m) return +1;return 0;}}
https://algs4.cs.princeton.edu/code/edu/princeton/cs/algs4/SuffixArray.java.html
https://algs4.cs.princeton.edu/code/edu/princeton/cs/algs4/SuffixArrayX.java.html
后缀数组原理以及实现相关推荐
- c++ 字符串数组长度排序_数组 | 后缀数组的求法及应用
作者:Andy__lee 链接:https://blog.nowcoder.net/n/6b4a93e186ed4a358321de6a7c3b4f19 来源:牛客网 定义 维基百科 - 后缀数组 让 ...
- 字符串相关处理kmp,前缀数,后缀树,后缀数组,最长回文串,最长重复字串,最长非重复字串
1. 最长回文串 一般用后缀数组或者后缀树可以解决, 用此方法:http://blog.csdn.net/v_july_v/article/details/6897097 预处理后缀树,使得查询LCA ...
- 五分钟搞懂后缀数组!
为什么学后缀数组 后缀数组是一个比较强大的处理字符串的算法,是有关字符串的基础算法,所以必须掌握. 学会后缀自动机(SAM)就不用学后缀数组(SA)了?不,虽然SAM看起来更为强大和全面,但是有些S ...
- [Pku 2774] 字符串(六) {后缀数组的构造}
{ 从这一篇开始介绍后缀数组 一个强大的字符串处理工具 可以先研读罗穗骞的论文 后缀数组--处理字符串的有力工具 再行阅读本文 本文仅作参考和补充 } 字符串的后缀很好理解 譬如对于字符串" ...
- 笨办法学 Python · 续 练习 22:后缀数组
练习 22:后缀数组 原文:Exercise 22: Suffix Arrays 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 我想告诉你一个关于后缀数组的故事.在一段时间里, ...
- 后缀数组(倍增)学习记录,我尽可能详细的讲了
后缀数组(倍增) 后缀数组 后缀数组能干什么 一些基本概念 那么到底怎么排序呢? 倍增排序 具体执行排序呢? 基数排序 关于排序的桶 关于桶排序在字符串倍增中的嵌入 具体改执行的排序事情 倍增排序的代 ...
- 【后缀数组】伊芙利特之祭--总结
前几个月总结的后缀数组专题,现在贴出来吧. 不可重叠重复串; 可重叠k次重复串; 不相同子串个数 最长回文子串 连续重复子串 重复次数最多的连续重复子串 最长公共子串 长度不小于k 的公共子串的个 ...
- 五分钟搞懂后缀数组!后缀数组解析以及应用(附详解代码)
为什么学后缀数组 后缀数组是一个比较强大的处理字符串的算法,是有关字符串的基础算法,所以必须掌握. 学会后缀自动机(SAM)就不用学后缀数组(SA)了?不,虽然SAM看起来更为强大和全面,但是有些SA ...
- doubling algorithm 构建后缀数组 code
suffix array的 相关知识及利用doubling algorithm构建原理详见 http://download.csdn.net/detail/wmj75617718/6724275 以下 ...
最新文章
- 推动边缘计算的七项核心技术
- 多台Linux服务器之间互相免密登陆
- JWT token信息保存
- uniapp跨域两次请求解决方案
- 安装Ubuntu镜像和VMware在安装Ubuntu镜像之后开机蓝屏的解决方案
- python入口文件详解_Python基础系列讲解——那些py文件中容易忽略的细节
- android 替代map,Android为什么推荐使用SparseArray来替代HashMap?
- python爬虫实例(爬取航班信息)
- [转]各种配置管理工具的比较
- 基于VRML的虚拟校园漫游系统
- dsniff嗅探工具
- 产品经理/总监 面试题及答案
- 读季琦《创始人·手记》
- 一个操作系统的实现(8)进程间通信
- imx6ull移植Linux系统第二篇——Linux内核的移植
- CodeForces 319B 栈
- 如何在Mac和Windows PC之间无线共享文件
- 专访阿里巴巴量子实验室:最强量子电路模拟器“太章”到底强在哪? 1
- 1--if中的return的作用/条件判断中如何退出函数
- 如何制作照片马赛克礼物