数据结构与算法-串

串及其匹配

串或字符串属于线性结构,结构简单,规模庞大,元素重复率高

  • 相关术语:
  • 接口:

  • 测评标准
    有效涵盖成功匹配情况的一种简便策略是,随机选取文本串T,并从T中随机取出长度为m的子串作为模式串P。

蛮力算法

原理


实现

package com.atguigu.string;
/**
 * @anthor shkstart
 * @create 2020-08-16 8:57
 */
public class brute extends Str{
	public static int match1(char[] P, char[] T){
		int n = P.length;
		int m = T.length;
		int i = 0;
		int j = 0;
		while (j < m && i < n){
			if (T[i] == P[j]){
				i++;
				j++;
			} else {
				i -= j - 1;
				j = 0;
			}
		}
		return i - j;
	}
	public static int match2(char[] P, char[] T){
		int n = P.length;
		int m = T.length;
		int i = 0;
		int j = 0;
		for (;i < n - m;i++){
			for (;j < m;j++){
				if (T[i+j] != P[j]) break;
			}
			if ( j >= m) break;
		}
		return i;
	}
}

效率

KMP算法

分析上面的方法:

  • 发现问题在于这里存在大量的局部匹配:每一轮的m次比对中,仅最后一次可能失配。而一旦发现失配,文本串、模式串的字符指针都要回退,并从头开始下一轮尝试。没有很好的利用已经掌握的信息。

  • 利用以往的成功比对所提供的信息(记忆),不仅可避免文本串字符指针的回退,而且可使模式串尽可能大跨度地右移

next表


  • 满足条件:P[0, t) = T[i - t, i) = P[j - t, j)
  • 原理
  • 实现
public int[] buildNext1(char[] P){
	int m = P.length;
	int j = 0;
	int[] N = new int[m];
	int t = N[0] = -1;
	while (j < m-1){
		if (0 > t || P[j] == P[t]){
			j++;
			t++;
			N[j] = t;
		} else {
			t = N[t];
		}
	}
	return N;
}
public int[] buildNext2(char[] P){
	int m = P.length;
	int j = 0;
	int[] N = new int[m];
	int t = N[0] = -1;
	while (j < m-1){
		if (0 > t || P[j] == P[t]){
			j++;
			t++;
			N[j] = (P[j] != P[t] ? t:N[t]);
		} else {
			t = N[t];
		}
	}
	return N;
}

实现

package com.atguigu.string;
/**
 * @anthor shkstart
 * @create 2020-08-16 8:57
 */
public class Kmp extends Str{
	public int match(char[] P, char[] T){
		int[] next = buildNext1(P);
		int n = P.length;
		int m = T.length;
		int i = 0;
		int j = 0;
		while (j < m && i < n){
			if (T[i] == P[j]){
				i++;
				j++;
			} else {
				j = next[j];
			}
		}
		return i - j;
	}
	public int[] buildNext1(char[] P){
		int m = P.length;
		int j = 0;
		int[] N = new int[m];
		int t = N[0] = -1;
		while (j < m-1){
			if (0 > t || P[j] == P[t]){
				j++;
				t++;
				N[j] = t;
			} else {
				t = N[t];
			}
		}
		return N;
	}
	public int[] buildNext2(char[] P){
		int m = P.length;
		int j = 0;
		int[] N = new int[m];
		int t = N[0] = -1;
		while (j < m-1){
			if (0 > t || P[j] == P[t]){
				j++;
				t++;
				N[j] = (P[j] != P[t] ? t:N[t]);
			} else {
				t = N[t];
			}
		}
		return N;
	}
}

实例


BM算法

原理

KMP算法的思路可概括为:当前比对一旦失配,即利用此前的比对(无论成功或失败)所提 供的信息,尽可能长距离地移动模式串。事先根据模式串预测出所可能出现的失配情况,并将这些信息 “浓缩”为一张next表。而BM算法一旦局部失配,这里不再是机械地令i += 1并在下一字符处重新对齐,而是采用了两种启发式策略确定最大的安全移动距离



坏字符策略

  • 快速排序不能对齐的位置

    移动情况:从右至左,一旦不匹配.从右开始找与x匹配的数,然后移动;或者太靠右,只移动一位
  • 构建bc表
public int[] buildBc(char[] P){
	int[] bc = new int[256];
	for (int j = 0;j < 256;j++) bc[j] = -1;
	for (int m = P.length,j = 0;j < m;j++){
		bc[P[j]] = j;
	}
	return bc;
}

好后缀策略

  • 成功的对比
    坏字符策略仅利用了此前(最后一次)失败比对所提供的“教训”。而实际上在此之前,还做过一系列成功的比对,而这些“经验”却被忽略了
  • 分析

    对齐的情况:
    已经发现w与U一直都对齐,直到x与y不同
    需要从在p中从右向左找到一个子串能够满足v(k)=u且k处与J处不相等
    如果不存在,则从p的所有前缀中找与某一后缀最匹配长度的
  • gs、ss表的构建

    gs表每个位置对应,应该移动多少位;其中的gs[10] = 6可理解为:一旦在P[10] = 'P'处发生失配,则应将模式串P右移6个字符,即用P[10 - 6] = P[4] = '□'对准文本串T的失配字符,然后启动下一轮比对
    ss表对于任一整数j Î [0, m),在P[0, j]的所后缀中,考查那些与P的某一后缀匹配者。若将其中的最长者记作MS[j],则ss[j]就是该串的长度|MS[j]|。


