第五章 字符串
字符串排序
-
键索引计数法
-
计算出现频率(count[r+1]+=1) //count[0]总为0
for(int i=0;i<N;i++){ count[a[i].key()+1]++; }
-
将频率转化为起始索引(知道每组数据的起始位置)
for(int r=0;r<R;r++){//R为组号 具体有几组 count[r+1]+=count[r]; }
-
数据分类
- 将所有元素移动到一个辅助数组aux[]中经行排序
for(int i=0;i<N;i++){ aux[count[a[i].key()]++]=a[i];//aux元素的位置由count决定 //每次移动之后将count加一以确保其下个元素的索引正确 }
过程图
-
回写(将aux中数据写回a中)
-
for(int i=0;i<N;i++){ a[i]=aux[i]; }
回写结果图
-
-
总代码
int N=a.length; String[] aux=new String[N]; int[] count=new int[R+1]; //计算出现频率 for(int i=0;i<N;i++){ count[a[i].key+1]++; } //计算其索引 for(int r=0;r<R;r++){ count[r+1]+=count[r]; } //在aux中将元素分好类 for(int i=0;i<N;i++){ aux[count[a[i].key()]++]; } //将元素回写 for(int i=0;i<N;i++){ a[i]=aux[i]; }
-
-
LSD(Least significant digit fisrt)低位优先
-
从最低位开始依次到最高位进行键索引计数法
public class LSD{ public static void sort(String[] a,int W)//W为要排序前面多少个字符, { //通过将前W个字符将a[]排序 int N=a.length; int R=256; String[] aux=new String[N]; for(int d=W-1;d=0;d--){//从最低位开始 int[] count=new int[R+1]; //计算频率 for(int i=0;i<n;i++){ count[a[i].charAt(d)+1]++; } //计算索引 for(int r=0;r<R;r++){ count[r+1]+=count[r]; } //在aux中进行分类 for(int i=0;i<N;i++){ aux[count[a[i].charAt(d)]++]=a[i]; } //回写 for(int i=0;i<N;i++){ a[i]=aux[i]; } } } } //从后往前依次排序 因为后面的排过序所以在顺序往下走时其顺序保持不变 从而使整个数组有序 //只适用于长度相同的字符
运行过程图
-
7%AC%94%E8%AE%B0%5C4&pos_id=img-k871FIY4-1704886669984)
-
MSD高位优先
-
需要特别注意的是字符串的末尾,合理的做法是将所有字符都已经被检查过的字符串所在的子数组排在所有子数组前面,这样就不用递归地将该子数组排序。
-
思路:在将一个字符串数组a[]排序时,首先根据它们的首字母用键索引计数法进行排序,然后(递归地)根据子数组中的字符串的首字母将子数组进行排序。
-
总体实现代码
public class MSD{ private static int R=256; private static final int M=15;//小数组的切换阈值 private static String[] aux; private static int charAt(String s,int d){//当指定位置超过了字符串的末尾时,返回-1 if(d<s.length()) return s.charAt(d); else return -1; } public static void sort(String[] a){ int N=a.length; aux=new String[N]; sort(a,0,N-1,0); } public static void sort(String[] a,int lo,int hi,int d){ //以第d个字符为键将a[lo]到a[hi]进行排序 if(hi<=lo+M){ Insertion.sort(a,lo,hi,d); return; } int[] count=new int[R+2]; for(int i=lo;i<=hi;i++){ count[charAt(a[i],d)+2]++; } for(int r=0;r<R+1,r++) count[r+1]+=count[r]; for(int i=lo;i<=hi;i++) aux[count[charAt(a[i],d)+1]++]=a[i]; for(int i=lo;i<=hi;i++) a[i]=aux[i-lo]; for(int r=0;r<R;r++) sort(a,lo+count[r],lo+count[r+1]-1,d+1);//递归的以每个字符为键进行排序 } } //最坏情况 输入的字符串全部一样
运行过程图
举例示意图
-
-
三向字符串快排(快排和高位结合)
-
思路:(先切分三组,再排序)
- 使用首字母将数据切分为“小于”“等于”“大于”的三个子数组
- 递归的将子数组经行排序
-
代码实现以及与快速排序对比
public class Quick3string { private static int charAt(String s,int d){ if(d<s.length()) return s.charAt(d); else return -1; } private static void sort(String[] a) { sort(a,0,a.length-1,0); } //三向字符串排序 private static void sort(String[] a,int lo,int hi,int d){ if(hi<=lo) return; int lt=lo; int gt=hi; int v=charAt(a[lo],d);//基准 int i=lo+1; while(i<=gt){ int t=charAt(a[i],d); if(t<v) exch(a,lt++,i++); else if(t>v) exch(a,i,gt--); else i++; } sort(a,lo,lt-1,d); if(v>=0) sort(a,lt,gt,d+1); sort(a,gt+1,hi,d); } }
过程示意图
举例示意图
各个排序的复杂的对比
-
字符串查找
-
暴力查找 (第一个字符不行就第二个,依次执行直到最后)
- 实现一:
public static int search(){ int M=pat.length(); int N=txt.length(); for(int i=0;i<=N-M;i++){ int j; for(j=0;j<M;j++){ if(txt.charAt(i+j)!=pat.charAt(j)) break; } if(j==M) return i;//匹配成功 } return N; }
-
实现二(具有指导意义,只需要一次遍历即可,指向的是已经匹配字符串的末端)
public static int search(){ int i,N=txt.length(); int j,M=pat.length(); for(i=0,j=0;i<N&&j<M;i++){ if(txt.charAt(i)==pat.charAt(j)) j++; else{ i-=j;//回退 j=0; } } if(j==M) return i-M; else return N; }
-
KMP算法(避免回退到已知字符之前,dfa数组判断跳几个)
- 实现思路:避免将指针回退到所有这些已知字符之前。是使用dfa数组来记录j应该回退多远(最长前后子缀)。
- 思想:提前判断如何重新开始查找,而这种判断只决定于模式本身。
- 关键:构造dfa数组(有限状态自动机)
- 难点:匹配失败时,回退了文本指针并在右移一位之后dfa是怎样的?提前确定好每个可能匹配失败的重启dfa的正确状态。
-
BM算法
- 从右向左扫描(不匹配时看是否有包含pat中的字符来决定下回匹配时向右移动几位,没有的话移动pat.length()个)。
- 重点和难点:跳跃表right[]的计算(有穷状态自动机)
-
RK指纹字符串查找算法
-
基于散列实现的字符串查找方法,需要计算模式字符串的散列函数,然后用相同的散列函数来计算文本中有可能的M个字符的子字符串散列值并进行匹配。(一般散列函数直接是取余数)
-
M比较少直接用int值即可,若M是100or1000则用Horner方法。
-
Horner方法(具体见书P506页图5.3.15及后面数学公式)
private long hash(String key,int M){ long h=0; for(int j=0;j<M;j++){ h=(R*h+key.charAt(j))%Q; return h; } }
-
-
关键是计算散列函数值(数学公式推导而出)
-
整个实现思想:由i个位置开始的M个字母散列值存到txtHash变量中并将每个新的散列值和patHash进行比较。
-