算法之字符串匹配


编程语言提供的字符串查找函数,比如 Java 中的 indexOf(),Python 中的 find() 函数等,它们底层就是依赖字符串匹配算法。字符串匹配算法有很多种,比较简单暴力的是BF算法和RK算法。RK 算法是 BF 算法的改进,它巧妙借助了哈希算法,让匹配的效率有了很大的提升。


BF匹配算法

BF 算法中的 BF 是 Brute Force 的缩写,中文叫作暴力匹配算法,也叫朴素匹配算法。从名字可以看出,这种算法的字符串匹配方式很“暴力”,当然也就会比较简单、好懂,但相应的性能也不高。

先举个例子,在字符串 A 中查找字符串 B,那字符串 A 就是主串,字符串 B 就是模式串。把主串的长度记作 n,模式串的长度记作 m。因为是在主串中查找模式串,所以 n>m。在主串中,检查起始位置分别是 0、1、2…n-m 且长度为 m 的 n-m+1 个子串,看有没有跟模式串匹配的。
在这里插入图片描述
在极端情况下,比如主串是“aaaaa…aaaaaa”(省略号表示有很多重复的字符 a),模式串是“aaaaab”。每次都比对 m 个字符,要比对 n-m+1 次,所以,这种算法的最坏情况时间复杂度是 O(n*m)。

从上面的分析看出,BF匹配算法的时间复杂度很高,不过在实际开发中,他却比较常用,原因有以下两点:

1:每次模式串与主串中的子串匹配的时候,当中途遇到不能匹配的字符的时候,就可以就停止了,不需要把 m 个字符都比对一下。所以,尽管理论上的最坏情况时间复杂度是 O(n*m),但是,统计意义上,大部分情况下,算法执行效率要比这个高很多。

2:朴素字符串匹配算法思想简单,代码实现也非常简单。 简单意味着不容易出错,如果有 bug 也容易暴露和修复。就是所谓的"Keep it Simple and Stupid"。

BF匹配算法代码实现:

package 匹配;

public class Bf {
	
	public static int bF(String a, String b) {
		int m = a.length();
		int n = b.length();
		int k = 0;
		// 将字符串变成字符数组比较两个字符串的字符
		char[] a1 = a.toCharArray();
		char[] b1 = b.toCharArray();
		// 总共比较m-n+1次
		for(int i=0;i<=m-n;i++) {
			k=0;
			// 每次比较n个字符
			for(int j=0;j<n;j++) {
				// 如果两个字符串中的字符相同,k加一
				if(a1[i+j]==b1[j]) {
					k++;
				}
				// 否则选取主串的下一个子串进行匹配
				else
					break;
			}
			if(k==n) {
				return i;
			}
		}
		return -1;
	}
	
	public static void main(String[] args) {
		int bF = bF("badef", "def");
		System.out.println(bF);
	}

}

RK匹配算法

RK 算法的全称叫 Rabin-Karp 算法,是由它的两位发明者 Rabin 和 Karp 的名字来命名的。它其实就是刚刚讲的 BF 算法的升级版。

在BF算法中,如果模式串长度为 m,主串长度为 n,那在主串中,就会有 n-m+1 个长度为 m 的子串,只需要暴力地对比这 n-m+1 个子串与模式串,就可以找出主串与模式串匹配的子串。但是,每次检查主串与子串是否匹配,需要依次比对每个字符,所以 BF 算法的时间复杂度就比较高,是 O(n*m)。对朴素的字符串匹配算法稍加改造,引入哈希算法,时间复杂度立刻就会降低。

RK算法的思路:通过哈希算法对主串中的 n-m+1 个子串分别求哈希值,然后逐个与模式串的哈希值比较大小。 如果某个子串的哈希值与模式串相等,那就说明对应的子串和模式串匹配了(这里先不考虑哈希冲突的问题,后面我们会讲到)。因为哈希值是一个数字,数字之间比较是否相等是非常快速的,所以模式串和子串比较的效率就提高了。
在这里插入图片描述
模式串哈希值与每个子串哈希值之间的比较的时间复杂度是 O(1),总共需要比较 n-m+1 个子串的哈希值,所以,这部分的时间复杂度也是 O(n)。所以,RK 算法整体的时间复杂度就是 O(n)。

RK算法代码实现如下:

package 匹配;

/**
 * RK字符串匹配代码实现
 * @author 15447
 *
 */

public class Rk {

	/**
	 * RK代码实现
	 * @param a
	 * @param b
	 * @return
	 */
	public static int rK(String a, String b) {
		int m = a.length(), n = b.length(), s, j;
		int[] hash = new int[m - n + 1];
		int[] table = new int[26];
		char[] a1 = a.toCharArray();
		char[] b1 = b.toCharArray();
		s = 1;
		// 将26的次方存储在一个表里
		for (j = 0; j < 26; j++) {
			table[j] = s;
			s *= 26;
		}
		// 计算出主串中各个子串的hash值
		for (int i = 0; i <= m - n; i++) {
			s = 0;
			for (j = 0; j < n; j++) {
				s += (a1[i + j] - 'a') * table[n - 1 - j];
			}
			hash[i] = s;
		}
		s = 0;
		// 将模式串的hash值计算出来
		for (j = 0; j < n; j++) {
			s += (b1[j] - 'a') * table[n - 1 - j];
		}
		// 比较各个子串和模式串的哈希值
		for (j = 0; j < m - n + 1; j++) {
			if (hash[j] == s) {
				return j;
			}
		}
		return -1;
	}
	
	public static void main(String[] args) {
		int rK = rK("badef", "def");
		System.out.println(rK);
	}
	
}

猜你喜欢

转载自blog.csdn.net/weixin_43894879/article/details/107891284