public int[] buildGS(char[] P){
	int[] ss = buildSS(P);
	int m = P.length;
	int[] gs = new int[m];
	for (int j = 0; j < m;j++) gs[j] = m;
	for (int i = 0,j = m-1;j > 0;j--){
		if (j + 1 == ss[j]){
			while (i < m - j - 1){
				gs[i++] = m - j -1;
			}
		}
	}
	for (int j = 0;j < m-1;j++){
		gs[m - ss[j] - 1] = m-j - 1;
	}
	return gs;
}
public int[] buildSS(char[] P){
	int m = P.length;
	int[] ss = new int[m];
	ss[m-1] = m;
	for (int lo = m-1,hi = m - 1,j = lo - 1;j >= 0;j--){
		if ((lo < j) && (ss[m - hi + j -1] <= j - lo)){
			ss[j] = ss[m - hi +j -1];
		} else {
			hi = j;
			lo = Math.min(lo,hi);
			while ((0 <= lo) && (P[lo] == P[m - hi + lo -1])){
				lo--;
			}
			ss[j] = hi -lo;
		}
	}
	return ss;
}

java实现

package com.atguigu.string;
/**
 * @anthor shkstart
 * @create 2020-08-16 8:57
 */
public class Bm extends Str{
	public int match(char[] P, char[] T){
		int[] bc = buildBc(P);
		int[] gs = buildGS(P);
		int i = 0;
		while (T.length >= i + P.length){
			int j = P.length - 1;
			while (P[j] == T[i+j]){
				if (0 > --j) break;
			}
			if (0 > j){
				break;
			} else {
				i += Math.max(gs[j],j - bc[T[i+j]]);
			}
		}
		return i;
	}
	public int[] buildBc(char[] P){
		int[] bc = new int[256];
		for (int j = 0;j < 256;j++) bc[j] = -1;
		for (int m = P.length,j = 0;j < m;j++){
			bc[P[j]] = j;
		}
		return bc;
	}
	public int[] buildGS(char[] P){
		int[] ss = buildSS(P);
		int m = P.length;
		int[] gs = new int[m];
		for (int j = 0; j < m;j++) gs[j] = m;
		for (int i = 0,j = m-1;j > 0;j--){
			if (j + 1 == ss[j]){
				while (i < m - j - 1){
					gs[i++] = m - j -1;
				}
			}
		}
		for (int j = 0;j < m-1;j++){
			gs[m - ss[j] - 1] = m-j - 1;
		}
		return gs;
	}
	public int[] buildSS(char[] P){
		int m = P.length;
		int[] ss = new int[m];
		ss[m-1] = m;
		for (int lo = m-1,hi = m - 1,j = lo - 1;j >= 0;j--){
			if ((lo < j) && (ss[m - hi + j -1] <= j - lo)){
				ss[j] = ss[m - hi +j -1];
			} else {
				hi = j;
				lo = Math.min(lo,hi);
				while ((0 <= lo) && (P[lo] == P[m - hi + lo -1])){
					lo--;
				}
				ss[j] = hi -lo;
			}
		}
		return ss;
	}
}

效率


单次比对成功的概率,是决定串匹配算法时间效率的一项关键因素。对于同一算法,计算时间与Pr具有单调正相关关系

Karp-Rabin算法

凡事皆树:将任一有限长度的整数向量视作自然数,进而在字符串与自然数之间建立联系

原理

判断模式串P是否与文本串T匹配”的问题,可以转化为“判断T中是否 某个子串与模式串P拥相同的指纹

  • 散列压缩
  • 散列冲突
  • 指纹快速更新

实现

package com.atguigu.string;
/**
 * @anthor shkstart
 * @create 2020-08-16 8:57
 */
public class Fingerprint extends Str{
    public int match(char[] P, char[] T){
	int m = P.length;
	int n = T.length;
	int Dm = peapreDm(m),hashP = 0,hashT = 0;
	for (int i = 0;i < m;i++){
		hashP = (hashP * R + DIGIT(P,i)) % M;
		hashT = (hashT * R + DIGIT(T,i)) % M;
	}
	for (int k = 0; ;){
		if (hashT == hashP){
			if (check1by1(P,T,k)) return k;
		}
		if (++k > n -m) return k; else updateHash(hashT,T,m,k,Dm);
	}
}
public static int M = 97;
public static int R = 10;
public int DIGIT(char[] S,int i){
	return S[i] - '0';
}
public Boolean check1by1(char[] P,char[] T,int i){
	for (int m = P.length,j = 0;j < m;j++,i++){
		if (P[j] != T[i]) return false;
	}
	return true;
}
public int peapreDm(int m){
	int Dm = 1;
	for (int i = 1;i < m;i++){
		Dm = (R * Dm) % M;
	}
	return Dm;
}
public void updateHash(int hashT,char[] T,int m,int k,int Dm){
	hashT = (hashT - DIGIT(T,k - 1) * Dm) % M;
	hashT = (hashT * R + DIGIT(T,k + m - 1)) % M;
	if (0 > hashT) hashT += M;
}
}
原文地址:https://www.cnblogs.com/suit000001/p/13526067.html